在控制器操作之间共享 Net::IMAP 连接

Share a Net::IMAP connection between controller actions

我只有一个控制器和其中的一些操作来处理与 IMAP 相关的不同功能。所以我的问题是我不想为每个动作创建一个单独的连接。例如在一个动作中我可以做类似的事情(它不是实际的代码):

def index
 @imap = Net::IMAP.new(server, 993, true)
 @imap.login(user, password)
 @imap.select("INBOX")
end

再次在同一个控制器内的另一个操作中,如果我需要做一些与 IMAP 相关的事情,那么我将不得不再次创建 @imap 变量。

我是第一次使用 IMAP,所以根据我的理解,每个操作中的 new 方法都会创建另一个到服务器的连接,我听说 google 有连接限制 (15) IMAP 连接数。

我无法序列化此连接对象或将其存储在 Redis 或 Memcached 等任何其他服务中或对其进行缓存,那么我如何才能一次创建此连接并在所有其他操作中使用它,至少在同一个控制器内的操作如果可能的?如果不可能,那么还有其他解决方案来处理这个问题吗?

当然,我可以从邮箱中缓存我需要的数据,但这没什么用,因为还有一些不需要数据的其他操作,需要在邮箱中进行一些操作比如删除邮件,所以需要连接实例。

你如何创建一个包装你的服务对象(单例)Net::IMAP。你可以把它贴在 app/services/imap_service.rb 或类似的地方。举个例子:

require 'singleton' # This is part of the standard library
require 'connection_pool' # https://github.com/mperham/connection_pool

class IMAPService
  include Singleton

  def initialize
    @imap = ConnectionPool.new(size: 15) { Net::IMAP.new(server, 993, true) }
  end

  def inbox(user, password)
    @imap.with do |conn|
      conn.login(user, password)
      conn.select("INBOX")
    end
  end
end

您可以像 IMAPService.instance 一样访问此单例,例如IMAPService.instance.inbox(user, password)。我根据我们的讨论添加了 connect_pool gem 以确保这是线程安全的。 IMAPService 上没有 attr_reader :imap。但是,如果您不想在此处包含所有必需的方法,您可以添加一个以便可以在代码中直接访问连接池(尽管我建议尽可能使用服务对象)。然后你可以做IMAPService.instance.imap.with { |conn| conn.login(user, password) },不需要依赖IMAPService中的方法。

值得注意的是,您不必使用 Singleton mixin。 Implementing "the lovely" Singleton 上有一篇非常好的文章,它将向您展示这两种方法。

如果您希望连接在请求之间保持打开状态,则不能将其作为实例变量存储在控制器中,因为每个请求都有自己的控制器实例。

存储连接的一种方法是使用单例。

这是一个例子:

class ImapService

  attr_accessor :imap

  def initialize
    @imap = Net::IMAP.new("imap.gmail.com", 993, true)
    @imap.login("username@gmail.com", "password")
    @imap.select("INBOX")
  end

  @@instance = ImapService.new
  private_class_method :new

  def self.instance
    return @@instance
  end
end

这将在您第一次访问时打开连接,如果您再次访问它,它将使用旧连接。

您可以在应用程序的任何位置使用 ImapService.instance.imap 访问 imap 变量。