httpthumbnailer 0.3.1 → 1.0.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.
data/bin/httpthumbnailer CHANGED
@@ -1,151 +1,94 @@
1
- #!/usr/bin/ruby
2
-
3
- require 'cli'
4
- require 'ip'
5
-
6
- options = CLI.new do
7
- description 'HTTP thumbnailing server'
8
- switch :no_bind, :description => "Do not bind to TCP socket - useful with -s fastcgi option"
9
- switch :no_logging, :description => "Disable logging"
10
- switch :debug, :description => "Enable debugging"
11
- switch :no_optimization, :description => "Disable size hinting and related optimization (loading, prescaling)"
12
- option :bind, :short => :b, :default => IP.new('127.0.0.1'), :cast => IP, :description => "HTTP server bind address - use 0.0.0.0 to bind to all interfaces"
13
- option :port, :short => :p, :default => 3100, :cast => Integer, :description => "HTTP server TCP port"
14
- option :server, :short => :s, :default => 'mongrel', :description => "Rack server handler like thin, mongrel, webrick, fastcgi etc."
15
- option :limit_memory, :default => 128*1024**2, :cast => Integer, :description => "Image cache heap memory size limit in bytes"
16
- option :limit_map, :default => 256*1024**2, :cast => Integer, :description => "Image cache memory mapped file size limit in bytes - used when heap memory limit is used up"
17
- option :limit_disk, :default => 0, :cast => Integer, :description => "Image cache temporary file size limit in bytes - used when memory mapped file limit is used up"
18
- end.parse!
19
-
20
- require 'sinatra/base'
21
- require 'haml'
22
- require 'RMagick'
1
+ #!/usr/bin/env ruby
2
+ require 'unicorn-cuba-base'
23
3
 
24
4
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
25
- require 'httpthumbnailer/thumbnailer'
26
- require 'httpthumbnailer/thumbnail_specs'
27
-
28
- sinatra = Sinatra.new
29
-
30
- unless options.no_bind
31
- sinatra.set :port, options.port
32
- sinatra.set :bind, options.bind.to_s
33
- else
34
- sinatra.set :port, nil
35
- sinatra.set :bind, nil
36
- end
37
- sinatra.set :environment, 'production'
38
- sinatra.set :server, options.server
39
- sinatra.set :lock, true
40
- sinatra.set :boundary, "thumnail image data"
41
- sinatra.set :logging, (not options.no_logging)
42
- sinatra.set :debug, options.debug
43
- sinatra.set :optimization, (not options.no_optimization)
44
- sinatra.set :limit_memory, options.limit_memory
45
- sinatra.set :limit_map, options.limit_map
46
- sinatra.set :limit_disk, options.limit_disk
47
-
48
- sinatra.before do
49
- logger.level = Logger::DEBUG if settings.logging and settings.debug
50
- if $thumbnailer.nil?
51
- $thumbnailer = Thumbnailer.new(:logger => logger, :limit_memory => settings.limit_memory, :limit_map => settings.limit_map, :limit_disk => settings.limit_disk)
52
5
 
53
- $thumbnailer.method('crop') do |image, width, height, options|
54
- image.resize_to_fill!(width, height)
55
- end
56
-
57
- $thumbnailer.method('fit') do |image, width, height, options|
58
- image.resize_to_fit!(width, height)
59
- end
60
-
61
- $thumbnailer.method('pad') do |image, width, height, options|
62
- image.resize_to_fit!(width, height)
63
-
64
- out = Magick::Image.new(width, height) {
65
- self.background_color = Magick::Pixel.new(Magick::MaxRGB, Magick::MaxRGB, Magick::MaxRGB, Magick::MaxRGB) # transparent
66
- }.composite!(image, Magick::CenterGravity, Magick::OverCompositeOp)
67
-
68
- image.destroy!
69
- out
70
- end
6
+ Application.new('httpthumbnailer', port: 3100) do
7
+ cli do
8
+ description 'Image thumbnailing HTTP server'
9
+ switch :formats,
10
+ short: :F,
11
+ description: 'print backend versions and supported formats'
12
+ option :limit_disk,
13
+ cast: Integer,
14
+ description: 'image cache temporary file size limit in MiB',
15
+ default: 1024
16
+ switch :no_optimization,
17
+ description: 'disable load time size hinting and prescaling optimizations'
18
+ version (Pathname.new(__FILE__).dirname + '..' + 'VERSION').read
71
19
  end
