I started a spike last night to help me understand what was needed to thumbnail images in Rack. The idea is that, given a request for an image ending in
_sm.jpg
, the handler should request the full-sized image, thumbnail it, and return the smaller image to the requesting client.I was actually pretty close last night, but sleepiness eventually got the better of me. This is a functioning version of last night's thumbnailer:
class ThumbNailMissing last night was the bold section, where I actually use the body response from a Rack application like it is supposed to be used—with an
def initialize(app); @app = app end
def call(env)
if env['REQUEST_URI'] =~ /_sm.jpe?g/
env['REQUEST_URI'].sub!(/_sm\./, '.')
env['REQUEST_PATH'].sub!(/_sm\./, '.')
env['PATH_INFO'].sub!(/_sm\./, '.')
http_code, headers, original_body = @app.call(env)
img_data = ''
body.each do |data|
img_data << data
end
ImageScience.with_image_from_memory(img_data) do |img|
img.resize(100, 150) do |small|
small.save "/tmp/small.jpg"
end
end
f = File.open "/tmp/small.jpg"
[http_code, headers, f.read]
else
@app.call(env)
end
end
end
use ThumbNail
each
iteration.I could delete the code and start on implementation proper, but first I would like to explore building a cache for the thumbnails. There is no sense in generating the images on each request. In this case, the
call
method needs to decide if a cache hit occurs and, if not, generate the thumbnail:# Main Rack call responderThe rest of the code remains essentially the same. The complete, seemingly functional Rack handler:
def call(env)
if env['REQUEST_URI'] =~ /_sm.jpe?g/
# Handle thumbnail requests
unless File.exists?(filename(env))
mk_thumbnail(env)
end
small = File.new(filename(env)).read
[200, { }, small]
else
# Pass-thru non thumbnail requests
@app.call(env)
end
end
class ThumbNailUpdating my Sinatra app to pull these images, I find the response time on my homepage much better (going from 1-2 seconds to ~100ms):
# hard-code the store location (could be set via use options)
STORE = '/var/cache/thumbnail'
# Basic Rack initialization
def initialize(app); @app = app end
# Main Rack call responder
def call(env)
if env['REQUEST_URI'] =~ /_sm.jpe?g/
# Handle thumbnail requests
unless File.exists?(filename(env))
mk_thumbnail(env)
end
small = File.new(filename(env)).read
[200, { }, small]
else
# Pass-thru non thumbnail requests
@app.call(env)
end
end
# Make the thumbnail, storing it in the cache for subsequent
# requests
def mk_thumbnail(env)
$stderr.puts "[thumbnail] building thumbnail..."
cache_filename = filename(env)
path = cache_filename.sub(/\/[^\/]+$/, '')
FileUtils.mkdir_p(path)
env['REQUEST_URI'].sub!(/_sm\./, '.')
env['REQUEST_PATH'].sub!(/_sm\./, '.')
env['PATH_INFO'].sub!(/_sm\./, '.')
http_code, headers, body = @app.call(env)
img_data = ''
body.each do |data|
img_data << data
end
ImageScience.with_image_from_memory(img_data) do |img|
img.resize(200, 150) do |small|
small.save cache_filename
end
end
end
# Helper method for calculating the location of the file in the
# cache.
def filename(env)
"#{STORE}#{env['PATH_INFO']}"
end
end
Tomorrow I delete all this code and then go back and do it right. Even in this limited spike, there is already code with which I am unhappy. A little BDD should clear that up.
No comments:
Post a Comment