httpthumbnailer 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
  ruby "1.9.3"
3
3
 
4
- gem "unicorn-cuba-base", "~> 1.0"
4
+ gem "unicorn-cuba-base", "~> 1.1"
5
5
  gem "rmagick", "~> 2"
6
6
 
7
7
  # Add dependencies to develop your gem here.
data/Gemfile.lock CHANGED
@@ -33,7 +33,7 @@ GEM
33
33
  rake
34
34
  rdoc
35
35
  json (1.7.7)
36
- kgio (2.8.0)
36
+ kgio (2.8.1)
37
37
  mime-types (1.23)
38
38
  multi_json (1.7.3)
39
39
  multipart-parser (0.1.1)
@@ -41,7 +41,7 @@ GEM
41
41
  rack (1.5.2)
42
42
  rack-test (0.6.2)
43
43
  rack (>= 1.0)
44
- raindrops (0.11.0)
44
+ raindrops (0.12.0)
45
45
  rake (10.0.4)
46
46
  rdoc (3.12.2)
47
47
  json (~> 1.4)
@@ -65,7 +65,7 @@ GEM
65
65
  kgio (~> 2.6)
66
66
  rack
67
67
  raindrops (~> 0.7)
68
- unicorn-cuba-base (1.0.0)
68
+ unicorn-cuba-base (1.1.0)
69
69
  cli (~> 1.1.0)
70
70
  cuba (~> 3.0)
71
71
  facter (~> 1.6.11)
@@ -90,4 +90,4 @@ DEPENDENCIES
90
90
  rmagick (~> 2)
91
91
  rspec (~> 2.13)
92
92
  rspec-mocks (~> 2.13)
