This repository has been archived by the owner on Feb 12, 2023. It is now read-only.
/
httpimagestore
executable file
·189 lines (160 loc) · 5.79 KB
/
httpimagestore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/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