httpimagestore 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
-