93
- unicorn-cuba-base (~> 1.0)
93
+ unicorn-cuba-base (~> 1.1)
data/README.md CHANGED
@@ -7,6 +7,7 @@ It is using [ImageMagick](http://www.imagemagick.org) or [GraphicsMagick](http:/
7
7
  ## Features
8
8
 
9
9
  * thumbnailing images with different aspect ratio keeping methods
10
+ * identification of image foramt and size
10
11
  * support of many input and output formats
11
12
  * efficient API for generating multiple thumbnails from single input image with just one request
12
13
  * many image scaling and loading performance optimizations
@@ -14,6 +15,16 @@ It is using [ImageMagick](http://www.imagemagick.org) or [GraphicsMagick](http:/
14
15
  * memory limits and disk memory offloading support
15
16
  * based on [Unicorn HTTP server](http://unicorn.bogomips.org) with UNIX socket communication support
16
17
 
18
+ ## Changelog
19
+
20
+ ### 1.1.0
21
+
22
+ * added identification API for image mime type and size identification
23
+ * stripping user meta data from input image further reducing output image size
24
+ * providing image size headers for input image and all generated thumbnails
25
+ * X-Input-Image-Content-Type header is now deprecated in favour of X-Input-Image-Content-Type
26
+ * not using [ImageMagick](http://www.imagemagick.org) for input image mime type resolution since it is accessing disk and behaves very inefficiently
27
+
17
28
  ## Installing
18
29
 
19
30
  You will need the following system packages installed: `imagemagick`, `pkg-config` and `make`.
@@ -140,6 +151,18 @@ HTTP Thumbnailer will generate 3 thumbnails:
140
151
 
141
152
  For detailed information about the API see [cucumber features](http://github.com/jpastuszek/httpthumbnailer/blob/master/features/thumbnails.feature).
142
153
 
154
+ #### Identification API
155
+
156
+ You can identify image mime type, width and height with **PUT** request to URI in format:
157
+
158
+ /identify
159
+
160
+ Server will respond with **JSON** containing **contentType**, **width** and **height** fields:
161
+
162
+ {"mimeType":"image/jpeg","width":1239,"height":1750}
163
+
164
+ For detailed information about the API see [cucumber features](http://github.com/jpastuszek/httpthumbnailer/blob/master/features/identify.feature).
165
+
143
166
  ### Ruby API client
144
167
 
145
168
  To make it easier to use this server [httpthumbnailer-client](http://github.com/jpastuszek/httpthumbnailer-client) gem provides useful class.
@@ -197,7 +220,8 @@ total_errors: 1
197
220
  calling: 1
198
221
  writing: 0
199
222
  total_images_loaded: 115
200
- total_images_prescaled: 30
223
+ total_images_reloaded: 30
224
+ total_images_downscaled: 30
201
225
  total_thumbnails_created: 147
202
226
  images_loaded: 0
203
227
  max_images_loaded: 3
@@ -223,6 +247,11 @@ $ curl 127.0.0.1:3100/stats/total_write_multipart
223
247
 
224
248
  [HTTP Image Store](https://github.com/jpastuszek/httpimagestore) service is configurable image storage and processing HTTP API server that uses this service as thumbnailing backend.
225
249
 
250
+ ## Known Issues
251
+
252
+ * When 413 error is reported due to memory limit exhaustion the disk offloading won't work any more and only requests that can fit in the memory can be processed without getting 413 - this is due to a bug in ImageMagick v6.8.6-8 (2013-08-06 6.8.6-8) or less
253
+ * Mime type generated for images may not be the official mime type assigned for given format; please let me know of any inconsistencies or send a patch to get better output in efficient way
254
+
226
255
  ## Contributing to HTTP Thumbnailer
227
256
 
228
257
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -0,0 +1,31 @@
1
+ Feature: Identify API endpoint
2
+ Identify API allows for image identification.
3
+
4
+ Background:
5
+ Given httpthumbnailer server is running at http://localhost:3100/
6
+
7
+ @identify
8
+ Scenario: Identifying of image mime type
9
+ Given test.jpg file content as request body
10
+ When I do PUT request http://localhost:3100/identify
11
+ Then response status should be 200
12
+ And response content type should be application/json
13
+ And response body should be JSON encoded
14
+ And response JSON should contain key mimeType of value image/jpeg
15
+ Given test.png file content as request body
16
+ When I do PUT request http://localhost:3100/identify
17
+ Then response status should be 200
18
+ And response content type should be application/json
19
+ And response body should be JSON encoded
20
+ And response JSON should contain key mimeType of value image/png
21
+
22
+ @identify
23
+ Scenario: Identifying of image width and height
24
+ Given test-large.jpg file content as request body
25
+ When I do PUT request http://localhost:3100/identify
26
+ Then response status should be 200
27
+ And response content type should be application/json
28
+ And response body should be JSON encoded
29
+ And response JSON should contain key width of integer value 9911
30
+ And response JSON should contain key height of integer value 14000
31
+
@@ -16,7 +16,7 @@ When /I save response body/ do
16
16
  @saved_response_body = @response.body
17
17
  end
18
18
 
19
- Then /(.*) header should be (.*)/ do |header, value|
19
+ Then /^([^ ]+) header should be (.*)/ do |header, value|
20
20
  @response.header[header].should_not be_empty
21
21
  @response.header[header].first.should == value
22
22
  end
@@ -69,23 +69,15 @@ Then /response mime type should be (.*)/ do |mime_type|
69
69
  step "response content type should be #{mime_type}"
70
70
  end
71
71
 
72
- Then /(.*) part mime type should be (.*)/ do |part, mime|
73
- @response_multipart[part_no(part)].headers['content-type'].should == mime
72
+ Then /^([^ ]+) part (.*) header should be (.*)/ do |part, header, value|
73
+ @response_multipart[part_no(part)].headers[header.downcase].should == value
74
74
  end
75
75
 
76
- Then /(.*) part content type should be (.*)/ do |part, content_type|
77
- @response_multipart[part_no(part)].headers['content-type'].should == content_type
78
- end
79
-
80
- Then /(.*) part status should be (.*)/ do |part, status|
81
- @response_multipart[part_no(part)].headers['status'].should == status
82
- end
83
-
84
- Then /(.*) part body should be CRLF endend lines$/ do |part, body|
76
+ Then /^([^ ]+) part body should be CRLF endend lines$/ do |part, body|
85
77
  @response_multipart[part_no(part)].body.should == body.gsub("\n", "\r\n")
86
78
  end
87
79
 
88
- Then /(.*) part body should be CRLF endend lines like$/ do |part, body|
80
+ Then /^([^ ]+) part body should be CRLF endend lines like$/ do |part, body|
89
81
  pbody = @response_multipart[part_no(part)].body
90
82
  pbody.should match(body)
91
83
  end
@@ -141,7 +133,6 @@ And /that image should be (.*) bit image/ do |bits|
141
133
  @image.depth.should == bits.to_i
142
134
  end
143
135
 
144
-
145
136
  And /there should be no leaked images/ do
146
137
  Integer(http_client.get_content("http://localhost:3100/stats/images_loaded").strip).should == 0
147
138
  end
@@ -150,3 +141,15 @@ And /there should be maximum (.*) images loaded during single request/ do |max|
150
141
  Integer(http_client.get_content("http://localhost:3100/stats/max_images_loaded").strip).should <= max.to_i
151
142
  end
152
143
 
144
+ And /response body should be JSON encoded/ do
145
+ @json = JSON.load(@response.body)
146
+ end
147
+
148
+ And /response JSON should contain key (.*) of value (.*)/ do |key, value|
149
+ @json[key].should == value
150
+ end
151
+
152
+ And /response JSON should contain key (.*) of integer value (.*)/ do |key, value|
153
+ @json[key].should == value.to_i
154
+ end
155
+
@@ -16,6 +16,7 @@ require 'httpclient'
16
16
  require "open3"
17
17
  require "thread"
18
18
  require 'RMagick'
19
+ require 'json'
19
20
 
20
21
  def gem_dir
21
22
  Pathname.new(__FILE__).dirname + '..' + '..'
@@ -9,11 +9,20 @@ Feature: Generating single thumbnail with PUT request
9
9
  @test
10
10
  Scenario: Single thumbnail
11
11
  Given test.jpg file content as request body
12
- When I do PUT request http://localhost:3100/thumbnail/crop,16,16,png
12
+ When I do PUT request http://localhost:3100/thumbnail/crop,16,24,png
13
13
  Then response status should be 200
14
- Then response should contain PNG image of size 16x16
14
+ Then response should contain PNG image of size 16x24
15
15
  And that image should be 8 bit image
16
- And response mime type should be image/png
16
+ And Content-Type header should be image/png
17
+ And X-Image-Width header should be 16
18
+ And X-Image-Height header should be 24
19
+ When I do PUT request http://localhost:3100/thumbnail/crop,16,24,jpeg
20
+ Then response status should be 200
21
+ Then response should contain JPEG image of size 16x24
22
+ And that image should be 8 bit image
23
+ And Content-Type header should be image/jpeg
24
+ And X-Image-Width header should be 16
25
+ And X-Image-Height header should be 24
17
26
 
18
27
  @transparent
19
28
  Scenario: Transparent image to JPEG handling - default background color white
@@ -228,14 +237,22 @@ Feature: Generating single thumbnail with PUT request
228
237
  Then saved response body will be smaller than response body
229
238
  Then response mime type should be image/png
230
239
 
231
- @hint
240
+ @hint @input
232
241
  Scenario: Hint on input image mime type
233
242
  Given test.jpg file content as request body
234
- When I do PUT request http://localhost:3100/thumbnails/crop,16,16,png
243
+ When I do PUT request http://localhost:3100/thumbnail/crop,16,16,png
235
244
  Then response status should be 200
236
- And X-Input-Image-Content-Type header should be image/jpeg
245
+ And X-Input-Image-Mime-Type header should be image/jpeg
237
246
  Given test.png file content as request body
238
- When I do PUT request http://localhost:3100/thumbnails/crop,16,16,png
247
+ When I do PUT request http://localhost:3100/thumbnail/crop,16,16,png
248
+ Then response status should be 200
249
+ And X-Input-Image-Mime-Type header should be image/png
250
+
251
+ @hint @input
252
+ Scenario: Hint on input image size
253
+ Given test-large.jpg file content as request body
254
+ When I do PUT request http://localhost:3100/thumbnail/crop,16,16,png
239
255
  Then response status should be 200
240
- And X-Input-Image-Content-Type header should be image/png
256
+ And X-Input-Image-Width header should be 9911
257
+ And X-Input-Image-Height header should be 14000
241
258
 
@@ -9,12 +9,14 @@ Feature: Generating set of thumbnails with single PUT request
9
9
  @multipart
10
10
  Scenario: Single thumbnail
11
11
  Given test.jpg file content as request body
12
- When I do PUT request http://localhost:3100/thumbnails/crop,16,16,png
12
+ When I do PUT request http://localhost:3100/thumbnails/crop,16,24,png
13
13
  Then response status should be 200
14
14
  And I should get multipart response
15
- Then first part should contain PNG image of size 16x16
15
+ Then first part should contain PNG image of size 16x24
16
16
  And that image should be 8 bit image
17
- And first part mime type should be image/png
17
+ And first part Content-Type header should be image/png
18
+ And first part X-Image-Width header should be 16
19
+ And first part X-Image-Height header should be 24
18
20
 
19
21
  @multipart
20
22
  Scenario: Multiple thumbnails
@@ -23,11 +25,17 @@ Feature: Generating set of thumbnails with single PUT request
23
25
  Then response status should be 200
24
26
  And I should get multipart response
25
27
  Then first part should contain PNG image of size 16x16
26
- And first part mime type should be image/png
28
+ And first part Content-Type header should be image/png
29
+ And first part X-Image-Width header should be 16
30
+ And first part X-Image-Height header should be 16
27
31
  Then second part should contain JPEG image of size 4x8
28
- And second part mime type should be image/jpeg
32
+ And second part Content-Type header should be image/jpeg
33
+ And second part X-Image-Width header should be 4
34
+ And second part X-Image-Height header should be 8
29
35
  Then third part should contain JPEG image of size 16x32
30
- And third part mime type should be image/jpeg
36
+ And third part Content-Type header should be image/jpeg
37
+ And third part X-Image-Width header should be 16
38
+ And third part X-Image-Height header should be 32
31
39
 
32
40
  @input_size
33
41
  Scenario: Thumbnails of width or height input should have input image width or height
@@ -45,9 +53,9 @@ Feature: Generating set of thumbnails with single PUT request
45
53
  When I do PUT request http://localhost:3100/thumbnails/crop,0,0,png/fit,0,0,jpeg/pad,0,0,jpeg
46
54
  Then response status should be 200
47
55
  And I should get multipart response
48
- And first part content type should be text/plain
49
- And second part content type should be text/plain
50
- And third part content type should be text/plain
56
+ And first part Content-Type header should be text/plain
57
+ And second part Content-Type header should be text/plain
58
+ And third part Content-Type header should be text/plain
51
59
 
52
60
  @error_handling
53
61
  Scenario: Reporitng of bad thumbanil spec format - bad dimension value
@@ -88,12 +96,12 @@ Feature: Generating set of thumbnails with single PUT request
88
96
  When I do PUT request http://localhost:3100/thumbnails/crop,4,4,png/blah,128,128,png
89
97
  Then response status should be 200
90
98
  And I should get multipart response
91
- And second part content type should be text/plain
99
+ And second part Content-Type header should be text/plain
92
100
  And second part body should be CRLF endend lines
93
101
  """
94
102
  thumbnail method 'blah' is not supported
95
103
  """
96
- And second part status should be 400
104
+ And second part Status header should be 400
97
105
 
98
106
  @error_handling
99
107
  Scenario: Reporitng of image thumbnailing errors
@@ -102,15 +110,15 @@ Feature: Generating set of thumbnails with single PUT request
102
110
  Then response status should be 200
103
111
  And I should get multipart response
104
112
  Then first part should contain PNG image of size 16x16
105
- And first part mime type should be image/png
106
- And second part content type should be text/plain
113
+ And first part Content-Type header should be image/png
114
+ And second part Content-Type header should be text/plain
107
115
  And second part body should be CRLF endend lines
108
116
  """
109
117
  at least one image dimension is zero: 0x0
110
118
  """
111
- And second part status should be 400
119
+ And second part Status header should be 400
112
120
  Then third part should contain JPEG image of size 16x32
113
- And third part mime type should be image/jpeg
121
+ And third part Content-Type header should be image/jpeg
114
122
 
115
123
  @resources
116
124
  Scenario: Memory limits exhausted while thumbnailing
@@ -119,24 +127,32 @@ Feature: Generating set of thumbnails with single PUT request
119
127
  Then response status should be 200
120
128
  And I should get multipart response
121
129
  Then first part should contain PNG image of size 16x16
122
- And first part mime type should be image/png
123
- And second part content type should be text/plain
130
+ And first part Content-Type header should be image/png
131
+ And second part Content-Type header should be text/plain
124
132
  And second part body should be CRLF endend lines like
125
133
  """
126
134
  image too large: cache resources exhausted
127
135
  """
128
- And second part status should be 413
136
+ And second part Status header should be 413
129
137
  Then third part should contain JPEG image of size 16x32
130
- And third part mime type should be image/jpeg
138
+ And third part Content-Type header should be image/jpeg
131
139
 
132
- @hint
140
+ @hint @input
133
141
  Scenario: Hint on input image mime type
134
142
  Given test.jpg file content as request body
135
143
  When I do PUT request http://localhost:3100/thumbnails/crop,16,16,png
136
144
  Then response status should be 200
137
- And X-Input-Image-Content-Type header should be image/jpeg
145
+ And X-Input-Image-Mime-Type header should be image/jpeg
138
146
  Given test.png file content as request body
139
147
  When I do PUT request http://localhost:3100/thumbnails/crop,16,16,png
140
148
  Then response status should be 200
141
- And X-Input-Image-Content-Type header should be image/png
149
+ And X-Input-Image-Mime-Type header should be image/png
150
+
151
+ @hint @input
152
+ Scenario: Hint on input image size
153
+ Given test-large.jpg file content as request body
154
+ When I do PUT request http://localhost:3100/thumbnails/crop,16,16,png
155
+ Then response status should be 200
156
+ And X-Input-Image-Width header should be 9911
157
+ And X-Input-Image-Height header should be 14000
142
158
 
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "httpthumbnailer"
8
- s.version = "1.0.0"
8
+ s.version = "1.1.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-07-16"
12
+ s.date = "2013-09-11"
13
13
  s.description = "Provides HTTP API for thumbnailing images"
14
14
  s.email = "jpastuszek@gmail.com"
15
15
  s.executables = ["httpthumbnailer"]
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  "VERSION",
29
29
  "bin/httpthumbnailer",
30
30
  "features/httpthumbnailer.feature",
31
+ "features/identify.feature",
31
32
  "features/step_definitions/httpthumbnailer_steps.rb",
32
33
  "features/support/env.rb",
33
34
  "features/support/test-large.jpg",
@@ -45,6 +46,8 @@ Gem::Specification.new do |s|
45
46
  "load_test/extralarge.jpg",
46
47
  "load_test/large.jpg",
47
48
  "load_test/large.png",
49
+ "load_test/load_test-374846090-1.1.0-rc1-identify-only.csv",
50
+ "load_test/load_test-374846090-1.1.0-rc1.csv",
48
51
  "load_test/load_test-cd9679c.csv",
49
52
  "load_test/load_test-v0.3.1.csv",
50
53
  "load_test/load_test.jmx",
@@ -73,7 +76,7 @@ Gem::Specification.new do |s|
73
76
  s.specification_version = 3
74
77
 
75
78
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
76
- s.add_runtime_dependency(%q<unicorn-cuba-base>, ["~> 1.0"])
79
+ s.add_runtime_dependency(%q<unicorn-cuba-base>, ["~> 1.1"])
77
80
  s.add_runtime_dependency(%q<rmagick>, ["~> 2"])
78
81
  s.add_development_dependency(%q<rspec>, ["~> 2.13"])
79
82
  s.add_development_dependency(%q<rspec-mocks>, ["~> 2.13"])
@@ -85,7 +88,7 @@ Gem::Specification.new do |s|
85
88
  s.add_development_dependency(%q<multipart-parser>, ["~> 0.1.1"])
86
89
  s.add_development_dependency(%q<daemon>, ["~> 1.1"])
87
90
  else
88
- s.add_dependency(%q<unicorn-cuba-base>, ["~> 1.0"])
91
+ s.add_dependency(%q<unicorn-cuba-base>, ["~> 1.1"])
89
92
  s.add_dependency(%q<rmagick>, ["~> 2"])
90
93
  s.add_dependency(%q<rspec>, ["~> 2.13"])
91
94
  s.add_dependency(%q<rspec-mocks>, ["~> 2.13"])
@@ -98,7 +101,7 @@ Gem::Specification.new do |s|
98
101
  s.add_dependency(%q<daemon>, ["~> 1.1"])
99
102
  end
100
103
  else
101
- s.add_dependency(%q<unicorn-cuba-base>, ["~> 1.0"])
104
+ s.add_dependency(%q<unicorn-cuba-base>, ["~> 1.1"])
102
105
  s.add_dependency(%q<rmagick>, ["~> 2"])
103
106
  s.add_dependency(%q<rspec>, ["~> 2.13"])
104
107
  s.add_dependency(%q<rspec-mocks>, ["~> 2.13"])
@@ -1,9 +1,5 @@
1
1
  class ErrorReporter < Controler
2
2
  self.define do
3
- on error Rack::UnhandledRequest::UnhandledRequestError do |error|
4
- write_error 404, error
5
- end
6
-
7
3
  on error Plugin::Thumbnailer::UnsupportedMediaTypeError do |error|
8
4
  write_error 415, error
9
5
  end
@@ -23,16 +19,7 @@ class ErrorReporter < Controler
23
19
  write_error 400, error
24
20
  end
25
21
 
26
- on error StandardError do |error|
27
- log.error "unhandled error while processing request: #{env['REQUEST_METHOD']} #{env['SCRIPT_NAME']}[#{env["PATH_INFO"]}]", error
28
- log.debug {
29
- out = StringIO.new
30
- PP::pp(env, out, 200)
31
- "Request: \n" + out.string
32
- }
33
-
34
- write_error 500, error
35
- end
22
+ run DefaultErrorReporter
36
23
  end
37
24
  end
38
25
 
@@ -1,6 +1,29 @@
1
1
  require 'RMagick'
2
2
  require 'forwardable'
3
3
 
4
+ # ImageMagick Image.mime_type is absolutely bunkers! It goes over file system to look for some strange files WTF?!
5
+ # Also it cannot be used for thumbnails since they are not yet rendered to desired format
6
+ # Here is stupid implementaiton
7
+ module MetaData
8
+ def width
9
+ @image.columns
10
+ end
11
+
12
+ def height
13
+ @image.rows
14
+ end
15
+
16
+ def mime_type
17
+ #TODO: how do I do it better?
18
+ format = @format || @image.format
19
+ mime = case format
20
+ when 'JPG' then 'jpeg'
21
+ else format.downcase
22
+ end
23
+ "image/#{mime}"
24
+ end
25
+ end
26
+
4
27
  module Plugin
5
28
  module Thumbnailer
6
29
  class UnsupportedMethodError < ArgumentError
@@ -105,7 +128,18 @@ module Plugin
105
128
  end
106
129
  end
107
130
 
108
- def_delegators :@image, :destroy!, :destroyed?, :mime_type
131
+ def_delegators :@image, :destroy!, :destroyed?
132
+
133
+ include MetaData
134
+
135
+ # We use base values since it might have been loaded with size hint and prescaled
136
+ def width
137
+ @image.base_columns
138
+ end
139
+
140
+ def height
141
+ @image.base_rows
142
+ end
109
143
 
110
144
  # needs to be seen as @image when returned in replace block
111
145
  def equal?(image)
@@ -124,24 +158,16 @@ module Plugin
124
158
  end
125
159
 
126
160
  def data
127
- format = @format
128
- quality = @quality
129
- @image.to_blob do
130
- self.format = format
131
- self.quality = quality if quality
132
- end
133
- end
134
-
135
- def mime_type
136
- #@image.mime_type cannot be used since it is raw crated image
137
- #TODO: how do I do it better?
138
- mime = case @format
139
- when 'JPG' then 'jpeg'
140
- else @format.downcase
161
+ format = @format
162
+ quality = @quality
163
+ @image.to_blob do
164
+ self.format = format
165
+ self.quality = quality if quality
141
166
  end
142
- "image/#{mime}"
143
167
  end
144
168
 
169
+ include MetaData
170
+
145
171
  private
146
172
 
147
173
  def default_quality(format)
@@ -162,7 +188,8 @@ module Plugin
162
188
  extend Stats
163
189
  def_stats(
164
190
  :total_images_loaded,
165
- :total_images_prescaled,
191
+ :total_images_reloaded,
192
+ :total_images_downscaled,
166
193
  :total_thumbnails_created,
167
194
  :images_loaded,
168
195
  :max_images_loaded,
@@ -244,8 +271,8 @@ module Plugin
244
271
  end
245
272
 
246
273
  def load(io, options = {})
247
- mw = options['max-width']
248
- mh = options['max-height']
274
+ mw = options[:max_width]
275
+ mh = options[:max_height]
249
276
  if mw and mh
250
277
  mw = mw.to_i
251
278
  mh = mh.to_i
@@ -258,7 +285,7 @@ module Plugin
258
285
  old_memory_limit = nil
259
286
  borrowed_memory_limit = nil
260
287
  if options.member?(:limit_memory)
261
- borrowed_memory_limit = options[:limit_memory].borrow(options[:limit_memory].limit)
288
+ borrowed_memory_limit = options[:limit_memory].borrow(options[:limit_memory].limit, 'image magick')
262
289
  old_memory_limit = set_limit(:memory, borrowed_memory_limit)
263
290
  end
264
291
 
@@ -270,12 +297,13 @@ module Plugin
270
297
  end
271
298
 
272
299
  image = images.first
273
- if image.columns > image.base_columns or image.rows > image.base_rows
300
+ if image.columns > image.base_columns or image.rows > image.base_rows and not options[:no_reload]
274
301
  log.warn "input image got upscaled from: #{image.base_columns}x#{image.base_rows} to #{image.columns}x#{image.rows}: reloading without max size hint!"
275
302
  images.each do |other|
276
303
  other.destroy!
277
304
  end
278
305
  images = Magick::Image.from_blob(blob)
306
+ Service.stats.incr_total_images_reloaded
279
307
  end
280
308
  blob = nil
281
309
 
@@ -285,14 +313,22 @@ module Plugin
285
313
  end
286
314
  log.info "loaded image: #{image.inspect}"
287
315
  Service.stats.incr_total_images_loaded
316
+
317
+ # clean up the image
288
318
  image.strip!
319
+ image.properties do |key, value|
320
+ log.debug "deleting user propertie '#{key}'"
321
+ image[key] = nil
322
+ end
323
+
324
+ image
289
325
  end.replace do |image|
290
- if mw and mh
291
- f = image.find_prescale_factor(mw, mh)
326
+ if mw and mh and not options[:no_downscale]
327
+ f = image.find_downscale_factor(mw, mh)
292
328
  if f > 1
293
- image = image.prescale(f)
294
- log.info "prescaled image by factor of #{f}: #{image.inspect}"
295
- Service.stats.incr_total_images_prescaled
329
+ image = image.downscale(f)
330
+ log.info "downscaled image by factor of #{f}: #{image.inspect}"
331
+ Service.stats.incr_total_images_downscaled
296
332
  end
297
333
  end
298
334
  InputImage.new(image, @processing_methods)
@@ -303,7 +339,7 @@ module Plugin
303
339
  ensure
304
340
  if old_memory_limit
305
341
  set_limit(:memory, old_memory_limit)
306
- options[:limit_memory].return(borrowed_memory_limit)
342
+ options[:limit_memory].return(borrowed_memory_limit, 'image magick')
307
343
  end
308
344
  end
309
345
  end
@@ -380,14 +416,14 @@ class Magick::Image
380
416
  end
381
417
  end
382
418
 
383
- def prescale(f)
419
+ def downscale(f)
384
420
  sample(columns / f, rows / f)
385
421
  end
386
422
 
387
- def find_prescale_factor(max_width, max_height, factor = 1)
423
+ def find_downscale_factor(max_width, max_height, factor = 1)
388
424
  new_factor = factor * 2
389
425
  if columns / new_factor > max_width * 2 and rows / new_factor > max_height * 2
390
- find_prescale_factor(max_width, max_height, factor * 2)
426
+ find_downscale_factor(max_width, max_height, factor * 2)
391
427
  else
392
428
  factor
393
429
  end
@@ -5,24 +5,29 @@ class Thumbnailer < Controler
5
5
  self.plugin Plugin::Thumbnailer
6
6
 
7
7
  self.define do
8
+ opts = {}
9
+ opts[:limit_memory] = memory_limit
10
+
8
11
  on put, 'thumbnail', /(.*)/ do |spec|
9
12
  spec = ThumbnailSpec.from_uri(spec)
10
13
  log.info "thumbnailing image to single spec: #{spec}"
11
14
 
12
- opts = {}
13
15
  if settings[:optimization]
14
- opts['max-width'] = spec.width if spec.width.is_a? Integer
15
- opts['max-height'] = spec.height if spec.height.is_a? Integer
16
+ opts[:max_width] = spec.width if spec.width.is_a? Integer
17
+ opts[:max_height] = spec.height if spec.height.is_a? Integer
16
18
  end
17
- opts[:limit_memory] = memory_limit
18
19
 
19
20
  thumbnailer.load(req.body, opts).use do |input_image|
20
21
  log.info "original image loaded: #{input_image.mime_type}"
21
- res["X-Input-Image-Content-Type"] = input_image.mime_type
22
22
 
23
23
  log.info "generating thumbnail: #{spec}"
24
24
  input_image.thumbnail(spec) do |image|
25
- write 200, image.mime_type, image.data
25
+ write 200, image.mime_type, image.data,
26
+ "X-Image-Width" => image.width,
27
+ "X-Image-Height" => image.height,
28
+ "X-Input-Image-Mime-Type" => input_image.mime_type,
29
+ "X-Input-Image-Width" => input_image.width,
30
+ "X-Input-Image-Height" => input_image.height
26
31
  end
27
32
  end
28
33
  end
@@ -31,22 +36,25 @@ class Thumbnailer < Controler
31
36
  thumbnail_specs = ThumbnailSpecs.from_uri(specs)
32
37
  log.info "thumbnailing image to multiple specs: #{thumbnail_specs.join(', ')}"
33
38
 
34
- opts = {}
35
39
  if settings[:optimization]
36
- opts['max-width'] = thumbnail_specs.max_width
37
- opts['max-height'] = thumbnail_specs.max_height
40
+ opts[:max_width] = thumbnail_specs.max_width
41
+ opts[:max_height] = thumbnail_specs.max_height
38
42
  end
39
- opts[:limit_memory] = memory_limit
40
43
 
41
44
  thumbnailer.load(req.body, opts).use do |input_image|
42
45
  log.info "original image loaded: #{input_image.mime_type}"
43
- write_preamble 200, "X-Input-Image-Content-Type" => input_image.mime_type
46
+ write_preamble 200,
47
+ "X-Input-Image-Mime-Type" => input_image.mime_type,
48
+ "X-Input-Image-Width" => input_image.width,
49
+ "X-Input-Image-Height" => input_image.height
44
50
 
45
51
  thumbnail_specs.each do |spec|
46
52
  log.info "generating thumbnail: #{spec}"
47
53
  begin
48
54
  input_image.thumbnail(spec) do |image|
49
- write_part image.mime_type, image.data
55
+ write_part image.mime_type, image.data,
56
+ "X-Image-Width" => image.width,
57
+ "X-Image-Height" => image.height
50
58
  end
51
59
  rescue => error
52
60
  case error
@@ -65,6 +73,30 @@ class Thumbnailer < Controler
65
73
  write_epilogue
66
74
  end
67
75
  end
76
+
77
+ on put, 'identify' do
78
+ log.info "identifying image"
79
+ # load as little of image as possible
80
+ if settings[:optimization]
81
+ opts[:max_width] = 2
82
+ opts[:max_height] = 2
83
+ end
84
+
85
+ # disable preprocessing since we don't need them here
86
+ opts[:no_reload] = true
87
+ opts[:no_downscale] = true
88
+
89
+ # RMagick of v2.13.2 does not use ImageMagick's PingBlob so we have to actually load the image
90
+ thumbnailer.load(req.body, opts).use do |input_image|
91
+ mime_type = input_image.mime_type
92
+ log.info "image loaded and identified as: #{mime_type}"
93
+ write_json 200, {
94
+ 'mimeType' => mime_type,
95
+ 'width' => input_image.width,
96
+ 'height' => input_image.height
97
+ }
98
+ end
99
+ end
68
100
  end
69
101
  end
70
102
 
@@ -0,0 +1,3 @@
1
+ sampler_label,aggregate_report_count,average,aggregate_report_median,aggregate_report_90%_line,aggregate_report_min,aggregate_report_max,aggregate_report_error%,aggregate_report_rate,aggregate_report_bandwidth
2
+ identify,9434,98,41,317,2,2041,1.0599957600169599E-4,86.93889211430889,16.460990345050824
3
+ TOTAL,9434,98,41,317,2,2041,1.0599957600169599E-4,86.93889211430889,16.460990345050824
@@ -0,0 +1,11 @@
1
+ sampler_label,aggregate_report_count,average,aggregate_report_median,aggregate_report_90%_line,aggregate_report_min,aggregate_report_max,aggregate_report_error%,aggregate_report_rate,aggregate_report_bandwidth
2
+ crop 100x100 JPEG,9434,97,67,168,10,6023,1.0599957600169599E-4,2.034068600763908,8.133324288364369
3
+ crop 800x600 JPEG,9434,437,364,877,11,3589,1.0599957600169599E-4,2.033919060628639,158.9437935773869
4
+ crop 300x400 PNG,9434,427,388,649,9,1524,1.0599957600169599E-4,2.0337520051341786,231.20404558388668
5
+ pad 100x100 JPEG,9434,57,43,102,4,940,1.0599957600169599E-4,2.0338642493706423,6.362563138196591
6
+ fit 100x100 JPEG,9434,55,41,102,3,876,1.0599957600169599E-4,2.033791464586396,6.162153297530174
7
+ limit 100x100 JPEG,9434,56,43,103,3,572,1.0599957600169599E-4,2.0337879570175277,6.160442445959317
8
+ limit 500x500 JPEG,9434,335,294,610,3,1638,1.0599957600169599E-4,2.0336318824503,74.19337637719873
9
+ limit 1500x1500 JPEG,9434,537,158,1366,3,9732,1.0599957600169599E-4,2.033324187613275,330.89337206349103
10
+ multipart,9434,2402,1201,4951,178,54452,1.0599957600169599E-4,2.0319087644103666,819.6699318973896
11
+ TOTAL,84906,489,139,992,3,54452,1.0599957600169599E-4,18.28246151103233,1640.6136821422608
@@ -30,12 +30,12 @@
30
30
  <collectionProp name="Arguments.arguments">
31
31
  <elementProp name="media_dir" elementType="Argument">
32
32
  <stringProp name="Argument.name">media_dir</stringProp>
33
- <stringProp name="Argument.value">/home/kazuya/Documents/Test Data/Tatoos</stringProp>
33
+ <stringProp name="Argument.value">/home/kazuya/Documents/Test Data</stringProp>
34
34
  <stringProp name="Argument.metadata">=</stringProp>
35
35
  </elementProp>
36
36
  <elementProp name="list_file" elementType="Argument">
37
37
  <stringProp name="Argument.name">list_file</stringProp>
38
- <stringProp name="Argument.value">/home/kazuya/Documents/Test Data/Tatoos/list.csv</stringProp>
38
+ <stringProp name="Argument.value">/home/kazuya/Documents/Test Data/tatoos.csv</stringProp>
39
39
  <stringProp name="Argument.metadata">=</stringProp>
40
40
  </elementProp>
41
41
  </collectionProp>
@@ -75,13 +75,70 @@
75
75
  <stringProp name="delimiter">,</stringProp>
76
76
  <stringProp name="fileEncoding"></stringProp>
77
77
  <stringProp name="filename">${list_file}</stringProp>
78
- <boolProp name="quotedData">false</boolProp>
78
+ <boolProp name="quotedData">true</boolProp>
79
79
  <boolProp name="recycle">false</boolProp>
80
80
  <stringProp name="shareMode">All threads</stringProp>
81
81
  <boolProp name="stopThread">true</boolProp>
82
82
  <stringProp name="variableNames">file</stringProp>
83
83
  </CSVDataSet>
84
84
  <hashTree/>
85
+ <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="identify" enabled="true">
86
+ <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
87
+ <collectionProp name="Arguments.arguments"/>
88
+ </elementProp>
89
+ <stringProp name="HTTPSampler.domain"></stringProp>
90
+ <stringProp name="HTTPSampler.port"></stringProp>
91
+ <stringProp name="HTTPSampler.connect_timeout"></stringProp>
92
+ <stringProp name="HTTPSampler.response_timeout"></stringProp>
93
+ <stringProp name="HTTPSampler.protocol"></stringProp>
94
+ <stringProp name="HTTPSampler.contentEncoding"></stringProp>
95
+ <stringProp name="HTTPSampler.path">/identify</stringProp>
96
+ <stringProp name="HTTPSampler.method">PUT</stringProp>
97
+ <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
98
+ <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
99
+ <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
100
+ <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
101
+ <elementProp name="HTTPsampler.Files" elementType="HTTPFileArgs">
102
+ <collectionProp name="HTTPFileArgs.files">
103
+ <elementProp name="${media_dir}/${file}" elementType="HTTPFileArg">
104
+ <stringProp name="File.path">${media_dir}/${file}</stringProp>
105
+ <stringProp name="File.paramname"></stringProp>
106
+ <stringProp name="File.mimetype"></stringProp>
107
+ </elementProp>
108
+ </collectionProp>
109
+ </elementProp>
110
+ <boolProp name="HTTPSampler.monitor">false</boolProp>
111
+ <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
112
+ </HTTPSamplerProxy>
113
+ <hashTree>
114
+ <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="200" enabled="true">
115
+ <collectionProp name="Asserion.test_strings">
116
+ <stringProp name="49586">200</stringProp>
117
+ </collectionProp>
118
+ <stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
119
+ <boolProp name="Assertion.assume_success">false</boolProp>
120
+ <intProp name="Assertion.test_type">2</intProp>
121
+ </ResponseAssertion>
122
+ <hashTree/>
123
+ <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Content-Type: image/jpeg" enabled="true">
124
+ <collectionProp name="Asserion.test_strings">
125
+ <stringProp name="2066544187">Content-Type: application/json</stringProp>
126
+ </collectionProp>
127
+ <stringProp name="Assertion.test_field">Assertion.response_headers</stringProp>
128
+ <boolProp name="Assertion.assume_success">false</boolProp>
129
+ <intProp name="Assertion.test_type">2</intProp>
130
+ </ResponseAssertion>
131
+ <hashTree/>
132
+ <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="identify JSON" enabled="true">
133
+ <collectionProp name="Asserion.test_strings">
134
+ <stringProp name="-1132805170">&quot;mimeType&quot;:&quot;image/</stringProp>
135
+ </collectionProp>
136
+ <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
137
+ <boolProp name="Assertion.assume_success">false</boolProp>
138
+ <intProp name="Assertion.test_type">2</intProp>
139
+ </ResponseAssertion>
140
+ <hashTree/>
141
+ </hashTree>
85
142
  <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="crop 100x100 JPEG" enabled="true">
86
143
  <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
87
144
  <collectionProp name="Arguments.arguments"/>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpthumbnailer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-16 00:00:00.000000000 Z
12
+ date: 2013-09-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: unicorn-cuba-base
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '1.0'
21
+ version: '1.1'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: '1.0'
29
+ version: '1.1'
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: rmagick
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +206,7 @@ files:
206
206
  - VERSION
207
207
  - bin/httpthumbnailer
208
208
  - features/httpthumbnailer.feature
209
+ - features/identify.feature
209
210
  - features/step_definitions/httpthumbnailer_steps.rb
210
211
  - features/support/env.rb
211
212
  - features/support/test-large.jpg
@@ -223,6 +224,8 @@ files:
223
224
  - load_test/extralarge.jpg
224
225
  - load_test/large.jpg
225
226
  - load_test/large.png
227
+ - load_test/load_test-374846090-1.1.0-rc1-identify-only.csv
228
+ - load_test/load_test-374846090-1.1.0-rc1.csv
226
229
  - load_test/load_test-cd9679c.csv
227
230
  - load_test/load_test-v0.3.1.csv
228
231
  - load_test/load_test.jmx
@@ -255,7 +258,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
255
258
  version: '0'
256
259
  segments:
257
260
  - 0
258
- hash: -1707312696099202820
261
+ hash: -1021234509576024506
259
262
  required_rubygems_version: !ruby/object:Gem::Requirement
260
263
  none: false
261
264
  requirements: