httpthumbnailer 0.3.1 → 1.0.0

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