Prawn And X Accel Redirect
30 May 2009Prawn is a pure Ruby PDF generation library. PrawnTo is a rails plugin that makes it dead easy to add PDF views to standard rails actions.
I needed to generate a significant number of PDF reports in an app I support and although Prawnto makes things simple I had a few requirements that meant it wasn’t a good fit:
- I wanted to share my reports across several projects
- I wanted to be able to generate PDFs in situations that weren’t responses to a HTTP request. For example, for attaching to an email or as part of a background job
- I didn’t want downloads of large PDFs to tie up one of my rails processes
With these in mind I came up with an alternative (and slightly more complex) technique for using Prawn in my app. The key features are storing the report definitions in their own directory (to allow sharing between projects using git sub modules) and using nginx’s X-Accel-Redirect feature to let nginx handle streaming the generated file to the client. You can read a little about this feature in the nginx wiki. Apache has a similar feature called X-Sendfile that is roughly comparable.
There are a number of steps involved in getting this technique running, so I will assume you are familiar with both Prawn, nginx and Rails. I have code snippets inline, or you can view a full sample application at github.
Start by editing your config/enviroment.rb file to add the following three lines. The first tells Rails about the new directory we’ll use to store our reports and the next 2 load prawn.
Next, edit or create config/initializers/mime_types.rb and add a line to register the PDF mime type:
Now, create a new class in app/reports/application_report.rb. This class will be a superclass for all the reports you will be generating.
Now, create a subclass of ApplicationReport in app/reports/product_report.rb. This will be your first real report. I’ve created a sample one here called ProductReport to display the basic information from my Product model. You will need to modify it to suit your application.
Next, we need to setup nginx to use the X-Accel-Redirect feature. Add the following four lines to your nginx config file, changing the path to point to your own rails app directory. For more information on this step, consult google. There’s plenty of relevant information around.
location /srv/rails/mypp/tmp/pdfs {
internal;
root /;
}
To simplify using X-Accel-Redirect, add two helper methods to your ApplicationController:
Finally, add the rendering and X-Accel-Redirect instructions to the relevant controller action. Since my sample report earlier was to display a single product, I’ve added it to the show action on my ProductsController.
The key lines for generating a report are:
I can call these lines any time I need to generate my ProductReport, whether it be in a controller action like here, a ActionMailer email, or a rake task. The output will be rendered to disk and I can retrieve the path by calling report.path.
The key line for using X-Accel-Redirect is:
I can call x_accel_pdf() in any controller action and the filename I give it will be streamed to the client by nginx instead of my rails app.
The setup to all this is a little involved, but once it’s in your app adding new reports is dead simple. A worthwhile tradeoff in my opinion.
The most likely problem you’re likely to run into is misconfiguring nginx. For hints on what might be wrong, make sure you check out the nginx error log.