Rails + thin: Not possible to download large files - ruby-on-rails

I have a rails app where users can manage large files (currently up to 15 GB). They have also the possibility to download the stored files.
Everything works perfect for files < 510 MB. But for > 510 MB, the download stops after 522,256 KB (510 MB).
I think thin produces this issue. When I start my dev server using thin, I cannot download the complete file. When I start the dev server using webrick, everything works.
I used top to compare the RAM/CPU behavior, but both server, thin and webrick, behave the same way. In development, both server read the complete file into RAM and then send it to the user/client.
I tried to change some options of send_file like stream, or buffer_size. I also set length manually. But again, I was not able to download the complete file using thin.
I can reproduce this behavior using Firefox, Chrome, and curl.
The problem is that my productive rails app uses 4 thin servers behind an nginx proxy. Currently, I cannot use unicorn, or passenger.
In development, I use thin 1.6.3, rails 4.1.8, ruby 2.1.2.
def download
file_path = '/tmp/big_file.tar.gz' # 5 GB
send_file(file_path, buffer_size: 4096, stream: true)
end

If you are using send_file, it is ideal to use a front end proxy to pass off the responsibility of serving the file. You said you are using nginx in production, so:
In your production.rb file, uncomment config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'.
You will also have to change your nginx configuration to accommodate Rack::Sendfile. Its documentation is located here. The changes amount to adding:
proxy_set_header X-Sendfile-Type X-Accel-Redirect;
proxy_set_header X-Accel-Mapping /=/files/; # or something similar that doesn't interfere with your routes
to your existing location block and adding an additional location block that handles the X-Accel-Mapping that you added. That new location block might look like:
location ~ /files(.*) {
internal;
alias $1;
}
You will know it is working correctly when you ssh to your production server and curl -I the thin server (not nginx) and see a X-Accel-Redirect header. curl (no -I) directly to the thin server should not send the file contents.
You can see my recent struggle with nginx and send_file here.

Related

Ruby on Rails header for sending files In NGINX

My application runs on a Nginx and passenger server. Inside the production.rb I see a line says:
# Specifies the header that your server uses for sending files.
config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
How does it Specifies header to sending files? How does Rails sends file without having this turned on?
Is it a good practice so turn this on? Does it make my application to run faster?
The behavior is explained in the send_file documentation
You should use this option, it will make your application faster and it is good practice to do so.
If you don't use this option, the file will be read by the ruby process, sent to nginx and then to the client.

Do I need use Gzip in a Rails 4 project?

I'm running some audits in my open-source project, BTC-Stores, and some times Chrome shows me that I need "Enable Gzip compression".
Sometime ago I read "High Performance Websites", from Steve Souders, and I already know the basic concepts about how make your page load faster. My project is using Ruby 2.0.0 and Rails 4.
I want know, Rails 4 already have a "gzip like compression" or I need to activate it by some gem or config? If you can, please link some good articles about Rails 4 performance and how to optimize it.
Here's some more about using gzip with Rails!
From http://guides.rubyonrails.org/asset_pipeline.html:
4.1.2 GZip Compression
When files are precompiled, Sprockets also creates a gzipped (.gz)
version of your assets. Web servers are typically configured to use a
moderate compression ratio as a compromise, but since precompilation
happens once, Sprockets uses the maximum compression ratio, thus
reducing the size of the data transfer to the minimum. On the other
hand, web servers can be configured to serve compressed content
directly from disk, rather than deflating non-compressed files
themselves.
Nginx is able to do this automatically enabling gzip_static:
location ~ ^/(assets)/ { root /path/to/public; gzip_static on; #
to serve pre-gzipped version expires max; add_header Cache-Control
public; } This directive is available if the core module that provides
this feature was compiled with the web server. Ubuntu packages, even
nginx-light have the module compiled. Otherwise, you may need to
perform a manual compilation:
./configure --with-http_gzip_static_module If you're compiling nginx
with Phusion Passenger you'll need to pass that option when prompted.
A robust configuration for Apache is possible but tricky; please
Google around. (Or help update this Guide if you have a good example
configuration for Apache.)
Also, the following may be of interest!
http://api.rubyonrails.org/classes/ActiveSupport/Gzip.html
Hope this helps!
Yes you need to use Gzip in a Rails 4 or any other project. You do that in the production server and not on your local.
Duplicated question: Compressing rails assets and nginx gzip (with nginx server)

