Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sinatra streaming response with headers

Tags:

ruby

sinatra

I want to proxy remote files through a Sinatra application. This requires streaming an HTTP response with headers from a remote source back to the client, but I can't figure out how to set the headers of the response while using the streaming API inside the block provided by Net::HTTP#get_response.

For example, this will not set response headers:

get '/file' do
  stream do |out|
    uri = URI("http://manuals.info.apple.com/en/ipad_user_guide.pdf")
    Net::HTTP.get_response(uri) do |file|
      headers 'Content-Type' => file.header['Content-Type']

      file.read_body { |chunk| out << chunk }
    end
  end
end

And this results in the error: Net::HTTPOK#read_body called twice (IOError):

get '/file' do
  response = nil
  uri = URI("http://manuals.info.apple.com/en/ipad_user_guide.pdf")
  Net::HTTP.get_response(uri) do |file|
    headers 'Content-Type' => file.header['Content-Type']

    response = stream do |out|
      file.read_body { |chunk| out << chunk }
    end
  end
  response
end
like image 768
Alex Avatar asked Sep 11 '12 00:09

Alex


1 Answers

I could be wrong but after thinking a bit about this it appears to me that when setting the response headers from inside the stream helper block, those headers don't get applied into the response because the execution of that block is actually being deferred. So, probably, the block gets evaluated and the response headers get set before it begins executing.

A possible workaround for this is issuing a HEAD request before streaming back the contents of the file.

For example:

get '/file' do
  uri = URI('http://manuals.info.apple.com/en/ipad_user_guide.pdf')

  # get only header data
  head = Net::HTTP.start(uri.host, uri.port) do |http|
    http.head(uri.request_uri)
  end

  # set headers accordingly (all that apply)
  headers 'Content-Type' => head['Content-Type']

  # stream back the contents
  stream do |out|
    Net::HTTP.get_response(uri) do |f| 
      f.read_body { |ch| out << ch }
    end
  end
end

It may not be ideal for your use case because of the additional request but it should be small enough to not be much of a problem (delay) and it adds the benefit that your app may be able to react if that request fails before sending back any data.

Hope it helps.

like image 183
Estanislau Trepat Avatar answered Nov 12 '22 04:11

Estanislau Trepat