httpimagestore 0.5.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -12
- data/Gemfile.lock +57 -55
- data/README.md +829 -0
- data/VERSION +1 -1
- data/bin/httpimagestore +114 -180
- data/features/cache-control.feature +26 -90
- data/features/compatibility.feature +129 -0
- data/features/error-reporting.feature +207 -0
- data/features/health-check.feature +30 -0
- data/features/s3-store-and-thumbnail.feature +65 -0
- data/features/step_definitions/httpimagestore_steps.rb +66 -26
- data/features/support/env.rb +32 -5
- data/features/support/test.empty +0 -0
- data/httpimagestore.gemspec +60 -47
- data/lib/httpimagestore/aws_sdk_regions_hack.rb +23 -0
- data/lib/httpimagestore/configuration/file.rb +120 -0
- data/lib/httpimagestore/configuration/handler.rb +239 -0
- data/lib/httpimagestore/configuration/output.rb +119 -0
- data/lib/httpimagestore/configuration/path.rb +77 -0
- data/lib/httpimagestore/configuration/s3.rb +194 -0
- data/lib/httpimagestore/configuration/thumbnailer.rb +244 -0
- data/lib/httpimagestore/configuration.rb +126 -29
- data/lib/httpimagestore/error_reporter.rb +36 -0
- data/lib/httpimagestore/ruby_string_template.rb +26 -0
- data/load_test/load_test.1k.23a022f6e.m1.small-comp.csv +3 -0
- data/load_test/load_test.1k.ec9bde794.m1.small.csv +4 -0
- data/load_test/load_test.jmx +344 -0
- data/load_test/thumbnail_specs.csv +11 -0
- data/spec/configuration_file_spec.rb +309 -0
- data/spec/configuration_handler_spec.rb +124 -0
- data/spec/configuration_output_spec.rb +338 -0
- data/spec/configuration_path_spec.rb +92 -0
- data/spec/configuration_s3_spec.rb +571 -0
- data/spec/configuration_spec.rb +80 -105
- data/spec/configuration_thumbnailer_spec.rb +417 -0
- data/spec/ruby_string_template_spec.rb +43 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/support/compute.jpg +0 -0
- data/spec/support/cuba_response_env.rb +40 -0
- data/spec/support/full.cfg +49 -0
- metadata +138 -84
- data/README.rdoc +0 -23
- data/features/httpimagestore.feature +0 -167
- data/lib/httpimagestore/image_path.rb +0 -54
- data/lib/httpimagestore/s3_service.rb +0 -37
- data/lib/httpimagestore/thumbnail_class.rb +0 -13
- data/spec/image_path_spec.rb +0 -72
- data/spec/test.cfg +0 -8
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/bin/httpimagestore
CHANGED
@@ -1,200 +1,134 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require 'cli'
|
5
|
-
|
6
|
-
cli_settings = CLI.new do
|
7
|
-
description 'HTTP based image storage and thumbnailer'
|
8
|
-
|
9
|
-
switch :no_bind,
|
10
|
-
description: "Do not bind to TCP socket - useful with -s fastcgi option"
|
11
|
-
switch :no_logging,
|
12
|
-
description: "Disable logging"
|
13
|
-
switch :debug,
|
14
|
-
description: "Enable debugging"
|
15
|
-
switch :no_uri_encode,
|
16
|
-
description: "Disable output URI encoding"
|
17
|
-
|
18
|
-
option :bind,
|
19
|
-
short: :b,
|
20
|
-
default: IP.new('127.0.0.1'),
|
21
|
-
cast: IP,
|
22
|
-
description: "HTTP server bind address - use 0.0.0.0 to bind to all interfaces"
|
23
|
-
option :port,
|
24
|
-
short: :p,
|
25
|
-
default: 3000,
|
26
|
-
cast: Integer,
|
27
|
-
description: "HTTP server TCP port"
|
28
|
-
option :server,
|
29
|
-
short: :s,
|
30
|
-
default: 'mongrel',
|
31
|
-
description: "Rack server handler like thin, mongrel, webrick, fastcgi etc."
|
32
|
-
option :upload_retry_times,
|
33
|
-
default: 3,
|
34
|
-
cast: Integer,
|
35
|
-
description: "Number of times file upload will be retried in case of an error"
|
36
|
-
option :upload_retry_delay,
|
37
|
-
default: 0.0,
|
38
|
-
cast: Float,
|
39
|
-
description: "Time to sleep between retries"
|
40
|
-
options :cache_control,
|
41
|
-
default: "public, max-age=#{(86400 * 365.25).to_i}",
|
42
|
-
description: "Stored object Cache-Control header value - can be used multiple times to specify flag by flag"
|
43
|
-
|
44
|
-
argument :config,
|
45
|
-
cast: Pathname,
|
46
|
-
description: "Configuration file path"
|
47
|
-
end.parse!
|
48
|
-
|
49
|
-
require 'sinatra/base'
|
50
|
-
require 'httpthumbnailer-client'
|
51
|
-
require 'digest/sha2'
|
2
|
+
require 'unicorn-cuba-base'
|
3
|
+
require 'base64'
|
52
4
|
|
53
5
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
54
|
-
require 'httpimagestore/configuration'
|
55
|
-
require 'httpimagestore/image_path'
|
56
|
-
require 'httpimagestore/s3_service'
|
57
|
-
|
58
|
-
sinatra = Sinatra.new
|
59
|
-
|
60
|
-
unless cli_settings.no_bind
|
61
|
-
sinatra.set :port, cli_settings.port
|
62
|
-
sinatra.set :bind, cli_settings.bind.to_s
|
63
|
-
else
|
64
|
-
sinatra.set :port, nil
|
65
|
-
sinatra.set :bind, nil
|
66
|
-
end
|
67
|
-
sinatra.set :environment, 'production'
|
68
|
-
sinatra.set :server, cli_settings.server
|
69
|
-
sinatra.set :lock, true
|
70
|
-
sinatra.set :logging, (not cli_settings.no_logging)
|
71
|
-
sinatra.set :debug, cli_settings.debug
|
72
|
-
sinatra.set :uri_encode, (not cli_settings.no_uri_encode)
|
73
|
-
sinatra.set :upload_retry_times, cli_settings.upload_retry_times
|
74
|
-
sinatra.set :upload_retry_delay, cli_settings.upload_retry_delay
|
75
|
-
sinatra.set :cache_control, cli_settings.cache_control
|
76
|
-
|
77
|
-
Configuration.from_file(cli_settings.config).put(sinatra)
|
78
|
-
|
79
|
-
class ThumbnailingError < RuntimeError
|
80
|
-
def initialize(thumbnail_class, remote_error)
|
81
|
-
@remote_error = remote_error
|
82
|
-
super "Thumbnailing for class '#{thumbnail_class.name}' failed: #{remote_error.message}"
|
83
|
-
end
|
84
|
-
|
85
|
-
attr_reader :remote_error
|
86
|
-
end
|
87
|
-
|
88
|
-
sinatra.helpers do
|
89
|
-
def hatl_response(status, msg)
|
90
|
-
headers "Content-Type" => "text/plain"
|
91
|
-
body msg.gsub("\n", "\r\n") + "\r\n"
|
92
|
-
halt status
|
93
|
-
end
|
94
|
-
|
95
|
-
def halt_exception(status, exception)
|
96
|
-
msg = "Error: #{exception.class.name}: #{exception}"
|
97
|
-
if status >= 500
|
98
|
-
logger.error msg + (exception.respond_to?(:response) ? ': ' + exception.response.inspect : '')
|
99
|
-
else
|
100
|
-
logger.warn msg
|
101
|
-
end
|
102
|
-
hatl_response(status, msg)
|
103
|
-
end
|
104
6
|
|
105
|
-
|
106
|
-
|
7
|
+
Application.new('httpimagestore', port: 3000, processor_count_factor: 2) do
|
8
|
+
cli do
|
9
|
+
description 'HTTP based image storage and thumbnailer'
|
10
|
+
argument :config,
|
11
|
+
cast: Pathname,
|
12
|
+
description: 'configuration file path'
|
13
|
+
version (Pathname.new(__FILE__).dirname + '..' + 'VERSION').read
|
107
14
|
end
|
108
|
-
end
|
109
15
|
|
110
|
-
|
111
|
-
|
112
|
-
logger.level = Logger::DEBUG if settings.logging and settings.debug
|
113
|
-
# singletons
|
114
|
-
$s3 ||= S3Service.new(settings.s3_key_id, settings.s3_key_secret, settings.s3_bucket,
|
115
|
-
cache_control: settings.cache_control,
|
116
|
-
logger: logger,
|
117
|
-
upload_retry_times: settings.upload_retry_times,
|
118
|
-
upload_retry_delay: settings.upload_retry_delay
|
119
|
-
)
|
120
|
-
$thumbnailer ||= HTTPThumbnailerClient.new(settings.thumbnailer_url)
|
121
|
-
rescue => exception
|
122
|
-
halt_exception(500, exception)
|
16
|
+
settings do |settings|
|
17
|
+
Controler.settings[:config_file] = settings.config
|
123
18
|
end
|
124
|
-
end
|
125
|
-
|
126
|
-
sinatra.get '/' do
|
127
|
-
"hello"
|
128
|
-
end
|
129
|
-
|
130
|
-
sinatra.put %r{/thumbnail/([^/]*)/?(.*)} do |thumbnail_classes, image_path|
|
131
|
-
thumbnail_classes = thumbnail_classes.split(',').map{|thumbnail_class| settings.thumbnail_classes[thumbnail_class]}
|
132
19
|
|
133
|
-
|
134
|
-
|
20
|
+
main do |settings|
|
21
|
+
require 'httpimagestore/error_reporter'
|
22
|
+
|
23
|
+
class HTTPImageStore < Controler
|
24
|
+
extend Stats
|
25
|
+
def_stats(
|
26
|
+
:workers,
|
27
|
+
:total_requests,
|
28
|
+
:total_errors
|
29
|
+
)
|
30
|
+
|
31
|
+
raindrops_stats = Raindrops::Middleware::Stats.new
|
32
|
+
self.use Raindrops::Middleware, stats: raindrops_stats
|
33
|
+
|
34
|
+
StatsReporter << HTTPImageStore.stats
|
35
|
+
StatsReporter << raindrops_stats
|
36
|
+
StatsReporter << Plugin::ResponseHelpers.stats
|
37
|
+
|
38
|
+
self.define do
|
39
|
+
HTTPImageStore.stats.incr_total_requests
|
40
|
+
on error? do
|
41
|
+
HTTPImageStore.stats.incr_total_errors
|
42
|
+
run ErrorReporter
|
43
|
+
end
|
44
|
+
|
45
|
+
on 'stats' do
|
46
|
+
run StatsReporter
|
47
|
+
end
|
48
|
+
|
49
|
+
on 'health_check' do
|
50
|
+
log.debug "health_check"
|
51
|
+
if client = env['app.configuration'].thumbnailer
|
52
|
+
# 8x8 PNG
|
53
|
+
data = Base64.decode64('iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAI0lEQVQI1z3KMQoAMAyAwEv//2c7pFQQHISqssXaQWby+NsFYkkV7w+CVgAAAAAASUVORK5CYII=')
|
54
|
+
|
55
|
+
begin
|
56
|
+
thumbnail = client.thumbnail(data, 'fit', 4, 4, 'jpeg')
|
57
|
+
unless thumbnail.data.length > 300 and thumbnail.data.include? 'JFIF'
|
58
|
+
write_plain 502, 'bad image data returned from thumbnailer'
|
59
|
+
halt res.finish
|
60
|
+
end
|
61
|
+
rescue Errno::ECONNREFUSED => error
|
62
|
+
write_error 502, error
|
63
|
+
halt res.finish
|
64
|
+
end
|
65
|
+
end
|
66
|
+
write_plain 200, 'HTTP Image Store OK'
|
67
|
+
end
|
68
|
+
|
69
|
+
env['app.configuration'].handlers.each do |handler|
|
70
|
+
on eval(handler.http_method), *handler.uri_matchers.map{|m| instance_eval(&m.matcher)} do |*args|
|
71
|
+
locals = {}
|
72
|
+
|
73
|
+
{
|
74
|
+
path: env["PATH_INFO"][1..-1] || ''
|
75
|
+
}.merge(
|
76
|
+
Hash[handler.uri_matchers.select{|m| m.name}.map{|m| m.name}.zip(args)]
|
77
|
+
).each do |name, value|
|
78
|
+
locals[name] = URI.decode(value).force_encoding('UTF-8')
|
79
|
+
end
|
80
|
+
|
81
|
+
state = Configuration::RequestState.new(req.body.read, locals, memory_limit)
|
82
|
+
|
83
|
+
handler.image_sources.each do |image_source|
|
84
|
+
image_source.realize(state) unless image_source.respond_to? :excluded? and image_source.excluded?(state)
|
85
|
+
end
|
86
|
+
handler.stores.each do |store|
|
87
|
+
store.realize(state) unless store.respond_to? :excluded? and store.excluded?(state)
|
88
|
+
end
|
89
|
+
handler.output.realize(state)
|
90
|
+
|
91
|
+
instance_eval &state.output_callback
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
on root do
|
96
|
+
write_plain 200, 'HTTP Image Store'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
135
100
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
101
|
+
class Configurator
|
102
|
+
def initialize(app, configuration)
|
103
|
+
@app = app
|
104
|
+
@configuration = configuration
|
105
|
+
end
|
141
106
|
|
142
|
-
|
143
|
-
|
144
|
-
|
107
|
+
def call(env)
|
108
|
+
env['app.configuration'] = @configuration
|
109
|
+
@app.call(env)
|
110
|
+
end
|
145
111
|
end
|
146
|
-
end
|
147
112
|
|
148
|
-
|
149
|
-
thumbnails.zip(thumbnail_classes).each do |thumbnail, thumbnail_class|
|
150
|
-
raise ThumbnailingError.new(thumbnail_class, thumbnail) if thumbnail.kind_of? HTTPThumbnailerClient::ThumbnailingError
|
151
|
-
end
|
113
|
+
require 'httpimagestore/configuration'
|
152
114
|
|
153
|
-
|
115
|
+
# connect Scope tree with Controler logger
|
116
|
+
Configuration::Scope.logger = Controler.logger_for(Configuration::Scope)
|
154
117
|
|
155
|
-
|
156
|
-
|
118
|
+
# load builin supported set
|
119
|
+
require 'httpimagestore/configuration/path'
|
120
|
+
require 'httpimagestore/configuration/handler'
|
121
|
+
require 'httpimagestore/configuration/thumbnailer'
|
122
|
+
require 'httpimagestore/configuration/file'
|
123
|
+
require 'httpimagestore/configuration/output'
|
124
|
+
require 'httpimagestore/configuration/s3'
|
157
125
|
|
158
|
-
|
159
|
-
|
126
|
+
HTTPImageStore.use Configurator, Configuration.from_file(settings.config)
|
127
|
+
HTTPImageStore
|
160
128
|
end
|
161
129
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
body urls.map{|u| settings.uri_encode ? URI.encode(u) : u}.map{|u| u + "\r\n"}.join
|
166
|
-
end
|
167
|
-
|
168
|
-
sinatra.error HTTPThumbnailerClient::UnsupportedMediaTypeError do
|
169
|
-
halt_exception(415, env['sinatra.error'])
|
170
|
-
end
|
171
|
-
|
172
|
-
sinatra.error HTTPThumbnailerClient::ImageTooLargeError do
|
173
|
-
halt_exception(413, env['sinatra.error'])
|
174
|
-
end
|
175
|
-
|
176
|
-
# problem with singe thumbnail
|
177
|
-
sinatra.error ThumbnailingError do
|
178
|
-
exception = env['sinatra.error']
|
179
|
-
case exception.remote_error.type
|
180
|
-
when "Thumbnailer::ImageTooLargeError"
|
181
|
-
halt_exception(413, exception)
|
182
|
-
else
|
183
|
-
halt_exception(500, exception)
|
130
|
+
after_fork do |server, worker|
|
131
|
+
HTTPImageStore.stats.incr_workers
|
184
132
|
end
|
185
133
|
end
|
186
134
|
|
187
|
-
sinatra.error Configuration::ThumbnailClassDoesNotExistError do
|
188
|
-
halt_exception(404, env['sinatra.error'])
|
189
|
-
end
|
190
|
-
|
191
|
-
sinatra.error Sinatra::NotFound do
|
192
|
-
hatl_response(404, "Resource '#{request.path_info}' not found")
|
193
|
-
end
|
194
|
-
|
195
|
-
sinatra.error do
|
196
|
-
halt_exception(500, env['sinatra.error'])
|
197
|
-
end
|
198
|
-
|
199
|
-
sinatra.run!
|
200
|
-
|
@@ -1,106 +1,42 @@
|
|
1
|
-
Feature:
|
1
|
+
Feature: S3 object Cache-Control header settings
|
2
|
+
S3 objects can have Cache-Control header set during storage.
|
2
3
|
|
3
4
|
Background:
|
4
|
-
Given
|
5
|
-
Given httpimagestore log is empty
|
6
|
-
Given httpthumbnailer log is empty
|
7
|
-
Given httpthumbnailer server is running at http://localhost:3100/
|
8
|
-
Given Content-Type header set to image/autodetect
|
9
|
-
|
10
|
-
@cache-control
|
11
|
-
Scenario: Image files get Cache-Control header with max-age set by default
|
5
|
+
Given S3 settings in AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_S3_TEST_BUCKET environment variables
|
12
6
|
Given httpimagestore server is running at http://localhost:3000/ with the following configuration
|
13
7
|
"""
|
14
|
-
|
15
|
-
s3_bucket 'issthumbtest'
|
8
|
+
s3 key="@AWS_ACCESS_KEY_ID@" secret="@AWS_SECRET_ACCESS_KEY@" ssl=false
|
16
9
|
|
17
|
-
|
18
|
-
"""
|
19
|
-
Given there is no test/image/4006450256177f4a/test.jpg file in S3 bucket
|
20
|
-
And there is no test/image/4006450256177f4a/test-small.jpg file in S3 bucket
|
21
|
-
Given test.jpg file content as request body
|
22
|
-
When I do PUT request http://localhost:3000/thumbnail/small/test/image/test
|
23
|
-
Then response status will be 200
|
24
|
-
And response content type will be text/uri-list
|
25
|
-
And response body will be CRLF ended lines
|
26
|
-
"""
|
27
|
-
http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test.jpg
|
28
|
-
http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test-small.jpg
|
29
|
-
"""
|
30
|
-
And http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test.jpg Cache-Control header will be public, max-age=31557600
|
31
|
-
And http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test-small.jpg Cache-Control header will be public, max-age=31557600
|
10
|
+
path "hash" "#{digest}.#{mimeextension}"
|
11
|
+
path "hash-name" "#{digest}-#{imagename}.#{mimeextension}"
|
32
12
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
s3_key 'AKIAJMUYVYOSACNXLPTQ', 'MAeGhvW+clN7kzK3NboASf3/kZ6a81PRtvwMZj4Y'
|
39
|
-
s3_bucket 'issthumbtest'
|
13
|
+
put "thumbnail" {
|
14
|
+
thumbnail "input" {
|
15
|
+
"no-cache" operation="crop" width=128 height=128 format="jpeg"
|
16
|
+
"cache" operation="crop" width=256 height=256 format="jpeg"
|
17
|
+
}
|
40
18
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
Given test.jpg file content as request body
|
46
|
-
When I do PUT request http://localhost:3000/thumbnail/small/test/image/test
|
47
|
-
Then response status will be 200
|
48
|
-
And response content type will be text/uri-list
|
49
|
-
And response body will be CRLF ended lines
|
50
|
-
"""
|
51
|
-
http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test.jpg
|
52
|
-
http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test-small.jpg
|
19
|
+
store_s3 "input" bucket="@AWS_S3_TEST_BUCKET@" public=true path="hash"
|
20
|
+
store_s3 "no-cache" bucket="@AWS_S3_TEST_BUCKET@" public=true path="hash-name"
|
21
|
+
store_s3 "cache" bucket="@AWS_S3_TEST_BUCKET@" public=true path="hash-name" cache-control="public, max-age=31557600, s-maxage=0"
|
22
|
+
}
|
53
23
|
"""
|
54
|
-
|
55
|
-
And http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test-small.jpg Cache-Control header will be private, max-age=300, s-maxage=300
|
56
|
-
|
57
|
-
@cache-control
|
58
|
-
Scenario: Image files get Cache-Control header defined with argument
|
59
|
-
Given httpimagestore argument --cache-control 'private'
|
60
|
-
Given httpimagestore argument --cache-control 'max-age=300'
|
61
|
-
Given httpimagestore argument --cache-control 's-maxage=300'
|
62
|
-
Given httpimagestore server is running at http://localhost:3000/ with the following configuration
|
63
|
-
"""
|
64
|
-
s3_key 'AKIAJMUYVYOSACNXLPTQ', 'MAeGhvW+clN7kzK3NboASf3/kZ6a81PRtvwMZj4Y'
|
65
|
-
s3_bucket 'issthumbtest'
|
66
|
-
|
67
|
-
thumbnail_class 'small', 'crop', 128, 128
|
68
|
-
"""
|
69
|
-
Given there is no test/image/4006450256177f4a/test.jpg file in S3 bucket
|
70
|
-
And there is no test/image/4006450256177f4a/test-small.jpg file in S3 bucket
|
71
|
-
Given test.jpg file content as request body
|
72
|
-
When I do PUT request http://localhost:3000/thumbnail/small/test/image/test
|
73
|
-
Then response status will be 200
|
74
|
-
And response content type will be text/uri-list
|
75
|
-
And response body will be CRLF ended lines
|
76
|
-
"""
|
77
|
-
http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test.jpg
|
78
|
-
http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test-small.jpg
|
79
|
-
"""
|
80
|
-
And http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test.jpg Cache-Control header will be private, max-age=300, s-maxage=300
|
81
|
-
And http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test-small.jpg Cache-Control header will be private, max-age=300, s-maxage=300
|
24
|
+
Given httpthumbnailer server is running at http://localhost:3100/
|
82
25
|
|
83
26
|
@cache-control
|
84
|
-
Scenario: Image files get Cache-Control header
|
85
|
-
Given
|
86
|
-
|
87
|
-
|
88
|
-
s3_key 'AKIAJMUYVYOSACNXLPTQ', 'MAeGhvW+clN7kzK3NboASf3/kZ6a81PRtvwMZj4Y'
|
89
|
-
s3_bucket 'issthumbtest'
|
90
|
-
|
91
|
-
thumbnail_class 'small', 'crop', 128, 128
|
92
|
-
"""
|
93
|
-
Given there is no test/image/4006450256177f4a/test.jpg file in S3 bucket
|
94
|
-
And there is no test/image/4006450256177f4a/test-small.jpg file in S3 bucket
|
27
|
+
Scenario: Image files get don't get Cache-Control header by default
|
28
|
+
Given there is no 4006450256177f4a.jpg file in S3 bucket
|
29
|
+
And there is no 4006450256177f4a-no-cache.jpg file in S3 bucket
|
30
|
+
And there is no 4006450256177f4a-cache.jpg file in S3 bucket
|
95
31
|
Given test.jpg file content as request body
|
96
|
-
When I do PUT request http://localhost:3000/thumbnail
|
32
|
+
When I do PUT request http://localhost:3000/thumbnail
|
97
33
|
Then response status will be 200
|
98
|
-
And response content type will be text/
|
34
|
+
And response content type will be text/plain
|
99
35
|
And response body will be CRLF ended lines
|
100
36
|
"""
|
101
|
-
|
102
|
-
http://issthumbtest.s3.amazonaws.com/test/image/4006450256177f4a/test-small.jpg
|
37
|
+
OK
|
103
38
|
"""
|
104
|
-
And http
|
105
|
-
And http
|
39
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a.jpg Cache-Control header will not be set
|
40
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a-no-cache.jpg Cache-Control header will not be set
|
41
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a-cache.jpg Cache-Control header will be public, max-age=31557600, s-maxage=0
|
106
42
|
|
@@ -0,0 +1,129 @@
|
|
1
|
+
Feature: Image list based thumbnailing and S3 storage
|
2
|
+
Storage based on URL specified image names to be generated and stored using two different path formats.
|
3
|
+
This configuration should be mostly compatible with pre v1.0 release.
|
4
|
+
|
5
|
+
Background:
|
6
|
+
Given S3 settings in AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_S3_TEST_BUCKET environment variables
|
7
|
+
Given httpimagestore server is running at http://localhost:3000/ with the following configuration
|
8
|
+
"""
|
9
|
+
s3 key="@AWS_ACCESS_KEY_ID@" secret="@AWS_SECRET_ACCESS_KEY@" ssl=false
|
10
|
+
|
11
|
+
path "hash" "#{digest}.#{mimeextension}"
|
12
|
+
path "hash-name" "#{digest}/#{imagename}.#{mimeextension}"
|
13
|
+
path "structured" "#{dirname}/#{digest}/#{basename}.#{mimeextension}"
|
14
|
+
path "structured-name" "#{dirname}/#{digest}/#{basename}-#{imagename}.#{mimeextension}"
|
15
|
+
|
16
|
+
put "thumbnail" ":name_list" ":path/.+/" {
|
17
|
+
thumbnail "input" {
|
18
|
+
"small" operation="crop" width=128 height=128 format="jpeg" if-image-name-on="#{name_list}"
|
19
|
+
"tiny_png" operation="crop" width=32 height=32 format="png" if-image-name-on="#{name_list}"
|
20
|
+
"bad" operation="crop" width=0 height=0 if-image-name-on="#{name_list}"
|
21
|
+
}
|
22
|
+
|
23
|
+
store_s3 "input" bucket="@AWS_S3_TEST_BUCKET@" path="structured" public=true
|
24
|
+
store_s3 "small" bucket="@AWS_S3_TEST_BUCKET@" path="structured-name" public=true if-image-name-on="#{name_list}"
|
25
|
+
store_s3 "tiny_png" bucket="@AWS_S3_TEST_BUCKET@" path="structured-name" public=true if-image-name-on="#{name_list}"
|
26
|
+
|
27
|
+
output_store_url {
|
28
|
+
"input"
|
29
|
+
"small" if-image-name-on="#{name_list}"
|
30
|
+
"tiny_png" if-image-name-on="#{name_list}"
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
put "thumbnail" ":name_list" {
|
35
|
+
thumbnail "input" {
|
36
|
+
"small" operation="crop" width=128 height=128 format="jpeg" if-image-name-on="#{name_list}"
|
37
|
+
"tiny_png" operation="crop" width=32 height=32 format="png" if-image-name-on="#{name_list}"
|
38
|
+
}
|
39
|
+
|
40
|
+
store_s3 "input" bucket="@AWS_S3_TEST_BUCKET@" path="hash" public=true
|
41
|
+
store_s3 "small" bucket="@AWS_S3_TEST_BUCKET@" path="hash-name" public=true if-image-name-on="#{name_list}"
|
42
|
+
store_s3 "tiny_png" bucket="@AWS_S3_TEST_BUCKET@" path="hash-name" public=true if-image-name-on="#{name_list}"
|
43
|
+
|
44
|
+
output_store_url {
|
45
|
+
"input"
|
46
|
+
"small" if-image-name-on="#{name_list}"
|
47
|
+
"tiny_png" if-image-name-on="#{name_list}"
|
48
|
+
"bad" if-image-name-on="#{name_list}"
|
49
|
+
}
|
50
|
+
}
|
51
|
+
"""
|
52
|
+
Given httpthumbnailer server is running at http://localhost:3100/
|
53
|
+
|
54
|
+
@compatibility @test
|
55
|
+
Scenario: Putting original and its thumbnails to S3 bucket
|
56
|
+
Given there is no 4006450256177f4a.jpg file in S3 bucket
|
57
|
+
And there is no 4006450256177f4a/small.jpg file in S3 bucket
|
58
|
+
And there is no 4006450256177f4a/tiny_png.png file in S3 bucket
|
59
|
+
Given test.jpg file content as request body
|
60
|
+
When I do PUT request http://localhost:3000/thumbnail/small,tiny_png
|
61
|
+
Then response status will be 200
|
62
|
+
And response content type will be text/uri-list
|
63
|
+
And response body will be CRLF ended lines
|
64
|
+
"""
|
65
|
+
http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a.jpg
|
66
|
+
http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a/small.jpg
|
67
|
+
http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a/tiny_png.png
|
68
|
+
"""
|
69
|
+
Then http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a.jpg will contain JPEG image of size 509x719
|
70
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a.jpg content type will be image/jpeg
|
71
|
+
Then http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a/small.jpg will contain JPEG image of size 128x128
|
72
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a/small.jpg content type will be image/jpeg
|
73
|
+
Then http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a/tiny_png.png will contain PNG image of size 32x32
|
74
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/4006450256177f4a/tiny_png.png content type will be image/png
|
75
|
+
|
76
|
+
@compatibility
|
77
|
+
Scenario: Putting original and its thumbnails to S3 bucket under custom path
|
78
|
+
Given there is no test/image/4006450256177f4a/test.jpg file in S3 bucket
|
79
|
+
And there is no test/image/4006450256177f4a/test-small.jpg file in S3 bucket
|
80
|
+
And there is no test/image/4006450256177f4a/test-tiny_png.png file in S3 bucket
|
81
|
+
Given test.jpg file content as request body
|
82
|
+
When I do PUT request http://localhost:3000/thumbnail/small,tiny_png/test/image/test
|
83
|
+
Then response status will be 200
|
84
|
+
And response content type will be text/uri-list
|
85
|
+
And response body will be CRLF ended lines
|
86
|
+
"""
|
87
|
+
http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test.jpg
|
88
|
+
http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test-small.jpg
|
89
|
+
http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test-tiny_png.png
|
90
|
+
"""
|
91
|
+
Then http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test.jpg will contain JPEG image of size 509x719
|
92
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test.jpg content type will be image/jpeg
|
93
|
+
Then http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test-small.jpg will contain JPEG image of size 128x128
|
94
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test-small.jpg content type will be image/jpeg
|
95
|
+
Then http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test-tiny_png.png will contain PNG image of size 32x32
|
96
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test-tiny_png.png content type will be image/png
|
97
|
+
|
98
|
+
@compatibility
|
99
|
+
Scenario: Input file extension should be based on content detected mime type and not on provided path
|
100
|
+
Given there is no test/image/4006450256177f4a/test.jpg file in S3 bucket
|
101
|
+
And there is no test/image/4006450256177f4a/test-tiny_png.jpg file in S3 bucket
|
102
|
+
Given test.jpg file content as request body
|
103
|
+
When I do PUT request http://localhost:3000/thumbnail/tiny_png/test/image/test.gif
|
104
|
+
Then response status will be 200
|
105
|
+
And response content type will be text/uri-list
|
106
|
+
And response body will be CRLF ended lines
|
107
|
+
"""
|
108
|
+
http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test.jpg
|
109
|
+
http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test-tiny_png.png
|
110
|
+
"""
|
111
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test.jpg content type will be image/jpeg
|
112
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/image/4006450256177f4a/test-tiny_png.png content type will be image/png
|
113
|
+
|
114
|
+
@compatibility
|
115
|
+
Scenario: Custom path name encoding when UTF-8 characters can be used
|
116
|
+
Given there is no test/图像/4006450256177f4a/测试.jpg file in S3 bucket
|
117
|
+
And there is no test/图像/4006450256177f4a/测试-small.jpg file in S3 bucket
|
118
|
+
Given test.jpg file content as request body
|
119
|
+
When I do PUT request http://localhost:3000/thumbnail/small/test/图像/测试
|
120
|
+
Then response status will be 200
|
121
|
+
And response content type will be text/uri-list
|
122
|
+
And response body will be CRLF ended lines
|
123
|
+
"""
|
124
|
+
http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/%E5%9B%BE%E5%83%8F/4006450256177f4a/%E6%B5%8B%E8%AF%95.jpg
|
125
|
+
http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/%E5%9B%BE%E5%83%8F/4006450256177f4a/%E6%B5%8B%E8%AF%95-small.jpg
|
126
|
+
"""
|
127
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/图像/4006450256177f4a/测试.jpg will contain JPEG image of size 509x719
|
128
|
+
And http://@AWS_S3_TEST_BUCKET@.s3.amazonaws.com/test/图像/4006450256177f4a/测试-small.jpg will contain JPEG image of size 128x128
|
129
|
+
|