Rails 3, apache & passenger, send_file sends zero byte files

I'm struggling with send_file with rails 3.0.9 running ruby 1.9, passenger 3.0.8 on apache on ubuntu lucid
The xsendfile module is installed and loaded into apache
root~# a2enmod xsendfile
Module xsendfile already enabled
Its symlinked correctly in mods-enabled
lrwxrwxrwx 1 root root 32 Aug 8 11:20 xsendfile.load -> ../mods-available/xsendfile.load
config.action_dispatch.x_sendfile_header = "X-Sendfile" is set in my production.rb
using send_file results in zero byte files being sent to the browser
filepath = Rails.root.join('export',"#{filename}.csv")
if File.exists?(filepath)
send_file filepath, :type => 'text/csv'
end
I believe the previous answer isn't the right way to go because, as far as I can tell, Apache isn't handling the downloads at all when this solution is applied, instead the rails process is. That's why the nginx directive, which shouldn't work, appears to. You get the same result by commenting out the config directive.
Another drawback (aside from tying up a rails process for too long) is that when the data streaming is handled by the rails process the response doesn't seem to send the content length header. So a user doesn't know how large the file they're downloading is, nor how long it will take (a usability problem).
I was able to get it to work by ensuring that mod_sendfile was properly included and loaded in my apache config, like so (this will be dependent on your apache install, etc.):
LoadModule xsendfile_module /usr/lib64/httpd/modules/mod_xsendfile.so
...
# enable mod_x_sendfile for offloading zip file downloads from rails
XSendFile on
XSendFilePath /

Compressing a JSON response from a Rails app

We have an app that queries for locations for a customer. We're getting to the point where some customers could have upwards of 10,000 locations. The JSON response for this can get quite large, over 1mb sometimes.
I'm wondering first off the best way to compress this. We have apache in front of a Rails app running in trinidad with JRuby. Can I just set mod_deflate to always compress any responses that are application/json? How might I go about doing this?
Next, what is the browser support for gzip'd json? When I gzip a sample response of 200k it goes down to 30k. That's a significant savings. We're really like to be able to minimize the size of that response without having to minimize the number of locations returned.
In general, for newer versions of Rails, you can do it by adding
use Rack::Deflater
before the "run" line in the config.ru file. This will work perfectly with browsers/clients that support gzip. We use it in production on major websites.
Note for JRuby users: This assumes that your Rails app is launched through Rack, which it often isn't for JRuby. You need a recent version of JRuby-Rack and configure it in Warbler to run in 'rack' mode instead of 'rails' mode.
If the browser supports gzip'd/deflated data, then JSON will go through it just fine. AJAX data is just a regular HTTP request that was done on behalf of a script, rather than a human. At the HTTP level, there's absolutely zero difference between transferring some HTML or a JSON string - it's just data.
For Googlers... [I'm running Apache 2.2.16 and don't care about IE6]
JSON Responses with Content-Encoding = gzip didn't happen until I edit mod_deflate.conf to include this:
AddOutputFilterByType DEFLATE application/json
You can check server response headers with Firefox / Firebug / Net tab
First, make sure you have apache's mod_deflate installed by running this command.
a2enmod deflate
If this command installed it, restart apache. If not, you're good for now.
service apache2 restart
In apache2.conf usually located at /etc/apache2 append this line to the end of the file. This will include a file that we'll create in a little.
Include mod_deflate.conf
Next, we'll edit the mod_deflate.conf with our options:
SetOutputFilter DEFLATE
SetEnvIfNoCase Request_URI \
\.(?:exe|t?gz|zip|bz2|sit|rar)$ \
no-gzip dont-vary
SetEnvIfNoCase Request_URI \.pdf$ no-gzip dont-vary
#Skip browsers with known problems
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
The first block of code disables gzipping exes, gzs, pdfs.. etc
The second block of code skips deflation from browsers that don't support it.
Finally, restart apache again
service apache2 restart
The settings were copied from the link below:
http://www.howtoforge.com/apache2_mod_deflate

