Rails send_data streaming PDF as bytes, unless route accessed directly - ruby-on-rails

I've got a simple Rails app that downloads a PDF from a (private) S3 bucket and serves it to the browser.
# app/controllers/file_controller.rb
class FileController < ApplicationController
def send_pdf
s3_file = Aws::S3::Resource.new(
region: "us-east-1",
access_key_id: S3_ACCESS_KEY_ID,
secret_access_key: S3_SECRET_ACCESS_KEY
).bucket('bucket-name').objects({prefix: "file_name"}).first.get.body.string
send_data s3_file, filename: "FileName.pdf", type: 'application/pdf', disposition: 'inline'
end
end
# config/routes.rb
Rails.application.routes.draw do
get 'file', to: 'file#send_pdf', defaults: { format: 'pdf' }
end
When accessing the route directly via URL, it displays the PDF fine.
When opening a link to the route in a new tab, it displays the PDF fine.
When opening a link in the same tab, the PDF data streams as text to the browser instead.
The same behavior occurs in Rails 4 and 5.
I'm probably missing something annoyingly minor here, but how can I get the open in the same tab behavior to properly display the PDF instead of streaming bytes as text?
Update 1:
Chrome gives a Failed to load PDF document error when send_pdf is modified to use send_file instead of send_data. (This error happens regardless of link click or direct route request.)
def send_pdf
# S3 download
temp_file = "#{Rails.root}/tmp/file.pdf"
File.open(temp_file,"wb") do |f|
f.write(s3_file)
f.close
end
send_file temp_file, filename: "FileName.pdf", type: 'application/pdf', disposition: 'inline'
File.delete(temp_file)
end

Have you tried send_file instead of send_data?
http://apidock.com/rails/v4.2.1/ActionController/DataStreaming/send_file

Related

Streaming Large (7 GB) S3 gz file in Rails 3.x to the Browser

I'm trying to stream a large Amazon S3 file to the browser using the Rails send_data method, however because the file is so large, the server runs out of memory and cannot complete the request.
The code looks something like this:
def download
s3_obj.read
end
def download_file
send_data(file.download, :filename => filename, :type => 'application/gzip', :disposition => 'attachment')
end
Is there a way to stream the chunks of the file with send_data so that it's a single file in the browser? the way I understand it is that send_data has to load the entire file into memory, then send all of that at once.
You should use send_file instead of send_data as it allow you to set the buffer and more option.
More information here.
UPDATE
If you want to download from S3, you can do this:
def download
data = open("S3_OBJECT_URL")
send_file data.read, filename: filename, type: "application/gzip", disposition: 'attachment', stream: 'true', buffer_size: '4096'
end
or
redirect_to s3_object.file.url

OpenURI::HTTPError in ArticlesController#download 403 Forbidden - S3 - Rails

I'm simply trying to allow my app to download files that are stored on s3. This seems straight forward but I keep getting this error OpenURI::HTTPError in ArticlesController#download
403 Forbidden
Here is my controller:
def download
article = Article.find_by(id: params[:id])
data = File.read(open("https://s3.us-east-2.amazonaws.com/theranostics-bucket/uploads/DMS-OctoberSocialMedia.pdf"))
send_data data, filename: "file.pdf", type: "application/pdf", disposition: 'inline', stream: 'true', buffer_size: '4096'
end
It fails on the File.read line. What am I missing? I'm using Carrierwave to upload the files which is working just fine. It's only when I try to download I'm having issues.

Firefox does not Show PDF File Extension on Download

I'm using rails to send a pdf back to the client and in Firefox the file extension is not showing:
My rails code looks like this:
send_data(
pdf,
:type => "application/pdf",
:disposition => "attachment; filename=transcript_#{Time.zone.now.strftime('%m-%d-%Y %H:%M')}.pdf",
# :filename => "transcript_#{Time.zone.now.strftime('%m-%d-%Y %H:%M')}.pdf"
)
I've been trying to set the file name with a combination of the :filename and :disposition key to display the correct filename in the browser. The :filename key doesn't seem to work in Firefox and the :disposition key gives me the picture above.
What do I need to change to get the pdf file extension to be shown in Firefox?
The space (inside of the time format) is throwing off the file name. You need to surround the file name in quotes.
Try this:
:disposition => "attachment; filename=\"transcript_#{Time.zone.now.strftime('%m-%d-%Y %H:%M')}.pdf\"",
^^ ^^
This behavior is explained here: http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download.
The key point being that
[The space] creates an ambiguity when parsing the header for the filename when the browser has to consider the possibility of internationalized filenames. As Internet Explorer does not have to worry about this, it will parse the filename until the end of the line. Mozilla will not.
this will definitely work
send_data pdf.render, filename: 'transcript_#{Time.zone.now.strftime('%m-%d-%Y %H:%M')}.pdf',
type: 'application/pdf',
disposition: "attachment"