72
- end
73
20
 
74
- sinatra.helpers do
75
- def plain_response(msg)
76
- headers "Content-Type" => "text/plain"
77
- body msg.gsub("\n", "\r\n") + "\r\n"
78
- end
21
+ settings do |settings|
22
+ if settings.formats
23
+ require 'httpthumbnailer/plugin/thumbnailer'
24
+ puts "Versions:"
25
+ puts "\t#{Plugin::Thumbnailer::Service.rmagick_version}"
26
+ puts "\t#{Plugin::Thumbnailer::Service.magick_version}"
27
+ puts
28
+ puts "Input formats:"
29
+ puts "\t#{Plugin::Thumbnailer::Service.input_formats.join(' ')}"
30
+ puts
31
+ puts "Output formats:"
32
+ puts "\t#{Plugin::Thumbnailer::Service.output_formats.join(' ')}"
33
+ exit 0
34
+ end
79
35
 
80
- def plain_exception(exception)
81
- plain_response("Error: #{exception.class.name}: #{exception}")
36
+ Controler.settings[:optimization] = (not settings.no_optimization)
37
+ Controler.settings[:limit_memory] = settings.limit_memory * 1024**2
38
+ Controler.settings[:limit_map] = settings.limit_disk * 1024**2
39
+ Controler.settings[:limit_disk] = settings.limit_disk * 1024**2
82
40
  end
83
- end
84
-
85
- sinatra.get '/' do
86
- logger.info 'hello'
87
- end
88
-
89
- sinatra.get '/stats/images' do
90
- $thumbnailer.images.to_s
91
- end
92
41
 
93
- sinatra.put %r{^/thumbnail/(.*)} do |specs|
94
- thumbnail_specs = ThumbnailSpecs.from_uri(specs)
95
-
96
- opts = {}
97
- if settings.optimization
98
- opts.merge!({'max-width' => thumbnail_specs.max_width, 'max-height' => thumbnail_specs.max_height})
99
- end
42
+ main do |settings|
43
+ require 'httpthumbnailer/error_reporter'
44
+ require 'httpthumbnailer/thumbnailer'
45
+
46
+ class HTTPThumbniler < Controler
47
+ extend Stats
48
+ def_stats(
49
+ :workers,
50
+ :total_requests,
51
+ :total_errors
52
+ )
53
+
54
+ raindrops_stats = Raindrops::Middleware::Stats.new
55
+ self.use Raindrops::Middleware, stats: raindrops_stats
56
+
57
+ StatsReporter << HTTPThumbniler.stats
58
+ StatsReporter << raindrops_stats
59
+ StatsReporter << Plugin::Thumbnailer::Service.stats
60
+ StatsReporter << Plugin::ResponseHelpers.stats
61
+
62
+ self.define do
63
+ HTTPThumbniler.stats.incr_total_requests
64
+ on error? do
65
+ HTTPThumbniler.stats.incr_total_errors
66
+ run ErrorReporter
67
+ end
68
+
69
+ on 'stats' do
70
+ run StatsReporter
71
+ end
100
72
 
101
- input_image_handler = $thumbnailer.load(request.body, opts)
102
- input_image_handler.get do |input_image|
103
- status 200
104
- headers "Content-Type" => "multipart/mixed; boundary=\"#{settings.boundary}\""
105
- headers "X-Input-Image-Content-Type" => input_image.mime_type
73
+ on 'health_check' do
74
+ write_plain 200, 'OK'
75
+ end
106
76
 
