You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 00:06:02