Rails sends 0 byte files using send_file

I can't get send_file(Model.attachment.path) to work.
It doesn't fail, instead, it sends a 0 byte size file to the client, the file names are correct though.
This problem started happening after I did a big migration from Rails 2.3.8 to 3.
There were a lot of other things that took place in this migration and I will try my best to detail all of them.
Distrubution change/Server Change. Rackspace RHEL5 to Linode Ubuntu 10.04LTS
Ruby version change, 1.8.6 -> 1.9.2
Rails version change, 2.3.8 -> 3.0.0
httpd platform change, apache2 -> nginx (However I tried on apache2 as well and it did not work).
I moved the attachments via ftp as they were not part of my git repositories so they were published via cap deploy, instead manual ftp remote(RHEL5) to local(Win7) then local(Win7) to remote(Ubuntu10).
I do know that FTPing does not retain the file permissions through the transfers, so what I've also done is mimicked the chmods that were seen on my previous servers so they are almost identical. (users/groups are different, set to root:root instead of olduser:olduser).
A snippet of the request to download a attachment from my production log.
Started GET "/attachments/replies/1410?1277105698" for 218.102.140.205 at 2010-09-16 09:44:31 +0000
Processing by AttachmentsController#replies as HTML
Parameters: {"1277105698"=>nil, "id"=>"1410"}
Sent file /srv/app/releases/20100916094249/attachments/replies/UE0003-Requisition_For_Compensation_Leave.doc (0.2ms)
Completed 200 OK in 78ms
Everything's okay. Let me also rule out local issues, I've tried downloading via Chrome on both Win7 and Ubuntu (on Vbox).
Let me also assure you that the path is indeed correct.
root#li162-41:/srv/app/current# tail /srv/app/releases/20100916094249/attachments/replies/UE0003-Requisition_For_Compensation_Leave.doc
#
#
%17nw
HQ��+1ae����
%33333333333(��QR���HX�"%%��#9
��#�p4��#P#��Unknown������������G��z �Times New Roman5��Symbol3&�
�z �Arial5&�
So to sum up the question, how do I get send_file to actually send files instead of fake 0 byte junk.
send_file has :x_sendfile param which defaults to true in Rails 3.
This feature offloads streaming download to front server - Apache (with mod_xsendfile) or lighttpd, by returning empty response with X-Sendfile header with path.
Nginx uses X-Accel-Redirect header for same functionality but you have to
configure Rails properly in proper environment file:
config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
Rails 3 update: this line already exists in production.rb, just uncomment it.
Add sendfile on; to your nginx config to utilize header sent by Rails.
Remember the absolute path must be used and nginx must have read access to file.
Another way for aliased files:
For better security I use aliases in nginx instead of absolute paths,
however send_file method checks existence of file which fails with alias.
Thus I changed my action to:
head(
'X-Accel-Redirect'=> file_item.location,
'Content-Type' => file_item.content_type,
'Content-Disposition' => "attachment; filename=\"#{file_item.name}\"");
render :nothing => true;
In Rails 3, just uncomment the line config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' in production.rb inside environments folder.
Yes, I had the same problem with X-sendfile being enabled by default in Rails 3 too.
If you have large volume of "send_file" calls,
you can just comment-out following line in config/environments/production.rb:
#config.action_dispatch.x_sendfile_header = "X-Sendfile"
Then send_file method started working perfectly.
Because I can't install x-sendfile extension to Apache, I just searched a little and found this.
I hope it helps.
I've had similar issues with send_file() in the past, using send_data() instead saved me back then (e.g. send_data File.read(filename), :disposition => 'inline', :type => "some/mimetype")
On Rails 4, I realize my problem is that I deleted the temporary file which I've generated to send to user.
If I didn't delete the file, send_file works. I've not tested on thin but it works great on Passenger 5 as stand-alone server.

Resources