107
- stream do |out| # this is non blocking
108
- input_image_handler.use do |input_image|
109
- thumbnail_specs.each do |spec|
110
- logger.info "Thumbnailing: #{spec}"
111
- out << "--#{settings.boundary}\r\n"
77
+ on root do
78
+ write_plain 200, 'HTTP Thumbnailer'
79
+ end
112
80
 
113
- begin
114
- input_image.thumbnail(spec) do |thumbnail|
115
- out << "Content-Type: #{thumbnail.mime_type}\r\n\r\n"
116
- out << thumbnail.data
117
- end
118
- rescue => e
119
- logger.error "Thumbnailing error: #{e.class.name}: #{e}: \n#{e.backtrace.join("\n")}"
120
- out << "Content-Type: text/plain\r\n\r\n"
121
- out << "Error: #{e.class.name}: #{e}\r\n"
122
- ensure
123
- out << "\r\n"
124
- end
81
+ on true do
82
+ run Thumbnailer
125
83
  end
126
- out << "--#{settings.boundary}--"
127
84
  end
128
85
  end
129
- end
130
- end
131
-
132
- sinatra.error Thumbnailer::UnsupportedMediaTypeError do
133
- plain_exception(env['sinatra.error'])
134
- halt 415
135
- end
136
-
137
- sinatra.error Thumbnailer::ImageTooLargeError do
138
- plain_exception(env['sinatra.error'])
139
- halt 413
140
- end
141
86
 
142
- sinatra.error 404 do
143
- plain_response("Resource '#{request.path_info}' not found")
144
- end
87
+ HTTPThumbniler
88
+ end
145
89
 
146
- sinatra.error do
147
- plain_exception(env['sinatra.error'])
90
+ after_fork do |server, worker|
91
+ HTTPThumbniler.stats.incr_workers
92
+ end
148
93
  end
149
94
 
150
- sinatra.run!
151
-
@@ -1,273 +1,24 @@
1
- Feature: Generating set of thumbnails with single PUT request
2
- In order to generate a set of image thumbnails
3
- A user must PUT an image to URL in format
4
- /thumbnail[/<thumbnail type>,<width>,<height>,<format>[,<option key>:<option value>]+]+
1
+ Feature: HTTP server
2
+ It should behave like valid HTTP server
5
3
 
6
4
  Background:
7
- Given httpthumbnailer log is empty
8
5
  Given httpthumbnailer server is running at http://localhost:3100/
9
6
 
