我开发了一个应用程序,允许我们的客户创建他们自己的成员(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/