I'm trying to understand HTTP Caching on Heroku. After reading their article, I'm curious about how the Cache-Control HTTP header is working.
In the sample application mentioned in the article the header is set in a controller action:
def image
#qrimage = QRImage.find_by_md5(params[:md5])
if #qrimage
headers['Cache-Control'] = 'public; max-age=2592000' # cache image for a month
send_data #qrimage.data, :filename => #qrimage.filename, :disposition => 'inline', :type => "image/png"
else
render :nothing => true, :status => 404
end
end
The code for #qrimage.data is like:
def data
qrcode = RQRCode::QRCode.new(self.message, :size => self.version, :level => self.ecc.to_sym)
qrcode.to_s
end
So to me it looks like the image is being generated on the server every time. And then cached by the browser for a month. So the only savings here is when the same visitor tries to view the same image.
If different visitors try to view the same image, it will still be generated and sent. Not really all that helpful if you ask me.
Is my understanding correct, or will the same image not be regenerate for each site visitor?
Heroku apps on the Aspen and Bamboo stacks are fronted by Varnish, an
HTTP accelerator. Varnish will cache output from your application
according to cues provided by standard HTTP headers to describe a
page’s cacheability. These headers are the same ones used by browsers,
so setting these headers correctly gives your app a double boost of
speed when on Heroku: at the Varnish layer, and again at the user’s
browser.
If you don't know, Varnish is a really fast cache that sits between your application and the internet, essentially. When headers say it's safe to cache, Varnish does so and responds to additional requests with the cached object without ever hitting your application.
Related
I have an application serving large (some hundreds of MB) video files and it is working perfectly on desktop browsers, using Rails + X-Sendfile on Apache.
An important requirement is that these videos must be private and visible only to logged users, so that's why i'm using Rails to serve them.
Everything works perfectly with other devices. I serve the videos in this way:
response.headers["X-Sendfile"]= filename
send_file filename, :disposition => :inline, :stream => true, :x_sendfile => true
But Ipad's requests need the byte range header. A solution (that does not work perfectly) is something like this:
size = File.size(filename)
bytes = Rack::Utils.byte_ranges(request.headers, size)[0]
offset = bytes.begin
length = bytes.end - bytes.begin
response.header["Accept-Ranges"]= "bytes"
response.header["Content-Range"] = "bytes #{bytes.begin}-#{bytes.end}/#{size}"
send_data IO.binread(filename,length, offset), :type => "video/mp4", :stream => true, :disposition => 'inline', :file_name => filename
With this solution I have problems with larger than 50mb videos, and, more important, I'm giving to rails a responsibility that it shouldn't have. It should be apache to handle the heavy load of the streaming, through the x-sendfile module. But I dont know how. the send_data method does not have the x-sendfile parameter, and solutions involving the send_file method are not working.
I found these 2 questions similar to mine but they are not working: rails media file stream accept byte range request through send_data or send_file method, What is the proper way to serve mp4 files through rails to an Ipad?
Any clue on what's going on? I'm struggling with this since weeks and i need to make it work. Other feasible solutions are welcome.
This could be completely unrelated as I am using nginx for the server, but if its only not working for ios, take a look at this blog post. There could be a similar solution for Apache.
In a sense, I had to add a proxy header that internally redirects to a folder path. How ever stupid this may seem, Apple has some sort of privacy issues that make this necessary for playback with audio and video files. Again not sure if this is the solution for you but for nginx this did wonders and cured my month long headache.
Have you enabled X-Sendfile config in your environment? Include the line config.action_dispatch.x_sendfile_header = "X-Sendfile" for Apache. The server will then use that header for sending files.
Check that the apache request body size is large enough too.
I need to have a system where a PDF is generated dynamically, asynchronously, and directly pushed to the browser, no disk storage is available. Getting resque to use prawn seems easy, its taking that data and sending it to the browser without storing it somewhere first, I can't find anything online. I thought about Faye, but can Faye handle pushing a PDF to the browser?
I've done this before in .net where i have an iframe's src attribute set to a service that returns a stream. The service aslo flips the http header to content-inline so that the browser doesn't try to download it but instead will try to render it inline. If you try to do this it wont work if the browser doesn't have a pdf plugin (must modern ones will but you always have that guy using IE6 yet) I don't know a lick of ruby but think you should be able to do something similar, or at least but an iframe on a page that targets a service written in something else.
u can use "PDFkit" for it.
the sample code is
def some_action
...
respond_to do |format|
format.html
format.pdf do
generate_pdf(file.html.haml, :css => [array of css file names that need to be added])
end
end
end
in application controller -
def generate_pdf(template, options={})
html = render_to_string(template, :layout => false)
kit = PDFKit.new(html, :orientation => 'Landscape')
kit.stylesheets << "#{Rails.root}/app/assets/stylesheets/default_css1.css"
kit.stylesheets << "#{Rails.root}/app/assets/stylesheets/default_css2.css"
# Add CSS
if options[:css]
options[:css].each do |css|
kit.stylesheets << "#{Rails.root}/app/assets/stylesheets/#{css}.css"
end
end
send_data(kit.to_pdf, :filename => 'latest.pdf', :type => 'application/pdf', :disposition => 'inline')
end
How big those PDFs are? Your database has BLOB columns (if you don't have storage, you are not using SQLite...) and you can store the resulting PDF in it.
Or you can store the resulting PDF in the Redis DB. Or save it in S3.
On the other end, the browser will be polling (with ajax) every now and then to know whether the PDF is complete, and as soon as it is ready it will download it and show it to the user.
Given that Heroku Cedar doesn't have http caching provided by Varnish I would like to use Rack::Cache.
I have been told that rails 3.1.1 have Rack::Cache active by default, I just need to make sure to have in the configuration:
config.action_controller.perform_caching = true
and I need to pick a cache store, for this experiment I'm using:
config.cache_store = :memory_store
In the action of the page I want to cache I've added the following lines:
response.header['Cache-Control'] = 'public, max-age=300'
response.header['Expires'] = CGI.rfc1123_date(Time.now + 300)
This code used to work fine with Varnish, the first request would return a 200 and the subsequent (for 5 mins) would return a 304.
This doesn't happen with Rails 3.1 and Heroku Cedar Stack.
I do get those headers in the response but subsequent requests returns 200 instead of 304.
What am I doing wrong? Thank you.
As you noted, the Cedar stack doesn't use Varnish. That means a web request will always hit the ruby server.
With that in mind, Rack::Cache will respect your headers and serve the cached content.
However, since the request is actually going past the http layer into the rails app, the response will always be 200 since the cache doesn't happen at the http layer anymore.
To confirm this is true, insert this in one of your cached actions:
<%= Time.now.to_i %>
Then, reload the page several times and you'll notice the timestamp won't change.
I'm trying to follow this tutorial.
When I'm adding .pdf to my url it does nothing. My controller has:
respond_to :html, :pdf.
My mime type has been declared.
I tried this too:
respond_to do |format|
format.html
format.pdf {
html = render_to_string(:layout => false , :action => "www.google.fr")
kit = PDFKit.new(html)
send_data(kit.to_pdf, :filename => "candidats.pdf", :type => 'application/pdf')
return # to avoid double render call
}
end
but it does not work and I don't get errors. My browser keep waiting for localhost, but nothing happens.
So how should I try to use pdfkit ?
edit 2 :
According to my rails' logs, rails render successfully the HTML. I saw this in the .log, rails doesn't send it to webrick nor to my browser. And my browser keeps waiting, and waiting and nothing happen. I only have little pictures here.
edit 3 : My webrick server seem unable to respond to other request, once he starts getting a .pdf version of my url, any ideas ?
edit 4 :
I am using rails 3.1, wkhtmltopdf 0.9.5 (windows installer) and pdfkit 0.5.2
I found a better way to access my .pdf urls even in developpement mode.
# Enable threaded mode
config.threadsafe!
# Code is not reloaded between requests
#config.cache_classes = true
Config.cache_classes is a comment, cause i had some problems when it wasn't. This way pdfkit works wonder even with rails 3.1. But, you don't reload code between requests.
That's not really a problem, cause you first work on your html, and you switch configuration, in order to check the pdf result. This way you don't have to bother about your production database.
Thanks to another stack overflow answer I got part of solution.
This works:
html = '<html><body>Toto de combats</body></html>'
#pdf = PDFKit.new(html)
send_data #pdf.to_pdf, :filename => "whatever.pdf",
:type => "application/pdf",
:disposition => "attachement"
You can replace attachement by inline, so the pdf is displayed in your browser.
In the stack overflow answer I spoke about (I don't remember the link), the .to_pdf was missing, but is mandatory. Otherwise PDF reader doesn't recognize it.
I'm trying to get this work with a .pdf url.
Edit 3:
My problem with .pdf urls is solved. known issue with rails 3.1, but google was unable to find them.
explanation : explanation
Workaround (hadn't tryied yet). workaround
Let's say I have an image that does not reside in the normal location:
{appname}/public/images/unconventional.gif
But instead here:
{appname}/unconventional.gif
I understand this is a complete violation of Rails conventions, is immoral and you should never do this under any circumstances and, furthermore, why would I even suggest such a foolish thing?
Ok, now that we have that out of the way, assuming I am on Windows and therefore symbolic links are out of the question, how is it possible to set this up?
Rails does not serve these images, it lets the web server do that. You had best change the configuration of your web server to handle this scenario. If you use Apache, for example, it would fairly easy to set up with mod_rewrite.
Making Rails serve these images will be ugly, but it is possible if you provide a route in your routes.rb that matches /public/images/unconventional.gif, and if the file itself does not exist. For example:
map.connect "public/images/unconventional.gif",
:controller => "static_image_controller",
:action => "serve"
And then create a controller StaticImageController:
class StaticImageController < ApplicationController
def serve
image = File.read(File.join(Rails.root, "unconventional.gif"))
send_data image, :type => "image/gif", :disposition => "inline"
end
end
Warning: if you use the above concept, note that if you use input from the URL to decide which file to serve (with params[:file], for example), you need to thoroughly sanitize the input, because you are risking exposing your entire file system to the outside world.