10
- Scenario: Single thumbnail
11
- Given test.jpg file content as request body
12
- When I do PUT request http://localhost:3100/thumbnail/crop,16,16,PNG
13
- Then response status will be 200
14
- And I will get multipart response
15
- Then first part will contain PNG image of size 16x16
16
- And first part mime type will be image/png
17
- And there will be no leaked images
18
-
19
- Scenario: Multiple thumbnails
20
- Given test.jpg file content as request body
21
- When I do PUT request http://localhost:3100/thumbnail/crop,16,16,PNG/crop,4,8,JPG/crop,16,32,JPEG
22
- Then response status will be 200
23
- And I will get multipart response
24
- Then first part will contain PNG image of size 16x16
25
- And first part mime type will be image/png
26
- Then second part will contain JPEG image of size 4x8
27
- And second part mime type will be image/jpeg
28
- Then third part will contain JPEG image of size 16x32
29
- And third part mime type will be image/jpeg
30
- And there will be no leaked images
31
-
32
- Scenario: Transparent image to JPEG handling - default background color white
33
- Given test-transparent.png file content as request body
34
- When I do PUT request http://localhost:3100/thumbnail/fit,128,128,JPEG
35
- Then response status will be 200
36
- And I will get multipart response
37
- And first part body will be saved as test-transparent-default.png for human inspection
38
- And first part will contain JPEG image of size 128x128
39
- And that image pixel at 32x32 will be of color white
40
- And there will be no leaked images
41
-
42
- Scenario: Thumbnails of format INPUT should have same format as input image - for JPEG
43
- Given test.jpg file content as request body
44
- When I do PUT request http://localhost:3100/thumbnail/crop,16,16,PNG/crop,4,8,INPUT/crop,16,32,INPUT
45
- Then response status will be 200
46
- And I will get multipart response
47
- Then first part will contain PNG image of size 16x16
48
- And first part mime type will be image/png
49
- Then second part will contain JPEG image of size 4x8
50
- And second part mime type will be image/jpeg
51
- Then third part will contain JPEG image of size 16x32
52
- And third part mime type will be image/jpeg
53
-
54
- Scenario: Thumbnails of format INPUT should have same format as input image - for PNG
55
- Given test.png file content as request body
56
- When I do PUT request http://localhost:3100/thumbnail/crop,16,16,JPEG/crop,4,8,INPUT/crop,16,32,INPUT
57
- Then response status will be 200
58
- And I will get multipart response
59
- Then first part will contain JPEG image of size 16x16
60
- And first part mime type will be image/jpeg
61
- Then second part will contain PNG image of size 4x8
62
- And second part mime type will be image/png
63
- Then third part will contain PNG image of size 16x32
64
- And third part mime type will be image/png
65
-
66
- Scenario: Thumbnails of width or height INPUT will have input image width or height
67
- Given test.jpg file content as request body
68
- When I do PUT request http://localhost:3100/thumbnail/crop,INPUT,16,JPEG/crop,4,INPUT,PNG/crop,INPUT,INPUT,PNG
69
- Then response status will be 200
70
- And I will get multipart response
71
- Then first part will contain JPEG image of size 509x16
72
- And first part mime type will be image/jpeg
73
- Then second part will contain PNG image of size 4x719
74
- And second part mime type will be image/png
75
- Then third part will contain PNG image of size 509x719
76
- And third part mime type will be image/png
77
-
78
- Scenario: Fit thumbnailing method
79
- Given test.jpg file content as request body
80
- When I do PUT request http://localhost:3100/thumbnail/fit,128,128,PNG
81
- Then response status will be 200
82
- And I will get multipart response
83
- And first part will contain PNG image of size 91x128
84
- And there will be no leaked images
85
-
86
- Scenario: Pad thumbnailing method - default background color white
87
- Given test.jpg file content as request body
88
- When I do PUT request http://localhost:3100/thumbnail/pad,128,128,PNG
89
- Then response status will be 200
90
- And I will get multipart response
91
- And first part body will be saved as test-pad.png for human inspection
92
- And first part will contain PNG image of size 128x128
93
- And that image pixel at 2x2 will be of color white
94
- And there will be no leaked images
95
-
96
- Scenario: Pad thumbnailing method with specified background color
97
- Given test.jpg file content as request body
98
- When I do PUT request http://localhost:3100/thumbnail/pad,128,128,PNG,background-color:green
99
- Then response status will be 200
100
- And I will get multipart response
101
- And first part body will be saved as test-pad-background-color.png for human inspection
102
- And first part will contain PNG image of size 128x128
103
- And that image pixel at 2x2 will be of color green
104
- And there will be no leaked images
105
-
106
- Scenario: Image leaking on error
107
- Given test.jpg file content as request body
108
- When I do PUT request http://localhost:3100/thumbnail/crop,0,0,PNG/fit,0,0,JPG/pad,0,0,JPEG
109
- Then response status will be 200
110
- And I will get multipart response
111
- And first part content type will be text/plain
112
- And second part content type will be text/plain
113
- And third part content type will be text/plain
114
- And there will be no leaked images
115
-
116
7
  Scenario: Reporitng of missing resource for GET
117
8
  When I do GET request http://localhost:3100/blah
118
- Then response status will be 404
119
- And response content type will be text/plain
120
- And response body will be CRLF endend lines
9
+ Then response status should be 404
10
+ And response content type should be text/plain
11
+ And response body should be CRLF endend lines
121
12
  """
122
- Resource '/blah' not found
13
+ request for URI '/blah' was not handled by the server
123
14
  """
