问题是 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,则在流式传输时也必须禁用其中间件。