Rails 3 Wicked PDF - include Paperclip S3 pdf files

I have a Rails 3 app that uses these gems:
gem 'paperclip'
gem 'wicked_pdf'
gem 'combine_pdf'
I'm using wicked_pdf to open a pdf for a costproject. The costproject has an HTML page called viewproject.pdf.erb.
I'm trying to combine the wicked pdf with the costproject attachments into a single pdf.
This is my controller code:
def viewproject
#costproject = Costproject.find(params[:costproject_id])
respond_to do |format|
format.html
format.pdf do
pdf = CombinePDF.new
pdf2 = render_to_string pdf: "Costproject.pdf", template: "costprojects/viewproject", encoding: "UTF-8"
pdf << CombinePDF.new(pdf2)
#costproject.attachments.each do |attachment|
pdf << CombinePDF.new(attachment.attach.path)
end
send_data pdf.to_pdf, :disposition => 'inline', :type => "application/pdf"
end
end
end
The line pdf << CombinePDF.new(pdf2) is giving me:
string contains null byte
If I look at pdf2, it starts like this - so it looks like a pdf:
>> pdf2
=> "%PDF-1.4\n1 0 obj\n<<\n/Title (\xFE\xFF)\n/Producer (wkhtmltopdf)\n/CreationDate (D:20150405202628)\n>>\nendobj\n4 0 obj\n<<\n/Type /ExtGState\n/SA true\n/SM 0.02\n/ca 1.0\n/CA 1.0\n/AIS false\n/SMask /None>>\nendobj\n5 0 obj\n[/Pattern /DeviceRGB]\nendobj\n8 0 obj\n<<\n/Type /XObject\n/Subtype /Image\n/Width 71\n/Height 75\n/BitsPerComponent 8\n/ColorSpace /DeviceRGB\n/Length 9 0 R\n/Filter
I also tried pdf << CombinePDF.new(pdf2.to_pdf)
Thanks for the help!
UPDATE1
As a test, to see if pdf2 is working, I did this successfully:
def viewproject
#costproject = Costproject.find(params[:costproject_id])
respond_to do |format|
format.html
format.pdf do
pdf2 = render_to_string pdf: "Costproject.pdf", template: "costprojects/viewproject", encoding: "UTF-8"
send_data pdf2, :disposition => 'inline', :type => "application/pdf"
end
end
end
UPDATE2
Myst was correct about using parse. Thanks!
I am now using this line in the controller code:
pdf << CombinePDF.new(attachment.attach.url)
I get this error:
No such file or directory - http://s3.amazonaws.com/ ...
But, if I copy the http address and paste into the browser the pdf shows up.
I am editing this answer to reflect the issue of remotely stored PDF files.
I should point out that without a persistent connection to the S3 storage and without using the S3 API, the following solution WILL effect performance*.
As I pointed out, the CombinePDF.new method is the same as the CombinePDF.load method. It accepts a file name and attempts to open the file. The CombinePDF.parse method will accept raw PDF data and parses it into a PDF Object.
In the following code I use Net::HTTP.get(URI.parse(url)) to get the raw PDF data.
I recommend replacing this solution with a S3 native solution, so that the whole application can share one or more persistent connections. This is a performance issue that may or may not be important for you.
require 'net/http'
def viewproject
#costproject = Costproject.find(params[:costproject_id])
respond_to do |format|
format.html
format.pdf do
pdf = CombinePDF.new
pdf2 = render_to_string pdf: "Costproject.pdf", template: "costprojects/viewproject", encoding: "UTF-8"
pdf << CombinePDF.parse(pdf2)
#costproject.attachments.each do |attachment|
pdf << CombinePDF.parse( Net::HTTP.get( URI.parse( attachment.attach.url ) ) )
end
send_data pdf.to_pdf, :disposition => 'inline', :type => "application/pdf"
end
end
end
* The performance hit is dependent on the amount of PDF attachments you have, on the number of users your app has, on network traffic, on your framework (single/multi-thread) and other factors.
A persistent connection should reduce the performance hit in a dramatic way, mainly due to the fact that establishing connections is an expensive action.

rails send_file and send_data sends out zero byte files

I'm trying to send a pdf back to the user but I'm having serious problem getting send_file and send_data to work. I created the pdf file as follows:
tmp = Tempfile.new('filled')
new_tmp_path = PDFPrint.fill_form_using_pdftk(template_path, tmp.path)
send_file (new_tmp_path, :filename => 'filled.pdf')
The browser prompts for a download, but the downloaded filled.pdf file has zero byte.
I have verified that new_tmp_path does contain a valid pdf (good, filled content)
I have tried this:
File.open(new_tmp_path, 'r') do |f|
send_data(f.read, :filename => "filled.pdf")
end
But this also gives me the same download->zero-byte problem, while the file on server (new_tmp_path) has perfect content.
Regards,
Try sending a simple file to see if it works
send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
Read this thread, I think it has everything you need.

Resources