124
15
 
125
16
  Scenario: Reporitng of missing resource for PUT
126
- When I do PUT request http://localhost:3100/blah/thumbnail/crop,0,0,PNG/fit,0,0,JPG/pad,0,0,JPEG
127
- Then response status will be 404
128
- And response content type will be text/plain
129
- And response body will be CRLF endend lines
130
- """
131
- Resource '/blah/thumbnail/crop,0,0,PNG/fit,0,0,JPG/pad,0,0,JPEG' not found
132
- """
133
-
134
- Scenario: Reporitng of unsupported media type
135
- Given test.txt file content as request body
136
- When I do PUT request http://localhost:3100/thumbnail/crop,128,128,PNG
137
- Then response status will be 415
138
- And response content type will be text/plain
139
- And response body will be CRLF endend lines like
140
- """
141
- Error: Thumbnailer::UnsupportedMediaTypeError: Magick::ImageMagickError:
142
- """
143
-
144
- Scenario: Reporitng of bad thumbanil spec format - bad dimmension value
145
- Given test.txt file content as request body
146
- When I do PUT request http://localhost:3100/thumbnail/crop,128,bogous,PNG
147
- Then response status will be 500
148
- And response content type will be text/plain
149
- And response body will be CRLF endend lines
150
- """
151
- Error: ThumbnailSpecs::BadThubnailSpecError::BadDimmensionValueError: bad dimmension value: bogous
152
- """
153
-
154
- Scenario: Reporitng of bad thumbanil spec format - missing param
155
- Given test.txt file content as request body
156
- When I do PUT request http://localhost:3100/thumbnail/crop,128,PNG
157
- Then response status will be 500
158
- And response content type will be text/plain
159
- And response body will be CRLF endend lines
17
+ When I do PUT request http://localhost:3100/blah/thumbnails/crop,0,0,PNG/fit,0,0,JPG/pad,0,0,JPEG
18
+ Then response status should be 404
19
+ And response content type should be text/plain
20
+ And response body should be CRLF endend lines
160
21
  """
161
- Error: ThumbnailSpecs::BadThubnailSpecError::MissingArgumentError: missing argument in: crop,128,PNG
22
+ request for URI '/blah/thumbnails/crop,0,0,PNG/fit,0,0,JPG/pad,0,0,JPEG' was not handled by the server
162
23
  """
163
24
 
