使用 Rack::Deflater 时,rails 中的 HTTP 流无法工作

发布于 2024-12-13 20:56:13 字数 687 浏览 5 评论 0原文

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

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

config.ru:unicorn.rb

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

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

感谢您的帮助。

I've setup unicorn in rails 3.1 and http streaming works until I enable Rack::Deflater.
I've tried both with and without use Rack::Chunked. In curl I can see my response while in chrome I get the following errror: ERR_INVALID_CHUNKED_ENCODING

The result is same in other browsers (firefox, safari) and between development (osx) and production (heroku).

config.ru:

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

unicorn.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

controller:

render "someview", :stream => true

Thanks for any help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

你对谁都笑 2024-12-20 20:56:13

问题是 Rails ActionController::Streaming 直接渲染到 Chunked::Body 中。这意味着 Rack::Deflater 中间件首先对内容进行分块,然后进行 gzip 压缩,而不是先进行 gzip 压缩,然后进行分块。

根据 HTTP/ 1.1 RFC 6.2.1,分块必须是最后应用于传输的编码。

因为“分块”是唯一需要理解的传输编码
对于 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,其中间件也必须是 流式传输时禁用

The problem is that Rails ActionController::Streaming renders directly into a Chunked::Body. This means the content is first chunked and then gzipped by the Rack::Deflater middleware, instead of gzipped and then chunked.

According to the HTTP/1.1 RFC 6.2.1, chunked must be last applied encoding to a transfer.

Since "chunked" is the only transfer-coding required to be understood
by HTTP/1.1 recipients, it plays a crucial role in delimiting messages
on a persistent connection. Whenever a transfer-coding is applied to a
payload body in a request, the final transfer-coding applied must be
"chunked".

I fixed it for us by monkey patching ActionController::Streaming _process_options and _render_template methods in an initializer so it does not wrap the body in a Chunked::Body, and lets the Rack::Chunked middleware do it instead.

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

And leave your config.ru as

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

Not a very nice solution, it will probably break some other middlewares that inspect/modify the body. If someone has a better solution I'd love to hear it.

If you are using new relic, its middleware must also be disabled when streaming.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文