httpimagestore 1.4.1 → 1.5.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.
@@ -1,18 +1,17 @@
1
1
  Feature: Storing images under different names
2
2
  Storage supports UUID and SHA digest based auto generated storage as well as user provided via request or static configuration string.
3
3
 
4
-
5
4
  Background:
6
5
  Given httpthumbnailer server is running at http://localhost:3100/health_check
7
6
  Given httpimagestore server is running at http://localhost:3000/health_check with the following configuration
8
7
  """
9
- path "input_digest" "#{input_digest}"
10
- path "input_sha256" "#{input_sha256}"
11
- path "image_digest" "#{image_digest}"
12
- path "image_sha256" "#{image_sha256}"
13
- path "uuid" "#{uuid}"
14
- path "image_meta" "#{image_width}x#{image_height}.#{image_mime_extension}"
15
- path "input_image_meta" "#{input_image_width}x#{input_image_height}.#{input_image_mime_extension}"
8
+ path "input_digest" "#{input_digest}"
9
+ path "input_sha256" "#{input_sha256}"
10
+ path "image_digest" "#{image_digest}"
11
+ path "image_sha256" "#{image_sha256}"
12
+ path "uuid" "#{uuid}"
13
+ path "image_meta" "#{image_width}x#{image_height}.#{image_mime_extension}"
14
+ path "input_image_meta" "#{input_image_width}x#{input_image_height}.#{input_image_mime_extension}"
16
15
 
17
16
  post "images" "input_digest" {
18
17
  thumbnail "input" "thumbnail" operation="crop" width="50" height="50"
@@ -196,4 +195,3 @@ Feature: Storing images under different names
196
195
  509x719.png
197
196
  """
198
197
  Then file /tmp/509x719.png will contain PNG image of size 50x100
199
-
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "httpimagestore"
8
- s.version = "1.4.1"
8
+ s.version = "1.5.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jakub Pastuszek"]
12
- s.date = "2013-10-07"
12
+ s.date = "2013-10-22"
13
13
  s.description = "Thumbnails images using httpthumbnailer and stored data on HTTP server (S3)"
14
14
  s.email = "jpastuszek@gmail.com"
15
15
  s.executables = ["httpimagestore"]
@@ -19,6 +19,17 @@ Gem::Specification.new do |s|
19
19
  ]
