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

如何让Ruby Net::SFTP非阻塞,实现远程文件流式下载?

解决Net::SFTP流式传输大文件,让浏览器立即响应下载

我太懂你这个痛点了——1GB的文件要等全下载完才让浏览器动,用户肯定以为卡了!其实你之前用的download/download!方法都是全量下载到本地/内存后再输出,自然会有延迟。Net::SFTP本身支持流式读取,我们换个方式直接逐块读取远程文件,边读边给浏览器发数据就行。

核心思路

通过Net::SFTP的open方法获取远程文件的句柄,然后循环读取固定大小的文件块,每读一块就立即输出到HTTP响应流并强制刷新,让浏览器实时接收数据,不用等整个文件下载完成。

具体实现代码(以Rails为例)

def stream_large_sftp_file
  # 先设置HTTP响应头,告诉浏览器这是下载文件,同时禁用缓存避免延迟
  response.headers['Content-Type'] = 'application/zip'
  response.headers['Content-Disposition'] = 'attachment; filename="test.zip"'
  response.headers['Cache-Control'] = 'no-cache, no-store'
  response.headers['X-Accel-Buffering'] = 'no' # 禁用Nginx等反向代理的缓存,确保数据实时传输

  # 建立SFTP连接(建议把连接逻辑抽成工具类,避免重复代码)
  sftp = Net::SFTP.start('你的远程主机IP', '用户名', password: '你的密码')
  remote_file_path = '/远程服务器上的路径/test.zip'
  file_total_size = sftp.lstat(remote_file_path).size

  # 打开远程文件句柄,开始流式读取
  sftp.open(remote_file_path, 'r') do |remote_file|
    block_size = 8192 # 每次读8KB,可根据服务器性能调整(比如16KB/32KB)
    bytes_read = 0

    while bytes_read < file_total_size
      # 读取一块数据
      chunk = remote_file.read(block_size)
      break unless chunk # 处理文件读取结束的情况

      # 输出到响应流并强制刷新,确保数据立刻发往浏览器
      response.stream.write(chunk)
      response.stream.flush

      bytes_read += chunk.size
    end
  end
ensure
  # 确保资源被正确释放,避免内存泄漏
  sftp.close if sftp
  response.stream.close
end

关键细节说明

  • X-Accel-Buffering: no:如果你的应用前面有Nginx等反向代理,一定要加这个头,不然代理会缓存整个文件后再发给浏览器,还是会有延迟。
  • 块大小调整:块太小会增加IO次数,块太大可能占用过多内存,8KB-32KB是比较均衡的选择。
  • 异常处理:可以在代码里添加rescue块捕获Net::SFTP::Exception等异常,给用户返回友好的错误提示(比如“下载失败,请稍后重试”)。
  • Web服务器支持:确保你的Ruby Web服务器(比如Puma、Unicorn)支持流式响应,大部分主流服务器默认都支持,但如果是老旧版本可能需要调整配置。

Sinatra环境下的适配示例

如果用的是Sinatra,逻辑类似,只是响应流的写法略有不同:

get '/download-test-zip' do
  content_type 'application/zip'
  headers['Content-Disposition'] = 'attachment; filename="test.zip"'
  headers['Cache-Control'] = 'no-cache, no-store'
  headers['X-Accel-Buffering'] = 'no'

  sftp = Net::SFTP.start('远程主机', '用户名', password: '密码')
  remote_path = '/path/to/test.zip'
  file_size = sftp.lstat(remote_path).size

  stream do |out|
    sftp.open(remote_path, 'r') do |file|
      block_size = 8192
      bytes_read = 0
      while bytes_read < file_size
        chunk = file.read(block_size)
        break unless chunk
        out.write(chunk)
        out.flush
        bytes_read += chunk.size
      end
    end
  ensure
    sftp.close if sftp
  end
end

这样修改后,浏览器应该会立即弹出下载提示,并且能看到实时的下载进度,不用再等几分钟啦!

内容的提问来源于stack exchange,提问作者gdonald

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 10:25:24