【问题标题】:Share a Net::IMAP connection between controller actions在控制器操作之间共享 Net::IMAP 连接
【发布时间】:2017-12-19 12:21:32
【问题描述】:

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

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

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

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

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

当然我可以从邮箱缓存我需要的数据,但这无济于事,因为还有一些其他操作不需要数据,它需要在邮箱中执行一些操作,例如删除邮件,因此需要连接实例。

【问题讨论】:

  • 是否要存储请求之间的连接?或者您只是想通过控制器中的任何操作打开新连接?

标签: ruby-on-rails ruby imap


【解决方案1】:

您如何创建一个包装您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 上有一篇非常好的文章,它将向您展示两种方法。

【讨论】:

  • 这看起来不错,所以我会尝试一下,但我仍然会等待任何其他答案。如果我没有得到更好的答案,我会接受你的。
  • 不,这只是一种习惯——我把它去掉了。我还更新了我的评论,建议首先执行服务对象而不是应用程序控制器对象,因为它保证保持其他答案指出的状态。我们也有两种不同风格的单例写作(在我链接的文章中介绍过),但我更喜欢我的,因为在快速阅读时更容易理解。不会有性能问题,如果是的话,您可以手动进行(虽然不会)。但是,我通常建议不要优化,除非它是一个瓶颈。
  • 使用像 Puma 这样的多线程网络服务器,如果在上一个完成之前启动 IMAPService 上的新请求,单例可能会导致一些意外行为。所有单件都是共享资源,可以非常小心地使用 - 很可能您不想向用户 B 显示用户 A 的收件箱。这里的另一个选项是使用类似 connection_pool 的东西,它在启动时创建与邮件服务器的很少连接,并且每个来自池的连接可以独占使用。
  • @Semjon 会在线程内执行所有操作吗?例如。 Thread.new { @imap.login(u, p); @imap.select('INBOX') }
  • 对不起,我上面的解释不好,我的意思是另一回事 - 如果你在多线程模式下使用 Puma,IMAPService 的单例对象可以同时由两个(或更多)不同您的 Web 应用程序的用户。在这种情况下,IMAPService 可以授权用户 A 并且用户 B 要求获取电子邮件。 IMAPService 将返回用户 A 的电子邮件并将其显示给用户 B。我不想提供代码 sn-p 因为它在很大程度上取决于您的需求。只是想知道使用单例。请看一下抽象实现github.com/mperham/connection_pool
【解决方案2】:

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

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

这是一个例子:

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 变量。

【讨论】:

  • 您好,感谢您的回答。我有一个问题,你能说它是线程安全的吗?并以 puma 的多线程模式将用户的 imap 连接公开给另一个用户,因为默认情况下会共享该类?也请在另一个答案上阅读 Semjon 的 cmets。
  • 此解决方案在多线程环境中也可能出现一些意外行为。在我的示例中,它将共享相同的连接,并且与多个用户共享,这将是一个问题,我将使用更好的解决方案更新我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-28
  • 1970-01-01
  • 2015-12-15
  • 2014-03-22
相关资源
最近更新 更多