httpimagestore 1.8.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|