【问题标题】:Reloading rails middleware without restarting the server in development重新加载rails中间件而不重新启动开发中的服务器
【发布时间】:2014-01-19 09:15:40
【问题描述】:

我有一个带有位于 lib/some/middleware.rb 的中间件的 rails 4 应用程序,该应用程序当前通过如下初始化程序注入堆栈:

MyApp::Application.configure.do |config|
    config.middleware.use 'Some::Middleware'
end

不幸的是,每当我更改某些内容时,我都需要重新启动服务器。如何在开发模式下对每个请求重新加载它?我已经看到类似的问题,关于通过自动加载或在 to_prepare 块中包装代码来重新加载 lib 代码,但我不确定如何在这种情况下应用。

谢谢, - FJM

更新 #1

如果我尝试删除中间件,然后在 to_prepare 块中重新添加它,我会收到错误“无法修改冻结数组”。

【问题讨论】:

  • 将您的中间件放入app/middlewares 并重试。在将字符串添加到中间件堆栈而不是类本身时使用字符串也很重要,否则它不会被重新加载。
  • @phoet 我尝试了你的建议,但没有成功。

标签: ruby-on-rails ruby-on-rails-4 rack rack-middleware


【解决方案1】:

我认为在某些时候 Rails 足够聪明地在运行时替换中间件代码,但我可能错了。

这是我想出的,绕过 Ruby 类加载疯狂并利用 Rails 类重新加载。

将中间件添加到堆栈中:

# config/environments/development.rb
[...]
config.middleware.use "SomeMiddleware", "some_additional_paramter"

利用自动重新加载,但要确保正在运行的 rails 实例和已经初始化的中间件对象不断“忘记”实际执行的代码:

# app/middlewares/some_middleware.rb
class SomeMiddleware
  def initialize(*args)
    @args = args
  end

  def call(env)
    "#{self.class}::Logic".constantize.new(*@args).call(env)
  end

  class Logic
    def initialize(app, additional)
      @app        = app
      @additional = additional
    end

    def call(env)
      [magic]
      @app.call(env)
    end
  end
end

应该通过 rails 在每个请求上自动重新加载来获取逻辑的变化。

我认为这实际上可能会成为一个有用的宝石!

【讨论】:

  • 太棒了。导致它重新加载的常量化是什么?
  • 我认为 rails 正在正确地重新加载代码,但由于中间件是在初始化期间加载的,因此您必须确保在每次请求时重新评估代码。如果你坚持调用.new 的真实类会出现问题,因为重新加载,现在对象树中有两个Logic 类。这是不好的。所以这就是为什么你必须将逻辑传递给不同的类并且不要保留对类常量的引用。
  • @FrankJosephMattia 我创建了一个插件 gem 的 alpha 版本,它应该负责在开发中重新加载:github.com/phoet/zazicki
  • 看起来不错。我最终将您的原始建议包装成一个 mixin,我包含在我的中间件类中。不过我得试一试。
【解决方案2】:

基于@phoet 的回答,我们实际上可以使用这种延迟加载来包装任何中间件,我发现它更有用:

class ReloadableMiddleware
  def initialize(app, middleware_module_name, *middleware_args)
    @app = app
    @name = middleware_module_name
    @args = middleware_args
  end

  def call(env)
    # Lazily initialize the middleware item and call it immediately
    @name.constantize.new(@app, *@args).call(env)
  end
end

然后它可以挂接到 Rails 配置中,任何其他中间件作为它的第一个参数,以字符串形式给出:

Rails.application.config.middleware.use ReloadableMiddleware, 'YourMiddleware'

或者 - 我将它打包成一个名为 reloadable_middleware 的 gem,可以这样使用:

Rails.application.config.middleware.use ReloadableMiddleware.wrap(YourMiddleware)

【讨论】:

    【解决方案3】:

    你不能简单地使用shotgun吗?如果我理解您的问题,您希望确保在您对代码所做的每次更改时重新加载环境。这就是霰弹枪的作用。

    【讨论】:

    • 我研究了霰弹枪,但它似乎会在每次请求时重新加载所有内容,这让我觉得对我的需求来说太过分了。我想以某种方式卸载并重新加载我的 Some::Middleware 类,就像我的模型/控制器一样。
    • 这在概念上太过分了。但在开发环境中,我发现它在实际实践中非常出色。自从我开始使用它以来,它从未明显影响性能或引入任何意外问题。但是是的,从美学的角度来看,它是丑陋的。
    【解决方案4】:

    在带有新的默认 Zeitwork 代码加载器的 Rails 6 中,这对我有用:

    # at the top of config/application.rb, after Bundler.require    
    
    # Load the middleware. It will later be hot-reloaded in config.to_prepare
    Dir["./app/middleware/*.rb"].each do |middleware|
      load middleware
    end
    

    在配置class Application的部分下方,在config.to_prepare中添加热重载:

    middleware = "#{Rails.root}/app/middleware"
    Rails.autoloaders.main.ignore(middleware)
    
    # Run before every request in development mode, or before the first request in production
    config.to_prepare do
      Dir.glob("#{middleware}/*.rb").each do |middleware|
        load middleware
      end
    end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-10
      • 1970-01-01
      • 1970-01-01
      • 2012-06-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多