164
- Scenario: Reporitng of bad thumbanil spec format - bad options format
165
- Given test.txt file content as request body
166
- When I do PUT request http://localhost:3100/thumbnail/crop,128,128,PNG,fas-fda
167
- Then response status will be 500
168
- And response content type will be text/plain
169
- And response body will be CRLF endend lines
170
- """
171
- Error: ThumbnailSpecs::BadThubnailSpecError::MissingOptionKeyOrValueError: missing option key or value in: fas-fda
172
- """
173
-
174
- Scenario: Reporitng of image thumbnailing errors
175
- Given test.jpg file content as request body
176
- When I do PUT request http://localhost:3100/thumbnail/crop,16,16,PNG/crop,0,0,JPG/crop,16,32,JPEG
177
- Then response status will be 200
178
- And I will get multipart response
179
- Then first part will contain PNG image of size 16x16
180
- And first part mime type will be image/png
181
- And second part content type will be text/plain
182
- And second part body will be CRLF endend lines
183
- """
184
- Error: ArgumentError: invalid result dimension (0, 0 given)
185
- """
186
- Then third part will contain JPEG image of size 16x32
187
- And third part mime type will be image/jpeg
188
- And there will be no leaked images
189
-
190
- Scenario: Handing of large image data - possible thanks to loading size optimization
191
- Given test-large.jpg file content as request body
192
- When I do PUT request http://localhost:3100/thumbnail/crop,16,16,PNG/crop,32,32,JPEG
193
- Then response status will be 200
194
- And I will get multipart response
195
- Then first part will contain PNG image of size 16x16
196
- And first part mime type will be image/png
197
- Then second part will contain JPEG image of size 32x32
198
- And second part mime type will be image/jpeg
199
- And there will be no leaked images
200
-
201
- Scenario: Memory limits exhausted while loading
202
- Given test-large.jpg file content as request body
203
- When I do PUT request http://localhost:3100/thumbnail/crop,7000,7000,PNG
204
- Then response status will be 413
205
- And response content type will be text/plain
206
- And response body will be CRLF endend lines like
207
- """
208
- Error: Thumbnailer::ImageTooLargeError: Magick::ImageMagickError: cache resources exhausted
209
- """
210
- And there will be no leaked images
211
-
212
- Scenario: Memory limits exhausted while thumbnailing
213
- Given test.jpg file content as request body
214
- When I do PUT request http://localhost:3100/thumbnail/crop,16,16,PNG/crop,16000,16000,JPG/crop,16,32,JPEG
215
- Then response status will be 200
216
- And I will get multipart response
217
- Then first part will contain PNG image of size 16x16
218
- And first part mime type will be image/png
219
- And second part content type will be text/plain
220
- And second part body will be CRLF endend lines like
221
- """
222
- Error: Thumbnailer::ImageTooLargeError: Magick::ImageMagickError: cache resources exhausted
223
- """
224
- Then third part will contain JPEG image of size 16x32
225
- And third part mime type will be image/jpeg
226
- And there will be no leaked images
227
-
228
- Scenario: Quality option - JPEG
229
- Given test.jpg file content as request body
230
- When I do PUT request http://localhost:3100/thumbnail/crop,32,32,JPEG,quality:10/crop,32,32,JPG,quality:80/crop,32,32,JPEG,quality:90
231
- Then response status will be 200
232
- And I will get multipart response
233
- And first part mime type will be image/jpeg
234
- And second part mime type will be image/jpeg
235
- And third part mime type will be image/jpeg
236
- Then first part will contain body smaller than second part
237
- Then second part will contain body smaller than third part
238
-
239
- Scenario: Quality option - JPEG - default 85
240
- Given test.jpg file content as request body
241
- When I do PUT request http://localhost:3100/thumbnail/crop,32,32,JPEG,quality:84/crop,32,32,JPG/crop,32,32,JPEG,quality:86
242
- Then response status will be 200
243
- And I will get multipart response
244
- And first part mime type will be image/jpeg
245
- And second part mime type will be image/jpeg
246
- And third part mime type will be image/jpeg
247
- Then first part will contain body smaller than second part
248
- Then second part will contain body smaller than third part
249
-
250
- Scenario: Quality option - PNG (XY where X - zlib compresion level, Y - filter)
251
- Given test.jpg file content as request body
252
- When I do PUT request http://localhost:3100/thumbnail/crop,64,64,PNG,quality:90/crop,64,64,PNG,quality:50/crop,64,64,PNG,quality:10
253
- Then response status will be 200
254
- And I will get multipart response
255
- And first part mime type will be image/png
256
- And second part mime type will be image/png
257
- And third part mime type will be image/png
258
- Then first part will contain body smaller than second part
259
- Then second part will contain body smaller than third part
260
- And there will be no leaked images
261
-
262
- Scenario: Hint on input image mime type - JPEG
263
- Given test.jpg file content as request body
264
- When I do PUT request http://localhost:3100/thumbnail/crop,16,16,PNG
265
- Then response status will be 200
266
- And X-Input-Image-Content-Type header will be image/jpeg
267
-
268
- Scenario: Hint on input image mime type - PNG
269
- Given test.png file content as request body
270
- When I do PUT request http://localhost:3100/thumbnail/crop,16,16,PNG
271
- Then response status will be 200
272
- And X-Input-Image-Content-Type header will be image/png
273
-