如何从 Rails 中访问 Rack 环境?

Posted

技术标签:

【中文标题】如何从 Rails 中访问 Rack 环境?【英文标题】:How do I access the Rack environment from within Rails? 【发布时间】:2010-10-25 07:37:02 【问题描述】:

我有一个如下所示的 Rack 应用程序:

class Foo
  def initialize(app)
    @app = app
  end
  def call(env)
    env["hello"] = "world"
    @app.call(env)
  end
end

将我的 Rack 应用程序连接到 Rails 后,如何从 Rails 中访问env["hello"]

更新:感谢 Gaius 的回答。 Rack and Rails 允许您在请求期间或会话期间存储内容:

# in middleware
def call(env)
  Rack::Request.new(env)["foo"] = "bar"  # sticks around for one request

  env["rack.session"] ||= 
  env["rack.session"]["hello"] = "world" # sticks around for duration of session
end

# in Rails
def index
  if params["foo"] == "bar"
    ...
  end
  if session["hello"] == "world"
    ...
  end
end

【问题讨论】:

您确定Rack::Request.new(env)["foo"] = "bar" 改变了Rails 的任何内容吗?从源代码中我可以看到,应该改变的只是Rack::Request 对象本身的一个实例变量。甚至the docs for that method 说:“请注意,修改不会保留在环境中。如果您想破坏性地修改参数,请使用 update_param 或 delete_param。” 【参考方案1】:

我很确定您可以使用 Rack::Request 对象来传递请求范围变量:

# middleware:
def call(env)
  request = Rack::Request.new(env) # no matter how many times you do 'new' you always get the same object
  request[:foo] = 'bar'
  @app.call(env)
end

# Controller:
def index
  if params[:foo] == 'bar'
    ...
  end
end

或者,您可以直接获取“env”对象:

# middleware:
def call(env)
  env['foo'] = 'bar'
  @app.call(env)
end

# controller:
def index
  if request.env['foo'] == 'bar'
    ...
  end
end

【讨论】:

(我知道它已经过时了)我相信第一个陈述不再正确(Rails 4+)。 Rack::Request.new(env) 每次都返回新对象,因此您不能在其中保留任何内容。 sources【参考方案2】:

简答:在控制器中使用request.envenv

长答案:

根据Rails Guide on Rails controllers,ActionController 提供了一个request 方法,您可以使用该方法访问有关您的控制器正在响应的当前HTTP 请求的信息。

在进一步检查 ActionController::Base#request 的文档后,我们看到它“返回一个代表当前请求的 ActionDispatch::Request 实例。”

如果我们查看ActionDispatch::Request 的文档,我们会发现它继承自Rack::Request。啊哈!我们开始吧。

现在,如果您不熟悉 Rack::Request 的文档,它基本上是 Rack 环境的封装。因此,在大多数情况下,您应该能够按原样使用它。如果您确实想要原始环境哈希,您可以使用Rack::Request#env 获得它。所以在 Rails 控制器中,就是request.env

深入挖掘:

在进一步检查ActionController::Base 的实例方法后,我注意到那里没有太多可看的东西。特别是,我注意到 paramssession 变量似乎丢失了。所以,我上一级到ActionController::MetalActionController::Base 继承自。

ActionController::Metal 中,我发现了一个方法env,它没有关于它的作用的文档——但我可以猜到。事实证明我是对的。那个变量是being assigned to request.env

ActionController::Metal 还包含params 方法,根据the source,默认设置为request.parameters。事实证明,request.parameters 不是来自Rack::Request,而是来自ActionDispatch::Http::Parameters,它包含在ActionDispatch::Request 中。此方法与Rack::Request#params 方法非常相似,不同之处在于更改它会修改特定于 Rails 的 Rack 环境变量(因此更改将在 ActionDispatch::Request 的实例中保持不变)。

但是,我似乎仍然找不到 session 方法。事实证明,它根本不在文档中。在搜索了ActionController::Metal的源码后,终于在this line上找到了。没错,就是request.session的快捷方式。

总结一下:

在控制器中...

使用request.envenv 获取原始环境对象 使用params 读取机架查询字符串并从机架输入流中发布数据。 (例如Rack::Request#params) 在机架环境中使用session 访问rack.session 的值

在中间件中...

通过环境哈希以通常的方式访问环境属性 通过环境哈希上的 rack.session 属性访问 Rails 会话 通过Rack::Request#params读取参数 通过Rack::Request#update_paramRack::Request#delete_param 更新参数(如Rack::Request#params 的文档中所述) 使用 ActionDispatch::Http::Parameters#paramsActionDispatch::Request 以特定于 Rails 的方式更新参数

【讨论】:

有助于阅读更深入的部分,尤其是关于参数的部分 env 在 rails 5.0 ActionController github.com/rails/rails/commit/… 已弃用

以上是关于如何从 Rails 中访问 Rack 环境?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ruby​​-on-rails 应用程序上使用 Rack 发送 POST 模拟请求?

如何获取 Rails 应用程序的 CORS 标头以访问 aws s3 存储桶?

理解 Rack 应用及其中间件

Ruby on Rails:无法加载rack / handler /

无法从 react-admin 访问由 rails 开发的 API

Heroku、Rails 4 和 Rack::Cors