Jon Williams
Jon Williams

Reputation: 77

Rails: Download file from another server (without reading file)

I have a rails application that has links to download a files from another server. I want to try and prevent the actual file url/locations from being "easily" detectable.

I tried using send_file but this only appears to work with local files?

I then tried with send_data - this only seems to work when first reading the file (e.g. file.open ...). I want to be able to send the file without reading it as some of the files are very large and therefore takes ages and has no user feedback etc...

Basically, I want the same behavior as if I was to simply link to/provide the direct file url: that the download opens and starts from the browser.

As I mentioned The reason I want to handle my downloads via a controller (using send_file/data methods) is that I do want the location of the files to be "easily" detectable.

I hope I have explained this well enough :)

Thanks for any advice

Cheers, Jon.

Upvotes: 0

Views: 2333

Answers (1)

Aaron Breckenridge
Aaron Breckenridge

Reputation: 1819

So you want to stream the data from a public URL to a client without reading it? You're going to have to read it somehow if you want to send it to a client. A redirect is the best solution here, but you don't like redirects because the public URL is ugly.

You could try streaming the response_body (which accepts enumerbles) from a Net::HTTP read_body (which produces an enumerable). That might keep your memory footprint down.

def DownloadController
  def show
    source = URI('http://www.example.com/some-big-pdf.pdf')

    # Tell the client to download the response with given filename
    headers['Content-Disposition'] = 'attachment; filename="some-big-pdf.pdf"'

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

      http.request(req) do |res|
        self.response_body = res.read_body
      end
    end
  end
end

Edit: Of course send_data can also read from buffers:

http.request(req) do |res|
  send_data res.read_body, filename: 'some-big-pdf.pdf', disposition: 'download'
end

However, be aware that this will tie up a worker process until complete. You should ensure that you have enough workers to handle simultaneous downloads. You may also want to look into doing this upstream within your webserver (NGINX?) rather than inside Rails.

See Net::HTTP#read_body and ActionController::Metal#response_body= for more details on these methods. See also https://coderwall.com/p/kad56a/streaming-large-data-responses-with-rails

Upvotes: 1

Related Questions