使用流式传输在sinatra中转发文件

时间:2014-01-11 09:13:28

标签: ruby stream streaming sinatra rack

我创建了一个ruby / sinatra网站,我需要让用户下载文件。  此文件不是本地托管的,它托管在远程API上。最终用户不得看到文件的真正来源。

get "/files/:elementKey/masterfile" do
  content_type "application/octet-stream"  
  loadMasterfile(params[:elementKey])
end

使用loadMasterfile:

http = Net::HTTP.new(plainURI,443)
 http.use_ssl = true; 
 http.start do |http|
   req = Net::HTTP::Get.new(resource, {"User-Agent" =>"API downloader"})
   req.basic_auth(user.keytechUserName, user.keytechPassword)
   response = http.request(req)     
   # return this as a file attachment
   attachment( response["X-Filename"])  #Use the sinatra helper to set this as filename
   response.body  << This lets sinatra download the file and then forward the whole content to the browser  
 end

此代码有效,但是:  该文件首先下载到ruby / sinatra,然后转发到浏览器。  用户必须等到下载开始 - 浏览器似乎冻结。

是否有解决方案从远程API开始下载并在一个流程中转发内容?

我没有发现任何相关内容或仅找到本地文件下载的解决方案,但我必须从远程API下载文件。

我也无法在本地或亚马逊AWS上缓存文件。

任何想法?

1 个答案:

答案 0 :(得分:1)

要以您的应用为代理的流媒体方式实现此目的,您需要在下载块时发送客户端块。这不是ruby / Net::HTTP的默认行为,但它是可能的。

来自ruby docs:

  

默认情况下,Net :: HTTP会将整个响应读入内存。如果您正在处理大型文件或希望实现进度条,则可以将主体直接流式传输到IO。

但可以通过read_body进行流式传输。

Net::HTTP Streaming Response Bodies

来自文档的示例用法:

uri = URI('http://example.com/large_file')

Net::HTTP.start(uri.host, uri.port) do |http|
  request = Net::HTTP::Get.new uri

  http.request request do |response|
    open 'large_file', 'w' do |io|
      response.read_body do |chunk|
        io.write chunk
      end
    end
  end
end

此文档中的示例将流数据写入文件,但您可以将其替换为对响应流的写入。结合Sinatra's streaming api,代码可能如下所示:

get "/files/:elementKey/masterfile" do
  content_type "application/octet-stream"

  stream do |out|
    loadMasterfile(params[:elementKey]) do |chunk|
      out << chunk
    end
  end
end


def loadMasterfile(resource, &block)
  http = Net::HTTP.new(plainURI, 443)
  http.use_ssl = true; 
  http.start do |http|
    req = Net::HTTP::Get.new(resource, {"User-Agent" =>"API downloader"})
    req.basic_auth(user.keytechUserName, user.keytechPassword)
    http.request(req) do |origin_repsonse|
      origin_repsonse.read_body(&block)
    end
  end
end

我不确定你是如何设置文件名的。您还希望在网络呼叫和流关闭中适当地处理错误。另请注意,像nginx这样的前端会影响流式响应的缓冲/分块。