使用 Rack::Deflater 时,Rails 中的 HTTP 流式传输不起作用

Posted

技术标签:

【中文标题】使用 Rack::Deflater 时,Rails 中的 HTTP 流式传输不起作用【英文标题】:HTTP streaming in rails not working when using Rack::Deflater 【发布时间】:2011-12-20 15:09:33 【问题描述】:

我已经在 rails 3.1 中设置了 unicorn,并且 http 流可以正常工作,直到我启用 Rack::Deflater。 我尝试过使用和不使用 Rack::Chunked。在 curl 中我可以看到我的响应,而在 chrome 中我收到以下错误:ERR_INVALID_CHUNKED_ENCODING

结果在其他浏览器(firefox、safari)中以及在开发(osx)和生产(heroku)之间是相同的。

config.ru:

require ::File.expand_path('../config/environment',  __FILE__)
use Rack::Chunked
use Rack::Deflater
run Site::Application

独角兽.rb:

listen 3001, :tcp_nopush => false
worker_processes 1 # amount of unicorn workers to spin up
timeout 30         # restarts workers that hang for 30 seconds

控制器:

render "someview", :stream => true

感谢您的帮助。

【问题讨论】:

【参考方案1】:

问题在于 Rails ActionController::Streaming 直接渲染成 Chunked::Body。这意味着内容首先被 Rack::Deflater 中间件分块然后 gzip,而不是先 gzip 然后再分块。

根据HTTP/1.1 RFC 6.2.1,chunked 必须是最后一次对传输应用编码。

因为“分块”是唯一需要理解的传输编码 对于 HTTP/1.1 接收者,它在分隔消息方面起着至关重要的作用 在持久连接上。每当将传输编码应用于 请求中的有效负载正文,应用的最终传输编码必须是 “分块”。

我通过在初始化程序中猴子修补 ActionController::Streaming _process_options 和 _render_template 方法为我们修复了它,因此它不会将主体包装在 Chunked::Body 中,而是让 Rack::Chunked 中间件来代替。

module GzipStreaming
  def _process_options(options)
    stream = options[:stream]
    # delete the option to stop original implementation  
    options.delete(:stream)
    super
    if stream && env["HTTP_VERSION"] != "HTTP/1.0"
      # Same as org implmenation except don't set the transfer-encoding header
      # The Rack::Chunked middleware will handle it 
      headers["Cache-Control"] ||= "no-cache"
      headers.delete('Content-Length')
      options[:stream] = stream
    end
  end

  def _render_template(options)
    if options.delete(:stream)
      # Just render, don't wrap in a Chunked::Body, let
      # Rack::Chunked middleware handle it
      view_renderer.render_body(view_context, options)
    else
      super
    end
  end
end

module ActionController
  class Base
    include GzipStreaming
  end
end

并将您的 config.ru 保留为

require ::File.expand_path('../config/environment',  __FILE__)
use Rack::Chunked
use Rack::Deflater
run Roam7::Application

不是一个很好的解决方案,它可能会破坏其他一些检查/修改主体的中间件。如果有人有更好的解决方案,我很乐意听到。

如果你使用的是new relic,它的中间件也必须是disabled when streaming。

【讨论】:

以上是关于使用 Rack::Deflater 时,Rails 中的 HTTP 流式传输不起作用的主要内容,如果未能解决你的问题,请参考以下文章

在创建新的 Rails 应用程序时,如何告诉 Rails 使用 RSpec 而不是 test-unit?

在创建新应用程序时指定要使用的 rails 版本

在 Rails 6 中使用 activestorage 时,如何在重新显示表单时保留文件?

使用 juggernaut 作为 rails 插件时遇到问题

在 Rails 中使用短语 gem 时内容未更新

使用 AWS Elastic Beanstalk 部署 rails 应用程序时管理迁移