Rails 7实现反向代理流式视频传输:解决先缓存再输出问题
问题描述
需求:使用Rails 7.0.4 + Ruby 3.2.2,让Rails服务器作为反向代理,分块流式传输远程视频数据给客户端。
现状:基于ActionController::Live实现了基础功能,但大文件场景下客户端等待时间过长——按官方文档,ActionController::Live应支持边读取远程数据边传输给客户端,但实际运行中是先读完全部数据再发送,调试日志显示块积累到一定数量后客户端才会收到数据。
当前代码:
class SocketController < ApplicationController include ActionController::Live def test uri = URI('https://somefile') i = 0 response.headers['Content-Type'] = 'video/mp4' Net::HTTP.start(uri.host, uri.port) do |http| request = Net::HTTP::Get.new uri http.request request do |remote_response| remote_response.read_body do |chunk| i += 1 puts i if i % 1000 == 0 response.stream.write chunk end end end ensure response.stream.close end end
解决方案
1. 强制刷新响应流
Rails的响应流默认会有缓冲,即使使用ActionController::Live,也需要显式调用flush把当前块立刻发送给客户端,避免积累缓冲:
remote_response.read_body do |chunk| i += 1 puts i if i % 1000 == 0 response.stream.write chunk response.stream.flush # 新增此行,强制发送当前块 end
2. 禁用Net::HTTP的本地缓冲
Net::HTTP默认会缓冲远程响应内容,导致先读完所有数据再处理。可以指定分块读取的大小,同时设置合理的读取超时:
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| http.read_timeout = 300 # 根据视频长度调整,防止超时 request = Net::HTTP::Get.new uri http.request request do |remote_response| # 指定按8KB分块读取,避免大缓冲 remote_response.read_body(nil, 8192) do |chunk| response.stream.write chunk response.stream.flush end end end
3. 添加流式传输专用响应头
补充头信息明确告知客户端和中间件这是分块传输,避免被缓存:
response.headers.merge!({ 'Content-Type' => 'video/mp4', 'Transfer-Encoding' => 'chunked', # 明确分块传输 'Cache-Control' => 'no-cache', # 禁止客户端/中间缓存 'X-Accel-Buffering' => 'no' # 针对Nginx等反向代理,禁用代理端缓冲 })
4. 排除干扰的Rails中间件
部分Rails中间件(如Rack::ETag、Rack::Cache)会缓冲响应,导致流式失效,可在控制器中跳过:
class SocketController < ApplicationController include ActionController::Live skip Rack::ETag skip Rack::Cache def test # ... 业务代码 end end
最终优化代码
class SocketController < ApplicationController include ActionController::Live skip Rack::ETag skip Rack::Cache def test uri = URI('https://somefile') response.headers.merge!({ 'Content-Type' => 'video/mp4', 'Transfer-Encoding' => 'chunked', 'Cache-Control' => 'no-cache', 'X-Accel-Buffering' => 'no' }) Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| http.read_timeout = 300 request = Net::HTTP::Get.new uri http.request request do |remote_response| remote_response.read_body(nil, 8192) do |chunk| response.stream.write chunk response.stream.flush end end end ensure response.stream.close end end
内容的提问来源于stack exchange,提问作者korncola
相关产品推荐
相关产品推荐

