Image Placeholder for your development environment
Image Placeholder for your development environment
Some time ago I was working together with Paweł on one of our clients web application. We used copy of products catalog coming from production server on our development machines. What we were lacking were product photos, causing application layout to look poorly and making any css job hard. We tried to find a smart solution for that case.
Every time you request non-existing image in your app, you got a 404. What if we could detect such case and modify response to actually contain image and return 200 OK? Custom rack middleware is a perfect place for that. The code is pretty simple:
require 'net/http'
module ImagePlaceholder
class Middleware
def initialize(app, image_extensions: %w(jpg png), size_pattern: {/.*/ => 100}, host: 'via.placeholder.com')
@app = app
@image_extensions = image_extensions
@size_pattern = size_pattern
@host = host
end
def call(env)
status, headers, response = @app.call(env)
request_path = URI.decode(Rack::Request.new(env).fullpath)
if not_found?(status) && image?(request_path)
serve_placeholder_image(matched_size(request_path))
else
[status, headers, response]
end
end
private
def serve_placeholder_image(size = 100)
net_response = Net::HTTP.get_response(URI("https://#{@host}/#{size}"))
rack_response = Rack::Response.new(net_response.body, net_response.code.to_i)
safe_headers = net_response.to_hash
.reject { |key, _| hop_by_hop_header_fields.include?(key.downcase) }
.reject { |key, _| key.downcase == 'content-length' }
safe_headers.each do |key, values|
values.each do |value|
rack_response.add_header(key, value)
end
end
rack_response.finish
end
def hop_by_hop_header_fields
# https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3.1
%w(connection keep-alive proxy-authenticate proxy-authorization te trailer transfer-encoding upgrade)
end
def not_found?(status)
status == 404
end
def image?(path)
@image_extensions.include? File.extname(path)[1, 3]
end
def matched_size(path)
@size_pattern.find { |pattern, _| pattern.match(path) }[1]
end
end
end
- Check whether image is requested
- Check if status is 404
- If yes, make a get request to a service like placeholder.com and modify response with image from it
- Otherwise, just return standard response
Our initial version used Fill Murray which brought us smile every time we launched products catalog to do some work on.
You can go with Steven Seagal eating a carrot or Nicolas Cage if you would like to. Just add host
option to middleware use:
# config/environments/development.rb
Rails.application.configure do
config.middleware.use ImagePlaceholder::Middleware, host: 'fillmurray.com'
end
You can also match desired image sizes, providing pattern:
# config/environments/development.rb
Rails.application.configure do
config.middleware.use ImagePlaceholder::Middleware, size_pattern: {
%r{/uploads/.*/s_[0-9]+\.[a-z]{3}$} => 200, # /uploads/product/cover/42/s_9781467775687.jpg
%r{/uploads/.*/xl_[0-9]+\.[a-z]{3}$} => 750, # /uploads/product/cover/42/xl_9781467775687.jpg
%r{.*} => 1024, # /uploads/random/spanish_inquisition.png
}
end
By default, ImagePlaceholde::Middleware
supports .jpg
and .png
images, but you can extend supported filetypes with ease:
# config/environments/development.rb
Rails.application.configure do
config.middleware.use ImagePlaceholder::Middleware, image_extensions: %w(jpg jpeg png webp gif)
end
And last, but not least, it can be used with any Rack application:
# config.ru
use ImagePlaceholder::Middleware, size_pattern: { /.*/ => '320/320' }, host: 'fillmurray.com'
run YourRackApp
To start using it, put gem image_placeholder
into your Gemfile
. If you would like to contribute or read more, visit https://github.com/arkency/image_placeholder
Enjoy!