httpimagestore 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/Gemfile +10 -12
  2. data/Gemfile.lock +57 -55
  3. data/README.md +829 -0
  4. data/VERSION +1 -1
  5. data/bin/httpimagestore +114 -180
  6. data/features/cache-control.feature +26 -90
  7. data/features/compatibility.feature +129 -0
  8. data/features/error-reporting.feature +207 -0
  9. data/features/health-check.feature +30 -0
  10. data/features/s3-store-and-thumbnail.feature +65 -0
  11. data/features/step_definitions/httpimagestore_steps.rb +66 -26
  12. data/features/support/env.rb +32 -5
  13. data/features/support/test.empty +0 -0
  14. data/httpimagestore.gemspec +60 -47
  15. data/lib/httpimagestore/aws_sdk_regions_hack.rb +23 -0
  16. data/lib/httpimagestore/configuration/file.rb +120 -0
  17. data/lib/httpimagestore/configuration/handler.rb +239 -0
  18. data/lib/httpimagestore/configuration/output.rb +119 -0
  19. data/lib/httpimagestore/configuration/path.rb +77 -0
  20. data/lib/httpimagestore/configuration/s3.rb +194 -0
  21. data/lib/httpimagestore/configuration/thumbnailer.rb +244 -0
  22. data/lib/httpimagestore/configuration.rb +126 -29
  23. data/lib/httpimagestore/error_reporter.rb +36 -0
  24. data/lib/httpimagestore/ruby_string_template.rb +26 -0
  25. data/load_test/load_test.1k.23a022f6e.m1.small-comp.csv +3 -0
  26. data/load_test/load_test.1k.ec9bde794.m1.small.csv +4 -0
  27. data/load_test/load_test.jmx +344 -0
  28. data/load_test/thumbnail_specs.csv +11 -0
  29. data/spec/configuration_file_spec.rb +309 -0
  30. data/spec/configuration_handler_spec.rb +124 -0
  31. data/spec/configuration_output_spec.rb +338 -0
  32. data/spec/configuration_path_spec.rb +92 -0
  33. data/spec/configuration_s3_spec.rb +571 -0
  34. data/spec/configuration_spec.rb +80 -105
  35. data/spec/configuration_thumbnailer_spec.rb +417 -0
  36. data/spec/ruby_string_template_spec.rb +43 -0
  37. data/spec/spec_helper.rb +61 -0
  38. data/spec/support/compute.jpg +0 -0
  39. data/spec/support/cuba_response_env.rb +40 -0
  40. data/spec/support/full.cfg +49 -0
  41. metadata +138 -84
  42. data/README.rdoc +0 -23
  43. data/features/httpimagestore.feature +0 -167
  44. data/lib/httpimagestore/image_path.rb +0 -54
  45. data/lib/httpimagestore/s3_service.rb +0 -37
  46. data/lib/httpimagestore/thumbnail_class.rb +0 -13
  47. data/spec/image_path_spec.rb +0 -72
  48. data/spec/test.cfg +0 -8
