ruby-on-rails - 在 Rails 的 API 包装器中使用 Thread.current 的替代方法

标签 ruby-on-rails ruby api

我开发了一个应用程序,允许我们的客户创建他们自己的成员(member)保护网站。然后,我的应用程序连接到外部 API 服务(客户特定的 api_key/api_url)以将数据同步/更新/添加到该其他服务。好吧,我已经为到目前为止一直有效的其他服务编写了一个 API 包装器。但是,我现在看到连接为零的情况非常随机。这是我目前使用连接的方式:

我有一个 xml/rpc 连接类

class ApiConnection
  attr_accessor :api_url, :api_key, :retry_count

  def initialize(url, key)
    @api_url = url
    @api_key = key
    @retry_count = 1
  end

  def api_perform(class_type, method, *args)
    server = XMLRPC::Client.new3({'host' => @api_url, 'path' => "/api/xmlrpc", 'port' => 443, 'use_ssl' => true})
    result = server.call("#{class_type}.#{method}", @api_key, *args)
    return result
  end
end

我还有一个模块可以包含在我的模型中以访问和调用 api 方法

module ApiService

  # Set account specific ApiConnection obj
  def self.set_account_api_conn(url, key)
    if ac = Thread.current[:api_conn]
      ac.api_url, ac.api_key = url, key
    else
      Thread.current[:api_conn] = ApiConnection.new(url, key)
    end
  end

  ########################
  ###  Email Service   ###
  ########################

  def api_email_optin(email, reason)
    # Enables you to opt contacts in
    Thread.current[:api_conn].api_perform('APIEmailService', 'optIn', email, reason)
  end

  ### more methods here ###

end

然后在应用程序 Controller 中,我使用设置 Thread.current[:api_conn] 的前过滤器在每个请求上创建一个新的 ApIConnection 对象。这是因为我有数百个客户,每个客户都有自己的 api_key 和 api_url,同时使用该应用程序。

# In before_filter of application controller
def set_api_connection
  Thread.current[:api_conn] = ApiService.set_account_api_conn(url, key)
end

我的问题是,我读过使用 Thread.current 并不是处理这个问题的最理想方式,我想知道这是否是 ApiConnection 为 nil 的原因根据随机请求。所以我想知道如何更好地设置这个包装器。

最佳答案

答案 1

我预计问题是连接完成之前的下一个请求,然后 before_filter 覆盖仍在进行中的连接的连接。我会尽量远离线程。 fork_off 更容易,但也有一些注意事项,尤其是在性能方面。

我尝试将这样的逻辑移至某种后台作业。一个常见的解决方案是延迟作业 https://github.com/collectiveidea/delayed_job这样您就不必弄乱线程,而且它更健壮且易于调试。然后,您可以启动后台作业,以便在有人登录时异步同步服务。

@account.delay.optin_via_email(email,user)

这将序列化帐户,将其保存到作业队列中,在那里它将被反序列化的延迟作业拾取,并调用延迟后的方法。您可以有任意数量的后台作业,甚至可以有一些专用于某些类型操作的作业队列(通过使用作业优先级 - 假设两个 bj 用于高优先级作业,一个专用于低优先级作业)

答案 2

只是把它变成一个对象

def before_filter
  @api_connection =  ApiConnection.new(url, key)
end

然后你可以在你的 Controller 方法中使用那个连接

def show
   #just use it straight off
   @api_connection.api_perform('APIEmailService', 'optIn', email, reason)
   # or send the connection as a parameter to some other class
   ApiService.do_stuff(@api_connection)
end

答案 3

最简单的解决方案可能就是在需要时创建 api 连接

class User < ActiveRecord::Base
  def api_connection 
    # added caching of the the connection in object
    # doing this makes taking a block a little pointless but making methods take blocks
    # makes the scope of incoming variables more explicit and looks better imho
    # might be just as good to not keep @conn as an instance variable
    @conn = ApiConnection.new(url, key) unless @conn 
    if block_given?
      yield(@conn)
    else
      @conn
    end
  end
end

这样你就可以很容易地忘记连接的创建,并有一个新的方便。这可能会有性能损失,但我怀疑它们微不足道,除非有额外的登录请求

@user.api_connection do { |conn| conn.optin_via_email(email,user) }

关于ruby-on-rails - 在 Rails 的 API 包装器中使用 Thread.current 的替代方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7509883/

相关文章:

ruby-on-rails - 将数据从 CSV 导入到 PSQL

ruby-on-rails - Rails - 以天、月和年为单位的持续时间

php - REST API 响应中的校验和

php - Laravel 5.3 在 API 中过滤搜索数据

api - ReSharper API...呃...它在哪里?

ruby-on-rails - Rails 发条不运行代码

ruby-on-rails - Rails + 甲骨文 : worth hassle?

ruby-on-rails - 如何使用 Turbolinks 5 运行 $(document).on 'ready'?

ruby-on-rails - Rails 4-Pundit-索引范围的策略

ruby - 如何在使用门卫的 oauth 提供商上为我的用户预授权客户端应用程序?