#!/usr/bin/env ruby require 'unicorn-cuba-base' require 'base64' $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) Application.new('httpimagestore', port: 3000, processor_count_factor: 2) do cli do description 'HTTP based image storage and thumbnailer' argument :config, cast: Pathname, description: 'configuration file path' version((Pathname.new(__FILE__).dirname + '..' + 'VERSION').read) end settings do |settings| Controller.settings[:config_file] = settings.config end main do |settings| require 'httpimagestore/error_reporter' class HTTPImageStore < Controller include PerfStats extend Stats def_stats( :workers, :total_requests, :total_errors ) raindrops_stats = Raindrops::Middleware::Stats.new self.use Raindrops::Middleware, stats: raindrops_stats StatsReporter << HTTPImageStore.stats StatsReporter << raindrops_stats StatsReporter << Plugin::ResponseHelpers.stats self.define do HTTPImageStore.stats.incr_total_requests on error? do HTTPImageStore.stats.incr_total_errors run ErrorReporter end on 'stats' do run StatsReporter end on 'health_check' do log.debug "health_check" if client = env['app.configuration'].thumbnailer # 8x8 PNG data = Base64.decode64('iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAI0lEQVQI1z3KMQoAMAyAwEv//2c7pFQQHISqssXaQWby+NsFYkkV7w+CVgAAAAAASUVORK5CYII=') begin thumbnail = client.thumbnail(data, 'fit', 4, 4, 'jpeg') unless thumbnail.data.length > 300 and thumbnail.data.include? 'JFIF' write_plain 502, 'bad image data returned from thumbnailer' halt res.finish end rescue Errno::ECONNREFUSED => error write_error 502, error halt res.finish end end write_plain 200, 'HTTP Image Store OK' end log.debug{"got request: #{env["REQUEST_METHOD"]} #{env["REQUEST_URI"]}"} env['app.configuration'].handlers.each do |handler| log.debug{"trying handler: #{handler}"} on eval(handler.http_method), *handler.uri_matchers.map{|m| instance_eval(&m.matcher)} do |*args| log.debug{"matched handler: #{handler}"} log.with_meta_context api_method: handler.http_method.upcase, api_handler: handler.to_s do measure "handling request", handler.to_s do # map and decode matched URI segments matches = {} names = handler.uri_matchers .map do |matcher| matcher.names end .flatten fail "matched more arguments than named (#{args.length} for #{names.length})" if args.length > names.length fail "matched less arguments than named (#{args.length} for #{names.length})" if args.length < names.length names.zip(args) .each do |name, value| fail "name should be a symbol" unless name.is_a? Symbol matches[name] = URI.utf_decode(value) end # decode remaining URI components path = (env['PATH_INFO'][1..-1] || '').split('/').map do |part| URI.utf_decode(part) end.join('/') # query string already decoded by Rack query_string = req.GET # actual request URI request_uri = env['REQUEST_URI'] request_headers = env.select{|k,v| k.start_with? 'HTTP_'}.map do |pair| [ pair[0].sub(/^HTTP_/, '').gsub('_', '-'), pair[1] ] end request_headers = Hash[request_headers] request_headers.delete('VERSION') body = measure "reading request body" do req.body.read end state = Configuration::RequestState.new(body, matches, path, query_string, request_uri, request_headers, memory_limit, env['xid'] || {}) measure "validating request" do handler.validators.each do |validator| validator.realize(state) unless validator.respond_to? :excluded? and validator.excluded?(state) end end unless handler.validators.empty? measure "sourcing images" do handler.sources.each do |source| source.realize(state) unless source.respond_to? :excluded? and source.excluded?(state) end end measure "processing images" do handler.processors.each do |processor| processor.realize(state) unless processor.respond_to? :excluded? and processor.excluded?(state) end end unless handler.processors.empty? measure "storing images" do handler.stores.each do |store| store.realize(state) unless store.respond_to? :excluded? and store.excluded?(state) end end unless handler.stores.empty? measure "sending response" do handler.output.realize(state) instance_eval(&state.output_callback) end end end end end on root do write_plain 200, 'HTTP Image Store' end end end class Configurator def initialize(app, configuration) @app = app @configuration = configuration end def call(env) env['app.configuration'] = @configuration @app.call(env) end end require 'httpimagestore/configuration' # connect Scope tree with Controller logger Configuration::Scope.logger = Controller.logger_for(Configuration::Scope) # load builin supported set require 'httpimagestore/configuration/path' require 'httpimagestore/configuration/handler' require 'httpimagestore/configuration/thumbnailer' require 'httpimagestore/configuration/identify' require 'httpimagestore/configuration/file' require 'httpimagestore/configuration/output' require 'httpimagestore/configuration/s3' require 'httpimagestore/configuration/validate_hmac' HTTPImageStore.use Configurator, Configuration.from_file(settings.config) HTTPImageStore end after_fork do |server, worker| HTTPImageStore.stats.incr_workers end end