20
20
  s.files = [
21
21
  ".document",
22
+ ".idea/.name",
23
+ ".idea/.rakeTasks",
24
+ ".idea/codeStyleSettings.xml",
25
+ ".idea/dictionaries/wcc.xml",
26
+ ".idea/encodings.xml",
27
+ ".idea/httpimagestore.iml",
28
+ ".idea/jenkinsSettings.xml",
29
+ ".idea/misc.xml",
30
+ ".idea/modules.xml",
31
+ ".idea/scopes/scope_settings.xml",
32
+ ".idea/vcs.xml",
22
33
  ".rspec",
23
34
  "Gemfile",
24
35
  "Gemfile.lock",
@@ -32,6 +43,7 @@ Gem::Specification.new do |s|
32
43
  "features/error-reporting.feature",
33
44
  "features/flexi.feature",
34
45
  "features/health-check.feature",
46
+ "features/request-matching.feature",
35
47
  "features/s3-store-and-thumbnail.feature",
36
48
  "features/step_definitions/httpimagestore_steps.rb",
37
49
  "features/storage.feature",
@@ -81,7 +93,7 @@ Gem::Specification.new do |s|
81
93
  s.specification_version = 3
82
94
 
83
95
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
84
- s.add_runtime_dependency(%q<unicorn-cuba-base>, ["~> 1.1.1"])
96
+ s.add_runtime_dependency(%q<unicorn-cuba-base>, ["~> 1.1.2"])
85
97
  s.add_runtime_dependency(%q<httpthumbnailer-client>, ["~> 1.1.1"])
86
98
  s.add_runtime_dependency(%q<aws-sdk>, ["~> 1.10"])
87
99
  s.add_runtime_dependency(%q<mime-types>, ["~> 1.17"])
@@ -96,7 +108,7 @@ Gem::Specification.new do |s|
96
108
  s.add_development_dependency(%q<prawn>, ["= 0.8.4"])
97
109
  s.add_development_dependency(%q<httpthumbnailer>, [">= 0"])
98
110
  else
99
- s.add_dependency(%q<unicorn-cuba-base>, ["~> 1.1.1"])
111
+ s.add_dependency(%q<unicorn-cuba-base>, ["~> 1.1.2"])
100
112
  s.add_dependency(%q<httpthumbnailer-client>, ["~> 1.1.1"])
101
113
  s.add_dependency(%q<aws-sdk>, ["~> 1.10"])
102
114
  s.add_dependency(%q<mime-types>, ["~> 1.17"])
@@ -112,7 +124,7 @@ Gem::Specification.new do |s|
112
124
  s.add_dependency(%q<httpthumbnailer>, [">= 0"])
113
125
  end
114
126
  else
115
- s.add_dependency(%q<unicorn-cuba-base>, ["~> 1.1.1"])
127
+ s.add_dependency(%q<unicorn-cuba-base>, ["~> 1.1.2"])
116
128
  s.add_dependency(%q<httpthumbnailer-client>, ["~> 1.1.1"])
117
129
  s.add_dependency(%q<aws-sdk>, ["~> 1.10"])
118
130
  s.add_dependency(%q<mime-types>, ["~> 1.17"])
@@ -66,12 +66,11 @@ module Configuration
66
66
  request_state[name] = request_state.generate_meta_variable(name) or raise VariableNotDefinedError.new(name)
67
67
  end
68
68
 
69
- merge! query_string
70
69
  self[:path] = path
71
70
  merge! matches
72
71
  self[:query_string_options] = query_string.sort.map{|kv| kv.join(':')}.join(',')
73
72
 
74
- log.debug "processing request with body length: #{body.bytesize} bytes and variables: #{self} "
73
+ log.debug "processing request with body length: #{body.bytesize} bytes and variables: #{map{|k,v| "#{k}: '#{v}'"}.join(', ')}"
75
74
 
76
75
  @body = body
77
76
  @images = Images.new(memory_limit)
@@ -177,14 +176,6 @@ module Configuration
177
176
  end
178
177
  end
179
178
 
180
- class OutputOK
181
- def realize(request_state)
182
- request_state.output do
183
- write_plain 200, 'OK'
184
- end
185
- end
186
- end
187
-
188
179
  class InclusionMatcher
189
180
  def initialize(value, template)
190
181
  @value = value
@@ -259,13 +250,28 @@ module Configuration
259
250
  end
260
251
 
261
252
  class Matcher
262
- def initialize(name, &matcher)
263
- @name = name
253
+ def initialize(names, debug_type = '', debug_value = '', &matcher)
254
+ @names = names
264
255
  @matcher = matcher
256
+ @debug_type = debug_type
257
+ @debug_value = case debug_value
258
+ when Regexp
259
+ "/#{debug_value.source}/"
260
+ else
261
+ debug_value.inspect
262
+ end
265
263
  end
266
264
 
267
- attr_reader :name
265
+ attr_reader :names
268
266
  attr_reader :matcher
267
+
268
+ def to_s
269
+ if @names.empty?
270
+ "#{@debug_type}(#{@debug_value})"
271
+ else
272
+ "#{@debug_type}(#{@names.join(',')} => #{@debug_value})"
273
+ end
274
+ end
269
275
  end
270
276
 
271
277
  class Handler < Scope
@@ -280,7 +286,7 @@ module Configuration
280
286
  end
281
287
 
282
288
  def self.parse(configuration, node)
283
- handler_configuration =
289
+ handler_configuration =
284
290
  Struct.new(
285
291
  :global,
286
292
  :http_method,
@@ -295,45 +301,64 @@ module Configuration
295
301
  handler_configuration.http_method = node.name
296
302
  handler_configuration.uri_matchers = node.values.map do |matcher|
297
303
  case matcher
298
- # URI component matchers
304
+ # URI matchers
299
305
  when %r{^:([^/]+)/(.*)/$} # :foobar/.*/
300
- name = $1
301
- regexp = $2
302
- Matcher.new(name.to_sym) do
303
- Regexp.new("(#{regexp})")
306
+ name = $1.to_sym
307
+ _regexp = Regexp.new($2)
308
+ regexp = Regexp.new("(#{$2})")
309
+ Matcher.new([name], 'Regexp', _regexp) do
310
+ regexp
311
+ end
312
+ when %r{^/(.*)/$} # /.*/
313
+ regexp = $1
314
+ _regexp = Regexp.new($1)
315
+ names = Regexp.new($1).names.map{|n| n.to_sym}
316
+ Matcher.new(names, 'Regexp', _regexp) do
317
+ -> {
318
+ matchdata = env["PATH_INFO"].match(/\A\/(?<_match_>#{regexp})(?<_tail_>(?:\/|\z))/)
319
+
320
+ next false unless matchdata
321
+
322
+ path, *vars = matchdata.captures
323
+
324
+ env["SCRIPT_NAME"] += "/#{path}"
325
+ env["PATH_INFO"] = "#{vars.pop}#{matchdata.post_match}"
326
+
327
+ captures.push(*vars)
328
+ }
304
329
  end
305
330
  when /^:(.+)\?(.*)$/ # :foo?bar
306
331
  name = $1.to_sym
307
332
  default = $2
308
- Matcher.new(name) do
333
+ Matcher.new([name], 'SegmentDefault', "<segment>|#{default}") do
309
334
  ->{match(name) || captures.push(default)}
310
335
  end
311
336
  when /^:(.+)$/ # :foobar
312
337
  name = $1.to_sym
313
- Matcher.new(name) do
338
+ Matcher.new([name], 'Segment', '<segment>') do
314
339
  name
315
340
  end
316
341
  # Query string matchers
317
342
  when /^\&([^=]+)=(.+)$/# ?foo=bar
318
- name = $1
343
+ name = $1.to_sym
319
344
  value = $2
320
- Matcher.new(nil) do
321
- ->{req[name] && req[name] == value}
345
+ Matcher.new([name], 'QueryKeyValue', "#{name}=#{value}") do
346
+ ->{req[name] && req[name] == value && captures.push(req[name])}
322
347
  end
323
348
  when /^\&:(.+)\?(.*)$/# &:foo?bar
324
- name = $1
349
+ name = $1.to_sym
325
350
  default = $2
326
- Matcher.new(name.to_sym) do
351
+ Matcher.new([name], 'QueryKeyDefault', "#{name}=<key>|#{default}") do
327
352
  ->{captures.push(req[name] || default)}
328
353
  end
329
354
  when /^\&:(.+)$/# &:foo
330
- name = $1
331
- Matcher.new(name.to_sym) do
355
+ name = $1.to_sym
356
+ Matcher.new([name], 'QueryKey', "#{name}=<key>") do
332
357
  ->{req[name] && captures.push(req[name])}
333
358
  end
334
- # String URI component matcher
359
+ # Literal URI segment matcher
335
360
  else # foobar
336
- Matcher.new(nil) do
361
+ Matcher.new([], "Literal", matcher) do
337
362
  Regexp.escape(matcher)
338
363
  end
339
364
  end
@@ -1,4 +1,5 @@
1
1
  require 'httpimagestore/configuration/handler'
2
+ require 'httpimagestore/ruby_string_template'
2
3
 
3
4
  module Configuration
4
5
  class StorePathNotSetForImage < ConfigurationError
@@ -13,6 +14,53 @@ module Configuration
13
14
  end
14
15
  end
15
16
 
17
+ class OutputText
18
+ def self.match(node)
19
+ node.name == 'output_text'
20
+ end
21
+
22
+ def self.parse(configuration, node)
23
+ configuration.output and raise StatementCollisionError.new(node, 'output')
24
+ text = node.grab_values('text').first
25
+ status, cache_control = *node.grab_attributes('status', 'cache-control')
26
+ configuration.output = OutputText.new(text, status || 200, cache_control)
27
+ end
28
+
29
+ def initialize(text, status, cache_control)
30
+ @text = RubyStringTemplate.new(text || fail("no text?!"))
31
+ @status = status || 200
32
+ @cache_control = cache_control
33
+ end
34
+
35
+ def realize(request_state)
36
+ # make sure variables are available in request context
37
+ status = @status
38
+ text = @text.render(request_state)
39
+ cache_control = @cache_control
40
+ request_state.output do
41
+ res['Cache-Control'] = cache_control if cache_control
42
+ write_plain status.to_i, text.to_s
43
+ end
44
+ end
45
+ end
46
+
47
+ class OutputOK < OutputText
48
+ def self.match(node)
49
+ node.name == 'output_ok'
50
+ end
51
+
52
+ def self.parse(configuration, node)
53
+ configuration.output and raise StatementCollisionError.new(node, 'output')
54
+ cache_control = node.grab_attributes('cache-control').first
55
+ configuration.output = OutputOK.new(cache_control)
56
+ end
57
+
58
+ def initialize(cache_control = nil)
59
+ super 'OK', 200, cache_control
60
+ end
61
+ end
62
+ Handler::register_node_parser OutputText
63
+
16
64
  class OutputMultiBase
17
65
  class ImageName < String
18
66
  include ConditionalInclusion
@@ -39,7 +87,8 @@ module Configuration
39
87
  @names = names
40
88
  end
41
89
  end
42
-
90
+ Handler::register_node_parser OutputOK
91
+
43
92
  class OutputImage
44
93
  include ClassLogging
45
94
 
@@ -61,7 +110,7 @@ module Configuration
61
110
 
62
111
  def realize(request_state)
63
112
  image = request_state.images[@name]
64
- mime_type =
113
+ mime_type =
65
114
  if image.mime_type
66
115
  image.mime_type
67
116
  else
@@ -28,10 +28,10 @@ describe Configuration do
28
28
  subject.handlers.length.should == 3
29
29
 
30
30
  subject.handlers[0].http_method.should == 'get'
31
- subject.handlers[0].uri_matchers.map{|m| m.name}.should == [nil, nil, :operation, :width, :height, :options]
31
+ subject.handlers[0].uri_matchers.map{|m| m.names}.flatten.should == [:operation, :width, :height, :options]
32
32
 
33
33
  subject.handlers[1].http_method.should == 'put'
34
- subject.handlers[1].uri_matchers.map{|m| m.name}.should == [nil, nil, :test]
34
+ subject.handlers[1].uri_matchers.map{|m| m.names}.flatten.should == [:test]
35
35
 
36
36
  subject.handlers[2].http_method.should == 'post'
37
37
  subject.handlers[2].uri_matchers.should be_empty
@@ -62,11 +62,6 @@ describe Configuration do
62
62
  subject[:path].should == '/hello/world.jpg'
63
63
  end
64
64
 
65
- it 'should provide query string params' do
66
- subject[:width].should == '123'
67
- subject[:height].should == '321'
68
- end
69
-
70
65
  it 'should provide matches' do
71
66
  subject[:operation].should == 'pad'
72
67
  end
@@ -149,7 +144,7 @@ describe Configuration do
149
144
  subject[:input_digest]
150
145
  }.to raise_error Configuration::NoRequestBodyToGenerateMetaVariableError, %q{need not empty request body to generate value for 'input_digest'}
151
146
  end
152
-
147
+
153
148
  it 'should raise ImageNotLoadedError when asking for image related variable of not loaded image' do
154
149
  expect {
155
150
  subject.with_locals(image_name: 'abc')[:image_mime_extension]
@@ -202,7 +197,7 @@ describe Configuration do
202
197
  limit.borrow 1
203
198
  request_state.images['test'] = Configuration::Image.new('x')
204
199
  limit.limit.should == 1
205
-
200
+
206
201
  limit.borrow 1
207
202
  limit.limit.should == 0
208
203
  request_state.images['test'] = Configuration::Image.new('x')
@@ -255,19 +250,6 @@ describe Configuration do
255
250
  subject.handlers[1].output.should be_a Configuration::OutputOK
256
251
  subject.handlers[2].output.should be_a Configuration::OutputOK
257
252
  end
258
-
259
- describe Configuration::OutputOK do
260
- it 'should output 200 with OK text/plain message when realized' do
261
- state = Configuration::RequestState.new('abc')
262
- subject.handlers[2].output.realize(state)
263
-
264
- env = CubaResponseEnv.new
265
- env.instance_eval &state.output_callback
266
- env.res.status.should == 200
267
- env.res.data.should == "OK\r\n"
268
- env.res['Content-Type'].should == 'text/plain'
269
- end
270
- end
271
253
  end
272
254
  end
273
255
  end
@@ -17,6 +17,91 @@ describe Configuration do
17
17
  CubaResponseEnv.new
18
18
  end
19
19
 
20
+ describe Configuration::OutputText do
21
+ subject do
22
+ Configuration.read(<<-'EOF')
23
+ get "test" {
24
+ output_text "hello world"
25
+ }
26
+ get "test" {
27
+ output_text "bad stuff" status=500
28
+ }
29
+ get "test" {
30
+ output_text "welcome" cache-control="public"
31
+ }
32
+ get "test" {
33
+ output_text "test1: #{test1} test2: #{test2}"
34
+ }
35
+ EOF
36
+ end
37
+
38
+ it 'should output hello world with default 200 status' do
39
+ subject.handlers[0].output.realize(state)
40
+ env = CubaResponseEnv.new
41
+ env.instance_eval &state.output_callback
42
+ env.res.status.should == 200
43
+ env.res.data.should == "hello world\r\n"
44
+ env.res['Content-Type'].should == 'text/plain'
45
+ env.res['Cache-Control'].should be_nil
46
+ end
47
+
48
+ it 'should output bad stuff with 500 status' do
49
+ subject.handlers[1].output.realize(state)
50
+ env = CubaResponseEnv.new
51
+ env.instance_eval &state.output_callback
52
+ env.res.status.should == 500
53
+ env.res.data.should == "bad stuff\r\n"
54
+ env.res['Content-Type'].should == 'text/plain'
55
+ env.res['Cache-Control'].should be_nil
56
+ end
57
+
58
+ it 'should output welcome with public cache control' do
59
+ subject.handlers[2].output.realize(state)
60
+ env = CubaResponseEnv.new
61
+ env.instance_eval &state.output_callback
62
+ env.res.status.should == 200
63
+ env.res.data.should == "welcome\r\n"
64
+ env.res['Content-Type'].should == 'text/plain'
65
+ env.res['Cache-Control'].should == 'public'
66
+ end
67
+
68
+ it 'should output text interpolated with variable values' do
69
+ state = Configuration::RequestState.new
70
+ state[:test1] = 'abc'
71
+ state[:test2] = 'xyz'
72
+
73
+ subject.handlers[3].output.realize(state)
74
+ env = CubaResponseEnv.new
75
+ env.instance_eval &state.output_callback
76
+ env.res.data.should == "test1: abc test2: xyz\r\n"
77
+ end
78
+ end
79
+
80
+ describe Configuration::OutputOK do
81
+ subject do
82
+ Configuration.read(<<-EOF)
83
+ put "test" {
84
+ output_ok
85
+ }
86
+ EOF
87
+ end
88
+
89
+ before :each do
90
+ subject.handlers[0].sources[0].realize(state)
91
+ end
92
+
93
+ it 'should output 200 with OK text/plain message when realized' do
94
+ state = Configuration::RequestState.new('abc')
95
+ subject.handlers[0].output.realize(state)
96
+
97
+ env = CubaResponseEnv.new
98
+ env.instance_eval &state.output_callback
99
+ env.res.status.should == 200
100
+ env.res.data.should == "OK\r\n"
101
+ env.res['Content-Type'].should == 'text/plain'
102
+ end
103
+ end
104
+
20
105
  describe Configuration::OutputImage do
21
106
  subject do
22
107
  Configuration.read(<<-EOF)
@@ -270,7 +355,7 @@ describe Configuration do
270
355
  env.res['Content-Type'].should == 'text/uri-list'
271
356
  env.res.data.should == "file://test.out\r\nfile://test.out2\r\n"
272
357
  end
273
-
358
+
274
359
  describe 'conditional inclusion support' do
275
360
  let :state do
276
361
  Configuration::RequestState.new('abc', list: 'input,image2')
@@ -335,4 +420,3 @@ describe Configuration do
335
420
  end
336
421
  end
337
422
  end
338
-