httpimagestore 1.8.1 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +7 -7
- data/Gemfile.lock +20 -20
- data/README.md +165 -37
- data/Rakefile +7 -2
- data/VERSION +1 -1
- data/bin/httpimagestore +74 -41
- data/lib/httpimagestore/configuration/file.rb +20 -11
- data/lib/httpimagestore/configuration/handler.rb +96 -257
- data/lib/httpimagestore/configuration/handler/source_store_base.rb +37 -0
- data/lib/httpimagestore/configuration/handler/statement.rb +114 -0
- data/lib/httpimagestore/configuration/identify.rb +17 -9
- data/lib/httpimagestore/configuration/output.rb +33 -61
- data/lib/httpimagestore/configuration/path.rb +2 -2
- data/lib/httpimagestore/configuration/request_state.rb +131 -0
- data/lib/httpimagestore/configuration/s3.rb +41 -29
- data/lib/httpimagestore/configuration/thumbnailer.rb +189 -96
- data/lib/httpimagestore/configuration/validate_hmac.rb +170 -0
- data/lib/httpimagestore/error_reporter.rb +6 -1
- data/lib/httpimagestore/ruby_string_template.rb +10 -19
- metadata +40 -102
- data/.rspec +0 -1
- data/features/cache-control.feature +0 -41
- data/features/compatibility.feature +0 -165
- data/features/data-uri.feature +0 -55
- data/features/encoding.feature +0 -103
- data/features/error-reporting.feature +0 -281
- data/features/flexi.feature +0 -259
- data/features/health-check.feature +0 -29
- data/features/request-matching.feature +0 -211
- data/features/rewrite.feature +0 -122
- data/features/s3-store-and-thumbnail.feature +0 -82
- data/features/source-failover.feature +0 -71
- data/features/step_definitions/httpimagestore_steps.rb +0 -203
- data/features/storage.feature +0 -198
- data/features/support/env.rb +0 -116
- data/features/support/test-large.jpg +0 -0
- data/features/support/test.empty +0 -0
- data/features/support/test.jpg +0 -0
- data/features/support/test.png +0 -0
- data/features/support/test.txt +0 -1
- data/features/support/tiny.png +0 -0
- data/features/xid-forwarding.feature +0 -49
- data/httpimagestore.gemspec +0 -145
- data/load_test/load_test.1k.23a022f6e.m1.small-comp.csv +0 -3
- data/load_test/load_test.1k.ec9bde794.m1.small.csv +0 -4
- data/load_test/load_test.jmx +0 -317
- data/load_test/thumbnail_specs.csv +0 -11
- data/load_test/thumbnail_specs_v2.csv +0 -10
- data/spec/configuration_file_spec.rb +0 -333
- data/spec/configuration_handler_spec.rb +0 -255
- data/spec/configuration_identify_spec.rb +0 -67
- data/spec/configuration_output_spec.rb +0 -821
- data/spec/configuration_path_spec.rb +0 -138
- data/spec/configuration_s3_spec.rb +0 -911
- data/spec/configuration_source_failover_spec.rb +0 -101
- data/spec/configuration_spec.rb +0 -90
- data/spec/configuration_thumbnailer_spec.rb +0 -483
- data/spec/ruby_string_template_spec.rb +0 -61
- data/spec/spec_helper.rb +0 -89
- data/spec/support/compute.jpg +0 -0
- data/spec/support/cuba_response_env.rb +0 -40
- data/spec/support/full.cfg +0 -183
- data/spec/support/utf_string.txt +0 -1
@@ -4,7 +4,7 @@ require 'msgpack'
|
|
4
4
|
require 'addressable/uri'
|
5
5
|
require 'httpimagestore/aws_sdk_regions_hack'
|
6
6
|
require 'httpimagestore/configuration/path'
|
7
|
-
require 'httpimagestore/configuration/handler'
|
7
|
+
require 'httpimagestore/configuration/handler/source_store_base'
|
8
8
|
require 'httpimagestore/configuration/source_failover'
|
9
9
|
|
10
10
|
module Configuration
|
@@ -123,7 +123,7 @@ module Configuration
|
|
123
123
|
io.write [header.length].pack('L') # header length
|
124
124
|
io.write header
|
125
125
|
io.write data
|
126
|
-
rescue
|
126
|
+
rescue
|
127
127
|
unlink # remove broken cache file
|
128
128
|
raise
|
129
129
|
end
|
@@ -296,15 +296,17 @@ module Configuration
|
|
296
296
|
node.required_attributes('bucket', 'path')
|
297
297
|
node.valid_attribute_values('public_access', true, false, nil)
|
298
298
|
|
299
|
-
bucket, path_spec, public_access, cache_control, prefix, cache_root,
|
300
|
-
*node.
|
299
|
+
bucket, path_spec, public_access, cache_control, prefix, cache_root, remaining =
|
300
|
+
*node.grab_attributes_with_remaining('bucket', 'path', 'public', 'cache-control', 'prefix', 'cache-root')
|
301
|
+
conditions, remaining = *ConditionalInclusion.grab_conditions_with_remaining(remaining)
|
302
|
+
remaining.empty? or raise UnexpectedAttributesError.new(node, remaining)
|
303
|
+
|
301
304
|
public_access = false if public_access.nil?
|
302
305
|
prefix = '' if prefix.nil?
|
303
306
|
|
304
|
-
self.new(
|
307
|
+
s3 = self.new(
|
305
308
|
configuration.global,
|
306
309
|
image_name,
|
307
|
-
InclusionMatcher.new(image_name, if_image_name_on),
|
308
310
|
bucket,
|
309
311
|
path_spec,
|
310
312
|
public_access,
|
@@ -312,10 +314,13 @@ module Configuration
|
|
312
314
|
prefix,
|
313
315
|
cache_root
|
314
316
|
)
|
317
|
+
s3.with_conditions(conditions)
|
318
|
+
s3
|
315
319
|
end
|
316
320
|
|
317
|
-
def initialize(global, image_name,
|
318
|
-
super
|
321
|
+
def initialize(global, image_name, bucket, path_spec, public_access, cache_control, prefix, cache_root)
|
322
|
+
super(global, image_name, path_spec)
|
323
|
+
|
319
324
|
@bucket = bucket
|
320
325
|
@public_access = public_access
|
321
326
|
@cache_control = cache_control
|
@@ -379,6 +384,8 @@ module Configuration
|
|
379
384
|
end
|
380
385
|
|
381
386
|
class S3Source < S3SourceStoreBase
|
387
|
+
include PerfStats
|
388
|
+
|
382
389
|
def self.match(node)
|
383
390
|
node.name == 'source_s3'
|
384
391
|
end
|
@@ -391,16 +398,18 @@ module Configuration
|
|
391
398
|
put_sourced_named_image(request_state) do |image_name, rendered_path|
|
392
399
|
log.info "sourcing '#{image_name}' image from S3 '#{@bucket}' bucket under '#{rendered_path}' key"
|
393
400
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
401
|
+
measure "sourcing image from S3", "image: '#{image_name}' bucket: '#{@bucket}'" do
|
402
|
+
object(rendered_path) do |object|
|
403
|
+
data = request_state.memory_limit.get do |limit|
|
404
|
+
object.read(limit + 1)
|
405
|
+
end
|
406
|
+
S3SourceStoreBase.stats.incr_total_s3_source
|
407
|
+
S3SourceStoreBase.stats.incr_total_s3_source_bytes(data.bytesize)
|
400
408
|
|
401
|
-
|
402
|
-
|
403
|
-
|
409
|
+
image = Image.new(data, object.content_type)
|
410
|
+
image.source_url = url(object)
|
411
|
+
image
|
412
|
+
end
|
404
413
|
end
|
405
414
|
end
|
406
415
|
end
|
@@ -413,6 +422,8 @@ module Configuration
|
|
413
422
|
SourceFailover::register_node_parser S3Source
|
414
423
|
|
415
424
|
class S3Store < S3SourceStoreBase
|
425
|
+
include PerfStats
|
426
|
+
|
416
427
|
def self.match(node)
|
417
428
|
node.name == 'store_s3'
|
418
429
|
end
|
@@ -426,21 +437,22 @@ module Configuration
|
|
426
437
|
acl = @public_access ? :public_read : :private
|
427
438
|
|
428
439
|
log.info "storing '#{image_name}' image in S3 '#{@bucket}' bucket under '#{rendered_path}' key with #{acl} access"
|
440
|
+
measure "storing image in S3", "image: '#{image_name}' bucket: '#{@bucket}'" do
|
441
|
+
object(rendered_path) do |object|
|
442
|
+
image.mime_type or log.warn "storing '#{image_name}' in S3 '#{@bucket}' bucket under '#{rendered_path}' key with unknown mime type"
|
429
443
|
|
430
|
-
|
431
|
-
|
444
|
+
options = {}
|
445
|
+
options[:single_request] = true
|
446
|
+
options[:content_type] = image.mime_type if image.mime_type
|
447
|
+
options[:acl] = acl
|
448
|
+
options[:cache_control] = @cache_control if @cache_control
|
432
449
|
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
options[:acl] = acl
|
437
|
-
options[:cache_control] = @cache_control if @cache_control
|
450
|
+
object.write(image.data, options)
|
451
|
+
S3SourceStoreBase.stats.incr_total_s3_store
|
452
|
+
S3SourceStoreBase.stats.incr_total_s3_store_bytes(image.data.bytesize)
|
438
453
|
|
439
|
-
|
440
|
-
|
441
|
-
S3SourceStoreBase.stats.incr_total_s3_store_bytes(image.data.bytesize)
|
442
|
-
|
443
|
-
image.store_url = url(object)
|
454
|
+
image.store_url = url(object)
|
455
|
+
end
|
444
456
|
end
|
445
457
|
end
|
446
458
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'httpthumbnailer-client'
|
2
2
|
require 'httpimagestore/ruby_string_template'
|
3
|
-
require 'httpimagestore/configuration/handler'
|
3
|
+
require 'httpimagestore/configuration/handler/statement'
|
4
4
|
|
5
5
|
module Configuration
|
6
6
|
class Thumnailer
|
@@ -25,20 +25,49 @@ module Configuration
|
|
25
25
|
end
|
26
26
|
Global.register_node_parser Thumnailer
|
27
27
|
|
28
|
-
class
|
28
|
+
class NoValueForSpecTemplatePlaceholderError < ConfigurationError
|
29
29
|
def initialize(image_name, spec_name, value_name, template)
|
30
30
|
super "cannot generate specification for thumbnail '#{image_name}': cannot generate value for attribute '#{spec_name}' from template '#{template}': no value for \#{#{value_name}}"
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
class UnknownThumbnailingDirectiveError < ConfigurationError
|
35
|
+
def initialize(source_image_name, image_name, directive)
|
36
|
+
super "unknown directive '#{directive}' for thumbnail specification '#{image_name}' for image '#{source_image_name}'"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
34
40
|
class NoSpecSelectedError < RuntimeError
|
35
41
|
def initialize(specs)
|
36
|
-
super "no
|
42
|
+
super "no thumbnail specs were selected, please use at least one of: #{specs.join(', ')}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class InvalidSpecError < RuntimeError
|
47
|
+
def initialize(spec_name, cause)
|
48
|
+
super "thumbnail spec '#{spec_name}' is invalid: #{cause}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class InvalidOptionsSpecError < InvalidSpecError
|
53
|
+
def initialize(spec_name, spec, cause)
|
54
|
+
super spec_name, "options spec '#{spec}' format is invalid: #{cause}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class InvalidEditsSpecError < InvalidSpecError
|
59
|
+
def initialize(spec_name, spec, cause)
|
60
|
+
super spec_name, "edits spec '#{spec}' format is invalid: #{cause}"
|
37
61
|
end
|
38
62
|
end
|
39
63
|
|
40
64
|
class Thumbnail < HandlerStatement
|
41
65
|
include ClassLogging
|
66
|
+
include ImageName
|
67
|
+
include LocalConfiguration
|
68
|
+
include GlobalConfiguration
|
69
|
+
include ConditionalInclusion
|
70
|
+
include PerfStats
|
42
71
|
|
43
72
|
extend Stats
|
44
73
|
def_stats(
|
@@ -63,33 +92,89 @@ module Configuration
|
|
63
92
|
|
64
93
|
class ThumbnailSpec < HandlerStatement
|
65
94
|
include ImageName
|
95
|
+
include LocalConfiguration
|
66
96
|
include ConditionalInclusion
|
67
97
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
98
|
+
class EditSpec
|
99
|
+
include HandlerStatement::ConditionalInclusion
|
100
|
+
|
101
|
+
def initialize(name, args, options, edit_no)
|
102
|
+
@name = name
|
103
|
+
@args = args.map.with_index do |arg, arg_no|
|
104
|
+
arg.to_template.with_missing_resolver{|locals, key| raise NoValueForSpecTemplatePlaceholderError.new(image_name, "edit #{edit_no + 1} argument #{arg_no + 1}", key, arg)}
|
105
|
+
end
|
106
|
+
|
107
|
+
@options = options.merge(options) do |option, old, template|
|
108
|
+
template.to_template.with_missing_resolver{|locals, key| raise NoValueForSpecTemplatePlaceholderError.new(image_name, "edit #{edit_no + 1} option '#{option}' value", key, arg)}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def render(request_state = {})
|
113
|
+
args = @args.map.with_index do |arg, arg_no|
|
114
|
+
arg.render(request_state)
|
115
|
+
end
|
116
|
+
|
117
|
+
options = @options.merge(@options) do |option, old, template|
|
118
|
+
template.render(request_state)
|
119
|
+
end
|
120
|
+
|
121
|
+
HTTPThumbnailerClient::ThumbnailSpec::EditSpec.new(@name, args, options)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def initialize(image_name, method, width, height, format, options = {}, edits = [])
|
126
|
+
with_image_name(image_name)
|
127
|
+
@method = method.to_template.with_missing_resolver{|locals, key| raise NoValueForSpecTemplatePlaceholderError.new(image_name, 'method', key, method)}
|
128
|
+
@width = width.to_s.to_template.with_missing_resolver{|locals, key| raise NoValueForSpecTemplatePlaceholderError.new(image_name, 'width', key, width)}
|
129
|
+
@height = height.to_s.to_template.with_missing_resolver{|locals, key| raise NoValueForSpecTemplatePlaceholderError.new(image_name, 'height', key, height)}
|
130
|
+
@format = format.to_template.with_missing_resolver{|locals, key| raise NoValueForSpecTemplatePlaceholderError.new(image_name, 'format', key, format)}
|
74
131
|
|
75
132
|
@options = options.merge(options) do |option, old, template|
|
76
|
-
template.to_s.to_template.with_missing_resolver{|locals, field| raise
|
133
|
+
template.to_s.to_template.with_missing_resolver{|locals, field| raise NoValueForSpecTemplatePlaceholderError.new(image_name, option, field, template)}
|
77
134
|
end
|
135
|
+
|
136
|
+
@edits = edits
|
78
137
|
end
|
79
138
|
|
80
|
-
def render(
|
81
|
-
options = @options.inject({}){|h, v| h[v.first] = v.last.render(
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
139
|
+
def render(request_state = {})
|
140
|
+
options = @options.inject({}){|h, v| h[v.first] = v.last.render(request_state); h}
|
141
|
+
|
142
|
+
# NOTE: normally options will be passed as options=String; but may be supplied each by each as in the configuration with key=value pairs
|
143
|
+
nested_options = begin
|
144
|
+
opts = options.delete('options') || ''
|
145
|
+
HTTPThumbnailerClient::ThumbnailSpec.parse_options(HTTPThumbnailerClient::ThumbnailSpec.split_args(opts))
|
146
|
+
rescue HTTPThumbnailerClient::ThumbnailSpec::InvalidFormatError => error
|
147
|
+
raise InvalidOptionsSpecError.new(image_name, opts, error)
|
148
|
+
end
|
149
|
+
|
150
|
+
edits_option = HTTPThumbnailerClient::ThumbnailSpec.split_edits(options.delete('edits') || '').map do |edit|
|
151
|
+
begin
|
152
|
+
HTTPThumbnailerClient::ThumbnailSpec::EditSpec.from_string(edit)
|
153
|
+
rescue HTTPThumbnailerClient::ThumbnailSpec::InvalidFormatError => error
|
154
|
+
raise InvalidEditsSpecError.new(image_name, edit, error)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
edits = @edits.select do |edit|
|
159
|
+
edit.included?(request_state)
|
160
|
+
end.map do |edit|
|
161
|
+
edit.render(request_state)
|
162
|
+
end
|
163
|
+
|
164
|
+
spec = begin
|
165
|
+
HTTPThumbnailerClient::ThumbnailSpec.new(
|
166
|
+
@method.render(request_state),
|
167
|
+
@width.render(request_state),
|
168
|
+
@height.render(request_state),
|
169
|
+
@format.render(request_state),
|
170
|
+
nested_options.merge(options),
|
171
|
+
edits | edits_option
|
172
|
+
)
|
173
|
+
rescue HTTPThumbnailerClient::ThumbnailSpec::InvalidFormatError => error
|
174
|
+
raise InvalidSpecError.new(image_name, error)
|
175
|
+
end
|
176
|
+
|
177
|
+
Struct.new(:name, :spec).new(image_name, spec)
|
93
178
|
end
|
94
179
|
end
|
95
180
|
|
@@ -98,13 +183,19 @@ module Configuration
|
|
98
183
|
end
|
99
184
|
|
100
185
|
def self.parse(configuration, node)
|
186
|
+
# if we don't have any subnodes use this node as single subnode and parse as if they were subnodes
|
101
187
|
use_multipart_api = node.values.length == 1 ? true : false
|
102
188
|
|
103
|
-
nodes = use_multipart_api ?
|
189
|
+
nodes = use_multipart_api ? node.children : [node]
|
104
190
|
source_image_name = use_multipart_api ? node.grab_values('source image name').first : nil # parsed later
|
105
191
|
|
192
|
+
general_conditions = []
|
193
|
+
if use_multipart_api
|
194
|
+
general_conditions, remaining = *ConditionalInclusion.grab_conditions_with_remaining(node.attributes)
|
195
|
+
remaining.empty? or raise UnexpectedAttributesError.new(node, remaining)
|
196
|
+
end
|
197
|
+
|
106
198
|
nodes.empty? and raise NoValueError.new(node, 'thumbnail image name')
|
107
|
-
matcher = nil
|
108
199
|
|
109
200
|
specs = nodes.map do |node|
|
110
201
|
if use_multipart_api
|
@@ -113,9 +204,26 @@ module Configuration
|
|
113
204
|
source_image_name, image_name = *node.grab_values('source image name', 'thumbnail image name')
|
114
205
|
end
|
115
206
|
|
116
|
-
operation, width, height, format,
|
207
|
+
operation, width, height, format, remaining = *node.grab_attributes_with_remaining('operation', 'width', 'height', 'format')
|
208
|
+
|
209
|
+
conditions, remaining = *ConditionalInclusion.grab_conditions_with_remaining(remaining)
|
210
|
+
|
211
|
+
edits = []
|
212
|
+
# check for subnodes
|
213
|
+
subnodes = node.children.group_by{|node| node.name}
|
214
|
+
edit_nodes = subnodes.delete('edit')
|
215
|
+
edit_nodes and edit_nodes.each.with_index do |node, edit_no|
|
216
|
+
edit_conditions, edit_options = *ConditionalInclusion.grab_conditions_with_remaining(node.attributes)
|
217
|
+
|
218
|
+
args = node.values.dup
|
219
|
+
|
220
|
+
edit = ThumbnailSpec::EditSpec.new(args.shift, args, edit_options, edit_no)
|
221
|
+
edit.with_conditions(edit_conditions)
|
222
|
+
|
223
|
+
edits << edit
|
224
|
+
end
|
117
225
|
|
118
|
-
|
226
|
+
subnodes.keys.empty? or raise UnknownThumbnailingDirectiveError.new(source_image_name, image_name, subnodes.keys.join(' and '))
|
119
227
|
|
120
228
|
ThumbnailSpec.new(
|
121
229
|
image_name,
|
@@ -124,41 +232,45 @@ module Configuration
|
|
124
232
|
height || 'input',
|
125
233
|
format || 'input',
|
126
234
|
remaining || {},
|
127
|
-
|
128
|
-
)
|
235
|
+
edits
|
236
|
+
).with_conditions(conditions)
|
129
237
|
end
|
130
238
|
|
131
|
-
|
132
|
-
|
133
|
-
configuration.processors << self.new(
|
239
|
+
thum = self.new(
|
134
240
|
configuration.global,
|
135
241
|
source_image_name,
|
136
242
|
specs,
|
137
243
|
use_multipart_api,
|
138
|
-
matcher
|
139
244
|
)
|
245
|
+
thum.with_conditions(general_conditions)
|
246
|
+
configuration.processors << thum
|
140
247
|
end
|
141
248
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
super(global, matcher)
|
249
|
+
def initialize(global, source_image_name, specs, use_multipart_api)
|
250
|
+
with_global_configuration(global)
|
251
|
+
with_image_name(source_image_name)
|
146
252
|
@source_image_name = source_image_name
|
147
|
-
@specs = specs
|
253
|
+
@specs = specs.freeze
|
148
254
|
@use_multipart_api = use_multipart_api
|
149
255
|
end
|
150
256
|
|
151
257
|
def realize(request_state)
|
152
258
|
client = @global.thumbnailer or fail 'thumbnailer configuration'
|
153
259
|
|
154
|
-
|
155
|
-
@specs.select do |spec|
|
260
|
+
specs = @specs.select do |spec|
|
156
261
|
spec.included?(request_state)
|
157
|
-
end.each do |spec|
|
158
|
-
rendered_specs.merge! spec.render(request_state)
|
159
262
|
end
|
160
263
|
|
161
|
-
|
264
|
+
if specs.empty?
|
265
|
+
# in single part form we are OK to skip the thumbnailing all thogether
|
266
|
+
return if not @use_multipart_api
|
267
|
+
# in multipart for at least one thumbnail spec should be selected
|
268
|
+
raise NoSpecSelectedError.new(@specs.map(&:image_name))
|
269
|
+
end
|
270
|
+
|
271
|
+
rendered_specs = specs.map do |spec|
|
272
|
+
spec.render(request_state)
|
273
|
+
end
|
162
274
|
|
163
275
|
source_image = request_state.images[@source_image_name]
|
164
276
|
|
@@ -167,69 +279,38 @@ module Configuration
|
|
167
279
|
input_width = nil
|
168
280
|
input_height = nil
|
169
281
|
|
282
|
+
log.info "thumbnailing '#{@source_image_name}' to specs: #{rendered_specs.map{|s| "#{s.name} -> #{s.spec}"}.join('; ')}"
|
170
283
|
Thumbnail.stats.incr_total_thumbnail_requests
|
171
284
|
Thumbnail.stats.incr_total_thumbnail_requests_bytes source_image.data.bytesize
|
172
285
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
# need to reference to local so they are available within thumbnail() block context
|
177
|
-
source_image_name = @source_image_name
|
178
|
-
logger = log
|
179
|
-
|
180
|
-
begin
|
181
|
-
thumbnails = client.with_headers(request_state.headers).thumbnail(source_image.data) do
|
182
|
-
rendered_specs.each_pair do |name, spec|
|
183
|
-
begin
|
184
|
-
thumbnail(*spec)
|
185
|
-
rescue HTTPThumbnailerClient::HTTPThumbnailerClientError => error
|
186
|
-
logger.warn 'got thumbnailer error while passing specs', error
|
187
|
-
raise ThumbnailingError.new(source_image_name, name, error)
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
rescue HTTPThumbnailerClient::HTTPThumbnailerClientError => error
|
192
|
-
logger.warn 'got thumbnailer error while sending input data', error
|
193
|
-
raise ThumbnailingError.new(source_image_name, nil, error)
|
194
|
-
end
|
195
|
-
|
196
|
-
input_mime_type = thumbnails.input_mime_type
|
197
|
-
input_width = thumbnails.input_width
|
198
|
-
input_height = thumbnails.input_height
|
199
|
-
|
200
|
-
# check each thumbnail for errors
|
201
|
-
thumbnails = Hash[rendered_specs.keys.zip(thumbnails)]
|
202
|
-
thumbnails.each do |name, thumbnail|
|
203
|
-
if thumbnail.kind_of? HTTPThumbnailerClient::HTTPThumbnailerClientError
|
204
|
-
error = thumbnail
|
205
|
-
log.warn 'got single thumbnail error', error
|
206
|
-
raise ThumbnailingError.new(@source_image_name, name, error)
|
207
|
-
end
|
286
|
+
thumbnails = begin
|
287
|
+
measure "thumbnailing image", "'#{@source_image_name}' to specs: #{rendered_specs.map{|s| "#{s.name} -> #{s.spec}"}.join('; ')}" do
|
288
|
+
client.with_headers(request_state.forward_headers).thumbnail(source_image.data, *rendered_specs.map(&:spec))
|
208
289
|
end
|
290
|
+
rescue HTTPThumbnailerClient::HTTPThumbnailerClientError => error
|
291
|
+
log.warn 'got thumbnailer error', error
|
292
|
+
raise ThumbnailingError.new(@source_image_name, rendered_specs.length == 1 ? rendered_specs.first.name : nil, error)
|
293
|
+
end
|
209
294
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
end
|
214
|
-
else
|
215
|
-
name, rendered_spec = *rendered_specs.first
|
216
|
-
log.info "thumbnailing '#{@source_image_name}' to '#{name}' with spec: #{rendered_spec}"
|
217
|
-
|
218
|
-
begin
|
219
|
-
thumbnail = client.with_headers(request_state.headers).thumbnail(source_image.data, *rendered_spec)
|
220
|
-
request_state.memory_limit.borrow(thumbnail.data.bytesize, "thumbnail '#{name}'")
|
221
|
-
|
222
|
-
input_mime_type = thumbnail.input_mime_type
|
223
|
-
input_width = thumbnail.input_width
|
224
|
-
input_height = thumbnail.input_height
|
295
|
+
input_mime_type = thumbnails.input_mime_type
|
296
|
+
input_width = thumbnails.input_width
|
297
|
+
input_height = thumbnails.input_height
|
225
298
|
|
226
|
-
|
227
|
-
|
228
|
-
|
299
|
+
# check each thumbnail for errors
|
300
|
+
thumbnails = Hash[rendered_specs.map(&:name).zip(thumbnails)]
|
301
|
+
thumbnails.each do |name, thumbnail|
|
302
|
+
if thumbnail.kind_of? HTTPThumbnailerClient::HTTPThumbnailerClientError
|
303
|
+
error = thumbnail
|
304
|
+
log.warn 'got single thumbnail error', error
|
229
305
|
raise ThumbnailingError.new(@source_image_name, name, error)
|
230
306
|
end
|
231
307
|
end
|
232
308
|
|
309
|
+
# borrow from memory limit - note that we might have already used too much memory
|
310
|
+
thumbnails.each do |name, thumbnail|
|
311
|
+
request_state.memory_limit.borrow(thumbnail.data.bytesize, "thumbnail '#{name}'")
|
312
|
+
end
|
313
|
+
|
233
314
|
# copy input source path and url
|
234
315
|
thumbnails.each do |name, thumbnail|
|
235
316
|
thumbnail.extend ImageMetaData
|
@@ -247,7 +328,19 @@ module Configuration
|
|
247
328
|
|
248
329
|
request_state.images.merge! thumbnails
|
249
330
|
end
|
331
|
+
|
332
|
+
def included?(request_state)
|
333
|
+
# TODO this is to complicated!
|
334
|
+
if @use_multipart_api
|
335
|
+
super(request_state)
|
336
|
+
else
|
337
|
+
@specs.any? do |spec|
|
338
|
+
spec.included?(request_state)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
250
342
|
end
|
343
|
+
|
251
344
|
Handler::register_node_parser Thumbnail
|
252
345
|
StatsReporter << Thumbnail.stats
|
253
346
|
end
|