data/README.md ADDED
@@ -0,0 +1,829 @@
1
+ # HTTP Image Store
2
+
3
+ HTTP API server for image thumbnailing and storage.
4
+ It is using [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) as image processing backend.
5
+
6
+ ## Features
7
+
8
+ * fully configurable image processing and storage pipeline with custom API configuration capabilities
9
+ * thumbnailing of sourced or input image into one or more thumbnails
10
+ * sourcing and storage of images on file system
11
+ * sourcing and storage of images on [Amazon S3](http://aws.amazon.com/s3/)
12
+ * image output with Cache-Control header
13
+ * S3 public or private and http:// or https:// URL list output for stored images
14
+ * storage under custom paths including image hash, content determined extension or used URL path
15
+ * based on [Unicorn HTTP server](http://unicorn.bogomips.org) with UNIX socket communication support
16
+
17
+ ## Installing
18
+
19
+ HTTP Image Store is released as gem and can be installed from [RubyGems](http://rubygems.org) with:
20
+
21
+ ```bash
22
+ gem install httpimagestore
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ To start HTTP Image Store server you need to prepare API configuration first.
28
+ Configuration is written in [SDL](http://sdl4r.rubyforge.org) format.
29
+
30
+ Configuration consists of:
31
+
32
+ * thumbnailer client configuration (optional)
33
+ * S3 client configuration (optional)
34
+ * storage path definitions
35
+ * API endpoint definitions
36
+ * image sourcing operations
37
+ * image storage operation
38
+ * output operations
39
+
40
+ ### Top level configuration elements
41
+
42
+ #### thumbnailer
43
+
44
+ Configures [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) client. It will be used to perform image processing operations.
45
+
46
+ Options:
47
+ * `url` - URL of [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) service
48
+
49
+ If omitted [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) service located at `http://localhost:3100` will be used.
50
+
51
+ Example:
52
+
53
+ ```sdl
54
+ thumbnailer url="http://2.2.2.2:1000"
55
+ ```
56
+
57
+ #### s3
58
+
59
+ Configures [Amazon S3](http://aws.amazon.com/s3/) client for S3 object storage and retrieval.
60
+
61
+ Options:
62
+
63
+ * `key` - API key to use
64
+ * `secret` - API secret for given key
65
+ * `ssl` - if `true` SSL protected connection will be used; source and storage URL will begin with `http://`; default: `true`
66
+
67
+ Example:
68
+
69
+ ```sdl
70
+ s3 key="AIAITCKMELYWQZPJP7HQ" secret="V37lCu0F48Tv9s7QVqIT/sLf/wwqhNSB4B0Em7Ei" ssl=false
71
+ ```
72
+
73
+ #### path
74
+
75
+ This directive is used to define storage and retrieval paths that will be used when storing and sourcing file on file system or S3 service.
76
+ You can declare one path per statement or use `{}` brackets syntax to define more than one with single statement - they are semantically equal.
77
+
78
+ Arguments:
79
+
80
+ 1. name - name of the path used later to reference it
81
+ 2. pattern - path patter that can consists of characters and variables surrounded by `#{}`
82
+
83
+ Variables:
84
+
85
+ * `digest` - input image digest based on it's content only; this is first 16 hex characters from SHA2 digest; ex. `2cf24dba5fb0a30e`
86
+ * `path` - remaining (unmatched) request URL path
87
+ * `basename` - name of the file without it's extension determined from `path`
88
+ * `direname` - name of the directory determined form `path`
89
+ * `extension` - file extension determined from `path`
90
+ * `mimeextension` - image extension based on mime type; mime type will be updated based on information from [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) for input image and output thumbnails - content determined
91
+ * `imagename` - name of the image that is being stored or sourced
92
+ * URL matches - other variables can be matched from URL pattern - see API configuration
93
+
94
+ Example:
95
+
96
+ ```sdl
97
+ path "uri" "#{path}"
98
+ path "hash" "#{digest}.#{extension}"
99
+
100
+ path {
101
+ "hash-name" "#{digest}/#{imagename}.#{extension}"
102
+ "structured" "#{dirname}/#{digest}/#{basename}.#{extension}"
103
+ "structured-name" "#{dirname}/#{digest}/#{basename}-#{imagename}.#{extension}"
104
+ }
105
+ ```
106
+
107
+ #### API endpoint
108
+
109
+ This group of statements allows to configure single API endpoint.
110
+ Each endpoint can have one or more operation defined that will be performed on request matching that endpoint.
111
+
112
+ Endpoints will be evaluated in order of definition until one is matched.
113
+
114
+ Statement should start with one of the following HTTP verbs in lowercase: `get`, `post`, `put`, `delete`, followed by list of URI component matchers:
115
+
116
+ * `string` - character string will match whole URI component in that position
117
+ * `:symbol` - string beginning with `:` will match any URI component in that position and will store matched component string in variable named as symbol; this variable can then be used to build path, thumbnail parameters or in any other places where variables are expanded
118
+ * `:symbol?` - string beginning with `:` and followed with `?` will be optionally matched; request URI may not contain component in this location (usually at the end of URI) to be matched for this endpoint to be evaluated
119
+ * `:symbol/regexp/` - in this format symbol will be matched only if `/` surrounded [regular expression](http://rubular.com) matches the URI section
120
+
121
+ Note that any remaining URI are is stored in `path` variable.
122
+
123
+ Example:
124
+
125
+ ```sdl
126
+ get "thumbnail" "v1" ":path" ":operation" ":width" ":height" ":options?" {
127
+ }
128
+
129
+ put "thumbnail" "v1" ":thumbnail_class/small|large/" {
130
+ }
131
+
132
+ post {
133
+ }
134
+ ```
135
+
136
+ In this example first endpoint will be matched for **GET** request with URI like `/thumbnail/v1/abc.jpg/fit/100/100` or `/thumbnail/v1/abc.jpg/pad/100/100/background-color:green`.
137
+ Second endpoint will be matched only for **PUT** requests with URIs beginning with `/thumbnail/v1/small` and `/thumbnail/v1/large`.
138
+ Third endpoint will match any **POST** request.
139
+
140
+ ### API endpoint image sourcing operations
141
+
142
+ Sourcing will load images into memory for further processing.
143
+ Each image is stored under predefined or user defined name.
144
+
145
+ If endpoint HTTP verb is `post` or `put` then image data will be sourced from request body. It can be referenced using `input` name.
146
+
147
+ #### source_file
148
+
149
+ With this statements image can be sourced from file system.
150
+
151
+ Arguments:
152
+
153
+ 1. image name - name under which the sourced image can be referenced
154
+
155
+ Options:
156
+
157
+ * `root` - file system path under which the images are looked up
158
+ * `path` - name of predefined path that will be used to locate the image file
159
+
160
+ Example:
161
+
162
+ ```sdl
163
+ path "myimage" "myimage.jpg"
164
+
165
+ get "small" {
166
+ source_file "original" root="/srv/images" path="myimage"
167
+ }
168
+ ```
169
+
170
+ Requesting `/small` URI will result with file `/srv/images/myimage.jpg` loaded into memory under `original` name.
171
+
172
+ #### source_s3
173
+
174
+ This statement can be used to load images from S3 bucket.
175
+ To use this bucket global `s3` statement needs to be used in top level to configure S3 client.
176
+
177
+ Arguments:
178
+
179
+ 1. image name - name under which the sourced image can be referenced
180
+
181
+ Options:
182
+
183
+ * `bucket` - name of bucket to source image from
184
+ * `path` - name of predefined path that will be used to generate key to object to source
185
+
186
+ Example:
187
+
188
+ ```sdl
189
+ s3 key="AIAITCKMELYWQZPJP7HQ" secret="V37lCu0F48Tv9s7QVqIT/sLf/wwqhNSB4B0Em7Ei" ssl=false
190
+ path "myimage" "myimage.jpg"
191
+
192
+ get "small" {
193
+ source_s3 "original" bucket="mybucket" path="myimage"
194
+ }
195
+ ```
196
+
197
+ Requesting `/small` URI will result with image fetched from S3 bucket `mybucket` and key `myimage.jpg` and named `original`.
198
+
199
+ #### thumbnail
200
+
201
+ This source will provide new images based on already sourced images by processing them with [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) backend.
202
+ This statement can be used to do single thumbnail operation or use multipart output API of the [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) when multiple operation are defined.
203
+ For more informations of meaning of options see [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) documentation.
204
+
205
+ Arguments:
206
+
207
+ 1. source image name - thumbnailer input image of which thumbnail will be generated
208
+ 2. thumbnail name - name under which the thumbnail image can be referenced
209
+
210
+ Options:
211
+
212
+ * `operation` - backend supported thumbnailing operation like `pad`, `fit`, `limit`, `crop`
213
+ * `width` - requested thumbnail width in pixels
214
+ * `height` - requested thumbnail height in pixels
215
+ * `format` - format in which the resulting image will be encoded; all backend supported formats can be specified like `jpeg` or `png`; default: `jpeg`
216
+ * `options` - list of options in format `key:value[,key:value]*` to be passed to thumbnailer; this can be a list of any backend supported options like `background-color` or `quality`
217
+ * backend supported options - options can also be defined as statement options
218
+
219
+ Note that you can use `#{variable}` expansion within all of this options.
220
+
221
+ Example:
222
+
223
+ ```sdl
224
+ put ":operation" ":width" ":height" ":options" {
225
+ thumbnail "input" {
226
+ "original" operation="#{operation}" width="#{width}" height="#{height}" options="#{options}" quality=84 format="jpeg"
227
+ "small" operation="crop" width=128 height=128 format="jpeg"
228
+ "padded" operation="pad" width=128 height=128 format="png" background-color="gray"
229
+ }
230
+ }
231
+ ```
232
+
233
+ Putting image under `/pad/128/256/backgroud-color=green` URI will result with three thumbnails generated with single [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) request from `input` image into `original`, `small` and `padded` references.
234
+ `original` image will have width of 128 pixels and height of 256 pixels; the image will be centered and padded with color green background to match this dimensions; it will be a JPEG image, saved with compression quality of 84.
235
+
236
+ You can also use shorter form that will perform only one thumbnailing operation per statement using [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer)'s single thumbnail API:
237
+
238
+ ```sdl
239
+ put ":operation" ":width" ":height" ":options" {
240
+ thumbnail "input" "original" operation="#{operation}" width="#{width}" height="#{height}" options="#{options}" quality=84 format="jpeg"
241
+ }
242
+ ```
243
+
244
+ ### API endpoint storage operations
245
+
246
+ This statements are executed after all source statements are finished.
247
+ They allow storing of any sourced images by specifying their references.
248
+
249
+ #### store_file
250
+
251
+ This statement can store image in file system.
252
+
253
+ Arguments:
254
+
255
+ 1. image name - image to be stored
256
+
257
+ Options:
258
+
259
+ * `root` - file system path under which the images are stored
260
+ * `path` - name of predefined path that will be used to store the image file under; relative to `root`
261
+
262
+ Example:
263
+
264
+ ```sdl
265
+ path "imagename" "#{name}"
266
+ path "hash" "#{digest}"
267
+
268
+ put "store" ":name" {
269
+ store_file "input" root="/srv/images" path="imagename"
270
+ store_file "input" root="/srv/images" path="hash"
271
+ }
272
+ ```
273
+
274
+ Putting image data to `/store/hello.jpg` will store two copies of the image: one under `/srv/images/hello.jpg` and second under its digest like `/srv/images/2cf24dba5fb0a30e`.
275
+
276
+ #### store_s3
277
+
278
+ S3 bucket can also be used for image storage.
279
+ To use this bucket top level `s3` statement needs to be used to configure S3 client.
280
+
281
+ Arguments:
282
+
283
+ 1. image name - image to be stored
284
+
285
+ Options:
286
+
287
+ * `bucket` - name of bucket to store image in
288
+ * `path` - name of predefined path that will be used to generate key to store object under
289
+ * `public` - if set to `true` the image will be readable by everybody; this affects fromat of output URL; default: `false`
290
+
291
+ Example:
292
+
293
+ ```sdl
294
+ s3 key="AIAITCKMELYWQZPJP7HQ" secret="V37lCu0F48Tv9s7QVqIT/sLf/wwqhNSB4B0Em7Ei" ssl=false
295
+
296
+ path "imagename" "#{name}"
297
+ path "hash" "#{digest}"
298
+
299
+ put ":name" {
300
+ store_s3 "input" bucket="mybucket" path="imagename"
301
+ store_s3 "input" bucket="mybucket" path="hash"
302
+ }
303
+ ```
304
+
305
+ Putting image data to `/store/hello.jpg` will store two copies of the image under `mybucket`: one under key `hello.jpg` and second under its digest like `2cf24dba5fb0a30e`.
306
+
307
+ ### API endpoint output operations
308
+
309
+ When all images are stored output statements are processed.
310
+ They are responsible with generating HTTP response for the API endpoint.
311
+ If not output statement is specified the server will respond with `200 OK` and `OK` in response body.
312
+
313
+ #### output_image
314
+
315
+ This statement will produce `200 OK` response containing referenced image data.
316
+
317
+ It will set `Content-Type` header to mime-type of the image that was determined by [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) based on image data if image was used as input in thumbnailing or it is resulting thumbnail. If the mime-type is unknown `application/octet-stream` will be used.
318
+
319
+ Arguments:
320
+
321
+ 1. image name - image to be sent in response body
322
+
323
+ Options:
324
+
325
+ * `cache-control` - value of response `Cache-Control` header can be specified with this option
326
+
327
+ Example:
328
+
329
+ ```sdl
330
+ put "test" {
331
+ output_image "input" cache-control="public, max-age=999, s-maxage=666"
332
+ }
333
+ ```
334
+
335
+ The output will contain the posted image with `Content-Type` header set to `application/octet-stream` and `Cache-Control` to `public, max-age=999, s-maxage=666`.
336
+
337
+ #### output_store_path
338
+
339
+ This statement will output actual storage path on the file system (without root) or S3 key under witch the image was stored.
340
+
341
+ The `Content-Type` of response will be `text/plain`.
342
+ You can specify multiple image names to output multiple paths of referenced images, each ended with `\r\n`.
343
+
344
+ Arguments:
345
+
346
+ 1. image names - names of images to output storage path for in order
347
+
348
+ Example:
349
+
350
+ ```sdl
351
+ path "out" "test.out"
352
+
353
+ put "single" {
354
+ store_file "input" root="/srv/images" path="out"
355
+
356
+ output_store_path "input"
357
+ }
358
+ ```
359
+
360
+ Putting image data to `/single` URI will result with image data stored under `/srv/images/test.out`. The response body will consist of `\r\n` ended line containing `test.out`.
361
+
362
+ ```sdl
363
+ s3 key="AIAITCKMELYWQZPJP7HQ" secret="V37lCu0F48Tv9s7QVqIT/sLf/wwqhNSB4B0Em7Ei" ssl=false
364
+
365
+ path {
366
+ "in" "test.in"
367
+ "out" "test.out"
368
+ "out2" "test.out2"
369
+ }
370
+
371
+ put "multi" {
372
+ source_file "original" root="/srv/images" path="in"
373
+
374
+ store_file "input" root="/srv/images" path="out"
375
+ store_file "original" root="/srv/images" path="out2"
376
+
377
+ output_store_path {
378
+ "input"
379
+ "original"
380
+ }
381
+ }
382
+ ```
383
+
384
+ Putting image data to `/multi` URI will result with image `original` sourced from `/srv/images/test.in` and stored under `/srv/images/out2`. The input data will also be stored under `/srv/images/test.out`. The response body will contain `\r\n` ended lines: `test.out` and `test.out2`.
385
+
386
+ #### output_store_url
387
+
388
+ This is similar statement to `output_store_file` but it will output `file://` URL for file stored images and valid S3 access URL for S3 stored images.
389
+
390
+ For S3 stored image if `ssl` is set to `true` on the S3 client statement (`s3 ssl="true"`) the URL will start with `https://`. If `public` is set to `true` when storing image in S3 (`store_s3 public="true"`) then the URL will not contain query string options, otherwise authentication tokens and expiration token (set to expire in 20 years) will be include in the query string.
391
+
392
+ The `Content-Type` header of this response is `text/uri-list`.
393
+ Each output URL is `\r\n` ended.
394
+
395
+ Arguments:
396
+
397
+ 1. image names - names of images
398
+
399
+ Example:
400
+
401
+ ```sdl
402
+ s3 key="AIAITCKMELYWQZPJP7HQ" secret="V37lCu0F48Tv9s7QVqIT/sLf/wwqhNSB4B0Em7Ei" ssl=false
403
+
404
+ path "hash" "#{digest}.#{mimeextension}"
405
+ path "hash-name" "#{digest}/#{imagename}.#{mimeextension}"
406
+
407
+ put "thumbnail" {
408
+ thumbnail "input" {
409
+ "small" operation="crop" width=128 height=128 format="jpeg"
410
+ "tiny_png" operation="crop" width=32 height=32 format="png"
411
+ }
412
+
413
+ store_s3 "input" bucket="mybucket" path="hash" public=true
414
+ store_s3 "small" bucket="mybucket" path="hash-name" public=true
415
+ store_s3 "tiny_png" bucket="mybucket" path="hash-name" public=true
416
+
417
+ output_store_url {
418
+ "input"
419
+ "small"
420
+ "tiny_png"
421
+ }
422
+ }
423
+ ```
424
+
425
+ Putting image data will result in storage of that image and two thumbnails generated from it. The output will contain `\r\n` ended lines like:
426
+
427
+ ```
428
+ http://mybucket.s3.amazonaws.com/4006450256177f4a.jpg
429
+ http://mybucket.s3.amazonaws.com/4006450256177f4a/small.jpg
430
+ http://mybucket.s3.amazonaws.com/4006450256177f4a/tiny_png.png
431
+ ```
432
+
433
+ #### output_source_file and output_source_url
434
+
435
+ This statements are similar to their storage variants. The difference is that they will output path and URL of the source locations.
436
+
437
+ ### API endpoint meta options
438
+
439
+ Additional meta options can be used with selected statements.
440
+
441
+ #### if-image-name-on
442
+
443
+ It can be used with all source, storage and file/url output statements.
444
+ The argument will expand `#{variable}`.
445
+
446
+ If specified on or withing given statement it will cause that statement or it's part to be ineffective unless the image name used within the statement is on the list of image names specified as value of the option.
447
+ The list is in format `image name[,image name]*`.
448
+
449
+ This option is useful when building API that works on predefined set of image operations and allows to select witch set of operations to perform with list included in the URL.
450
+
451
+ ### Complete example
452
+
453
+ This complete API configuration presents API compatible with previous version of httpimagestore (v0.5.0) and thumbnail on demand API.
454
+
455
+ ```sdl
456
+ s3 key="AIAITCKMELYWQZPJP7HQ" secret="V37lCu0F48Tv9s7QVqIT/sLf/wwqhNSB4B0Em7Ei" ssl=false
457
+
458
+ # Compatibility API
459
+ path "compat-hash" "#{digest}.#{mimeextension}"
460
+ path "compat-hash-name" "#{digest}/#{imagename}.#{mimeextension}"
461
+ path "compat-structured" "#{dirname}/#{digest}/#{basename}.#{mimeextension}"
462
+ path "compat-structured-name" "#{dirname}/#{digest}/#{basename}-#{imagename}.#{mimeextension}"
463
+
464
+ put "thumbnail" ":name_list" ":path/.+/" {
465
+ thumbnail "input" {
466
+ # Make limited source image for migration to on demand API
467
+ "migration" operation="limit" width=2160 height=2160 format="jpeg" quality=95
468
+
469
+ # Backend classes
470
+ "original" operation="crop" width="input" height="input" format="jpeg" options="background-color:0xF0F0F0" if-image-name-on="#{name_list}"
471
+ "search" operation="pad" width=162 height=162 format="jpeg" options="background-color:0xF0F0F0" if-image-name-on="#{name_list}"
472
+ "brochure" operation="pad" width=264 height=264 format="jpeg" options="background-color:0xF0F0F0" if-image-name-on="#{name_list}"
473
+ }
474
+
475
+ # Store migartion source image into on demand API bucket
476
+ store_s3 "migration" bucket="mybucket_v1" path="hash"
477
+
478
+ # Save input image for future reprocessing
479
+ store_s3 "input" bucket="mybucket" path="compat-structured" public=true
480
+
481
+ # Backend classes
482
+ store_s3 "original" bucket="mybucket" path="compat-structured-name" public=true cache-control="public, max-age=31557600, s-maxage=0" if-image-name-on="#{name_list}"
483
+ store_s3 "search" bucket="mybucket" path="compat-structured-name" public=true cache-control="public, max-age=31557600, s-maxage=0" if-image-name-on="#{name_list}"
484
+ store_s3 "brochure" bucket="mybucket" path="compat-structured-name" public=true cache-control="public, max-age=31557600, s-maxage=0" if-image-name-on="#{name_list}"
485
+
486
+ output_store_url {
487
+ "input"
488
+ "original" if-image-name-on="#{name_list}"
489
+ "search" if-image-name-on="#{name_list}"
490
+ "brochure" if-image-name-on="#{name_list}"
491
+ }
492
+ }
493
+
494
+ put "thumbnail" ":name_list" {
495
+ thumbnail "input" {
496
+ # Make limited source image for migration to on demand API
497
+ "migration" operation="limit" width=2160 height=2160 format="jpeg" quality=95
498
+
499
+ # Backend classes
500
+ "original" operation="crop" width="input" height="input" format="jpeg" options="background-color:0xF0F0F0" if-image-name-on="#{name_list}"
501
+ "search" operation="pad" width=162 height=162 format="jpeg" options="background-color:0xF0F0F0" if-image-name-on="#{name_list}"
502
+ "brochure" operation="pad" width=264 height=264 format="jpeg" options="background-color:0xF0F0F0" if-image-name-on="#{name_list}"
503
+ }
504
+
505
+ # Store migartion source image into on demand API bucket
506
+ store_s3 "migration" bucket="mybucket_v1" path="hash"
507
+
508
+ # Save input image for future reprocessing
509
+ store_s3 "input" bucket="mybucket" path="compat-hash" public=true
510
+
511
+ # Backend classe
512
+ store_s3 "original" bucket="mybucket" path="compat-hash-name" public=true cache-control="public, max-age=31557600, s-maxage=0" if-image-name-on="#{name_list}"
513
+ store_s3 "search" bucket="mybucket" path="compat-hash-name" public=true cache-control="public, max-age=31557600, s-maxage=0" if-image-name-on="#{name_list}"
514
+ store_s3 "brochure" bucket="mybucket" path="compat-hash-name" public=true cache-control="public, max-age=31557600, s-maxage=0" if-image-name-on="#{name_list}"
515
+
516
+ output_store_url {
517
+ "input"
518
+ "original" if-image-name-on="#{name_list}"
519
+ "search" if-image-name-on="#{name_list}"
520
+ "brochure" if-image-name-on="#{name_list}"
521
+ }
522
+ }
523
+
524
+ # Thumbnail on demand API
525
+ path "hash" "#{digest}.#{mimeextension}"
526
+ path "path" "#{path}"
527
+
528
+ put "v1" "original" {
529
+ thumbnail "input" "original" operation="limit" width=2160 height=2160 format="jpeg" quality=95
530
+
531
+ store_s3 "original" bucket="mybucket_v1" path="hash"
532
+
533
+ output_store_path "original"
534
+ }
535
+
536
+ get "v1" "thumbnail" ":path" ":operation" ":width" ":height" ":options?" {
537
+ source_s3 "original" bucket="mybucket_v1" path="path"
538
+
539
+ thumbnail "original" "thumbnail" operation="#{operation}" width="#{width}" height="#{height}" options="#{options}" quality=84 format="jpeg"
540
+
541
+ output_image "thumbnail" cache-control="public, max-age=31557600, s-maxage=0"
542
+ }
543
+ ```
544
+
545
+ Compatibility API works by storing input image and selected (via URI) classes of thumbnails generated during image upload. Once the image is uploaded thumbnails can be served directly from S3. There are two endpoints defined for that API to handle URIs that contain optional image storage name that results in usage of different storage key.
546
+
547
+ With thumbnail on demand API user uploads original image. It is converted to JPEG and if it is too large also scaled down. Than that processed version is stored in S3 under key composed from hash of input image data and final image extension. Client will receive storage key for further reference in the response body. To obtain thumbnail **GET** request with obtained key and thumbnail parameters encoded in the URI needs to be send to the sever. It will read parameters from the URI and source selected image from S3. That image is then thumbnailed in the backend and sent back to client with custom Cache-Control header.
548
+
549
+ Note that Compatibility API will also store "migarion" image in bucket used by on demand API. This allows for migration from that API to on demand API.
550
+
551
+ Compatibility API example:
552
+
553
+ ```bash
554
+ # Uploading image and thumbnailing to 'original' and 'brochure' classes
555
+ $ curl -X PUT 10.1.1.24:3000/thumbnail/original,brochure -q --data-binary @Pictures/compute.jpg
556
+ http://s3-eu-west-1.amazonaws.com/test.my.bucket/4006450256177f4a.jpg
557
+ http://s3-eu-west-1.amazonaws.com/test.my.bucket/4006450256177f4a/original.jpg
558
+ http://s3-eu-west-1.amazonaws.com/test.my.bucket/4006450256177f4a/brochure.jpg
559
+
560
+ # Obtaining 'brochure' class thumbnail
561
+ $ curl http://s3-eu-west-1.amazonaws.com/test.my.bucket/4006450256177f4a/brochure.jpg -v -s -o /tmp/test.jpg
562
+ * About to connect() to s3-eu-west-1.amazonaws.com port 80 (#0)
563
+ * Trying 178.236.7.32... connected
564
+ > GET /test.my.bucket/4006450256177f4a/brochure.jpg HTTP/1.1
565
+ > User-Agent: curl/7.22.0 (x86_64-apple-darwin10.8.0) libcurl/7.22.0 OpenSSL/1.0.1c zlib/1.2.7 libidn/1.25
566
+ > Host: s3-eu-west-1.amazonaws.com
567
+ > Accept: */*
568
+ >
569
+ < HTTP/1.1 200 OK
570
+ < x-amz-id-2: ZXJSWlUBthbIoUXztc9GkSu7mhpGK5HK+sVXWPdbCX9+a3nVkr4A6pclH1kdKjM9
571
+ < x-amz-request-id: 3DD4C96B6B55B4ED
572
+ < Date: Thu, 11 Jul 2013 11:33:31 GMT
573
+ < Cache-Control: public, max-age=31557600, s-maxage=0
574
+ < Last-Modified: Thu, 11 Jul 2013 11:31:36 GMT
575
+ < ETag: "cf060f47d557bcf9316554d34411dc51"
576
+ < Accept-Ranges: bytes
577
+ < Content-Type: image/jpeg
578
+ < Content-Length: 39458
579
+ < Server: AmazonS3
580
+ <
581
+ { [data not shown]
582
+ * Connection #0 to host s3-eu-west-1.amazonaws.com left intact
583
+ * Closing connection #0
584
+
585
+ $ identify /tmp/test.jpg
586
+ /tmp/test.jpg JPEG 264x264 264x264+0+0 8-bit sRGB 11.9KB 0.000u 0:00.009
587
+ ```
588
+
589
+ On demand API example:
590
+
591
+ ```bash
592
+ # Uploading image
593
+ $ curl -X PUT 10.1.1.24:3000/v1/original -q --data-binary @Pictures/compute.jpg
594
+ 4006450256177f4a.jpg
595
+
596
+ # Obtainig fit method 100x1000 thumbnail to /tmp/test.jpg
597
+ $ curl 10.1.1.24:3000/v1/thumbnail/4006450256177f4a.jpg/fit/100/1000 -v -s -o /tmp/test.jpg
598
+ * About to connect() to 10.1.1.24 port 3000 (#0)
599
+ * Trying 10.1.1.24... connected
600
+ > GET /v1/thumbnail/4006450256177f4a.jpg/fit/100/1000 HTTP/1.1
601
+ > User-Agent: curl/7.22.0 (x86_64-apple-darwin10.8.0) libcurl/7.22.0 OpenSSL/1.0.1c zlib/1.2.7 libidn/1.25
602
+ > Host: 10.1.1.24:3000
603
+ > Accept: */*
604
+ >
605
+ < HTTP/1.1 200 OK
606
+ < Server: nginx/1.2.9
607
+ < Date: Thu, 11 Jul 2013 11:26:15 GMT
608
+ < Content-Type: image/jpeg
609
+ < Content-Length: 4681
610
+ < Connection: keep-alive
611
+ < Status: 200 OK
612
+ < Cache-Control: public, max-age=31557600, s-maxage=0
613
+ <
614
+ { [data not shown]
615
+ * Connection #0 to host 10.1.1.24 left intact
616
+ * Closing connection #0
617
+
618
+ $ identify /tmp/test.jpeg
619
+ /tmp/test.jpeg JPEG 100x141 100x141+0+0 8-bit sRGB 4.68KB 0.000u 0:00.000
620
+ ```
621
+
622
+ ## Usage
623
+
624
+ After installation of the gem the `httpimagestore` executable is installed in **PATH**.
625
+ This executable is used to start HTTP Image Service with given configuration file path as it's last argument.
626
+
627
+ ### Stand alone
628
+
629
+ In this mode `httpimagestore` daemon is listening on TCP port directly. This is the easiest way you can start the daemon but it is not recommended for production use.
630
+ It is recommended to use [nginx](http://nginx.org) server in front of this daemon in production to buffer requests and responses.
631
+
632
+ To start this daemon in foreground for testing purposes with prepared `api.conf` configuration file use:
633
+
634
+ ```bash
635
+ httpimagestore --verbose --foreground api.conf
636
+ ```
637
+
638
+ Hitting Ctrl-C will ask the server to shutdown.
639
+
640
+ If you start it without `--foreground` switch the daemon will fork into background and write it's PID in `httpimagestore.pid` by default.
641
+
642
+ Note that in order to perform thumbnailing [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) needs to be running.
643
+
644
+ ### Options
645
+
646
+ You can run `httpimagestore --help` to display all available switches, options and arguments.
647
+
648
+ PID file location can be controlled with `--pid-file` options.
649
+
650
+ To change number of worker processes use `--worker-processes`.
651
+ You can also change time after witch worker process will be killed if it didn't provide response to request with `--worker-timeout`.
652
+
653
+ By default `httpimagestore` will not keep more than 128MiB of image data in memory - if this is exceeded the daemon will abort processing and send response with 413 status. The limit can be changed with `--limit-memory` option.
654
+
655
+ `--listener` can be used multiple times to define listening sockets; use `--listener /var/run/httpimagestore.sock` to listen on UNIX socket instead of default TCP port **3000**.
656
+
657
+ If running as root you can use `--user` option to specify user with whose privileges the worker processes will be running.
658
+
659
+ ### Logging
660
+
661
+ `httpimagestore` logs to `httpimagestore.log` file in current directory by default. You can change log file location with `--log-file` option and verbosity with `--verbose` or `--debug` switch.
662
+
663
+ Additionally `httpimagestore` will log requests in [common NCSA format](http://en.wikipedia.org/wiki/Common_Log_Format) to `httpimagestore_access.log` file. Use `--access-log-file` option to change location of access log.
664
+
665
+ ### Running with nginx
666
+
667
+ [nginx](http://nginx.org) if configured properly will buffer incoming requests before sending them to the backend and server response before sending them to client.
668
+ Since `httpimagestore` is based on [Unicorn HTTP server](http://unicorn.bogomips.org) that is based on single threaded HTTP request processing worker processes the number of processing threads is very limited. Slow clients could keep precious process busy for long time slowly sending request or reading response effectively rendering service unavailable.
669
+
670
+ Starting `httpimagestore` daemon with UNIX socket listener and `/etc/httpimagestore.conf` configuration file:
671
+
672
+ ```bash
673
+ httpimagestore --pid-file /var/run/httpimagestore/pidfile --log-file /var/log/httpimagestore/httpimagestore.log --access-log-file /var/log/httpimagestore/httpimagestore_access.log --listener /var/run/httpimagestore.sock --user httpimagestore /etc/httpimagestore.conf
674
+ ```
675
+
676
+ Starting `httpthumbnailer` daemon:
677
+
678
+ ```bash
679
+ httpthumbnailer --pid-file /var/run/httpthumbnailer/pidfile --log-file /var/log/httpthumbnailer/httpthumbnailer.log --access-log-file /var/log/httpthumbnailer/httpthumbnailer_access.log --listener 127.0.0.1:3100 --user httpthumbnailer /etc/httpthumbnailer.conf
680
+ ```
681
+
682
+ To start [nginx](http://nginx.org) we need to configure it to run as reverse HTTP proxy for our UNIX socket based `httpimagestore` backend.
683
+ Also we set it up so that it does request and response buffering.
684
+ Here is the example `/etc/nginx/nginx.conf` file:
685
+
686
+ ```nginx
687
+ user nginx;
688
+ worker_processes 1;
689
+
690
+ error_log /var/log/nginx/error.log error;
691
+
692
+ pid /var/run/nginx.pid;
693
+
694
+ events {
695
+ worker_connections 1024;
696
+ }
697
+
698
+ http {
699
+ include /etc/nginx/mime.types;
700
+ default_type application/octet-stream;
701
+
702
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
703
+ '$status $body_bytes_sent "$http_referer" '
704
+ '"$http_user_agent" "$http_x_forwarded_for" $request_time';
705
+
706
+ access_log /var/log/nginx/access.log main;
707
+
708
+ sendfile on;
709
+ tcp_nopush on;
710
+ tcp_nodelay off;
711
+
712
+ keepalive_timeout 600s;
713
+ client_header_timeout 10s;
714
+
715
+ upstream httpimagestore {
716
+ server unix:/var/run/httpimagestore.sock fail_timeout=0;
717
+ }
718
+
719
+ server {
720
+ listen *:3000;
721
+ server_name localhost;
722
+
723
+ location / {
724
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
725
+ proxy_set_header X-Forwarded-Proto $scheme;
726
+ proxy_set_header Host $http_host;
727
+
728
+ client_body_buffer_size 16m;
729
+ client_max_body_size 128m;
730
+
731
+ proxy_buffering on;
732
+ proxy_buffer_size 64k;
733
+ proxy_buffers 256 64k;
734
+ proxy_busy_buffers_size 256k;
735
+ proxy_temp_file_write_size 128m;
736
+
737
+ proxy_read_timeout 120s;
738
+ proxy_connect_timeout 10s;
739
+
740
+ proxy_pass http://httpimagestore;
741
+ }
742
+ }
743
+ }
744
+ ```
745
+
746
+ Now it can be (re)started via usual init.d or systemd.
747
+
748
+ ## Status codes
749
+
750
+ HTTP Image Store will respond with different status codes on different situations.
751
+ If all goes well `200 OK` will be returned otherwise:
752
+
753
+ ### 400
754
+
755
+ * bad thumbnail specification
756
+ * empty body when image data expected
757
+
758
+ ### 404
759
+
760
+ * no API endpoint found for given URL
761
+ * file not found
762
+ * S3 bucket key not found
763
+
764
+ ### 413
765
+
766
+ * uploaded image is too big to fit in memory
767
+ * request body is too long
768
+ * too much image data is loaded in memory
769
+ * memory or pixel cache limit in the thumbnailer backend has been exhausted
770
+
771
+ ### 415
772
+
773
+ * [HTTP Thumbnailer](https://github.com/jpastuszek/httpthumbnailer) backend cannot decode input image - see supported formats by the backend
774
+
775
+ ### 500
776
+
777
+ * may be caused by configuration error
778
+ * unexpected error has occurred - see the log file
779
+
780
+ ## Statistics API
781
+
782
+ HTTP Image Store comes with statistics API that shows various runtime collected statistics.
783
+ It is set up under `/stats` URI. You can also request single stat with `/stats/<stat name>` request.
784
+
785
+ Example:
786
+
787
+ ```bash
788
+ $ curl 10.1.1.24:3000/stats
789
+ total_requests: 2
790
+ total_errors: 0
791
+ calling: 1
792
+ writing: 0
793
+ total_write_multipart: 0
794
+ total_write: 1
795
+ total_write_part: 0
796
+ total_write_error: 0
797
+ total_write_error_part: 0
798
+ total_thumbnail_requests: 1
799
+ total_thumbnail_requests_bytes: 43308
800
+ total_thumbnail_thumbnails: 3
801
+ total_thumbnail_thumbnails_bytes: 102914
802
+ total_file_store: 0
803
+ total_file_store_bytes: 0
804
+ total_file_source: 0
805
+ total_file_source_bytes: 0
806
+ total_s3_store: 4
807
+ total_s3_store_bytes: 146222
808
+ total_s3_source: 1
809
+ total_s3_source_bytes: 51581
810
+
811
+ $ curl 10.1.1.24:3000/stats/total_s3_source
812
+ 1
813
+ ```
814
+
815
+ ## Contributing to HTTP Image Store
816
+
817
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
818
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
819
+ * Fork the project
820
+ * Start a feature/bugfix branch
821
+ * Commit and push until you are happy with your contribution
822
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
823
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
824
+
825
+ ## Copyright
826
+
827
+ Copyright (c) 2013 Jakub Pastuszek. See LICENSE.txt for
828
+ further details.
829
+