akdubya-rack-thumb 0.2.2

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.
@@ -0,0 +1,81 @@
1
+ == Rack::Thumb: Drop-in image thumbnailing for your Rack stack
2
+
3
+ <tt>Rack::Thumb</tt> is drop-in dynamic thumbnailing middleware for Rack-based
4
+ applications, featuring simple configuration, optional security (via url-signing),
5
+ and maximum flexibility.
6
+
7
+ == Getting Started
8
+
9
+ You will need ImageMagick and the Mapel gem (http://github.com/akdubya/mapel).
10
+
11
+ gem install akdubya-rack-thumb
12
+
13
+ # rackup.ru
14
+ require 'myapp'
15
+ require 'rack/thumb'
16
+
17
+ use Rack::Thumb
18
+ use Rack::Static, :urls => ["/media"]
19
+
20
+ run MyApp.new
21
+
22
+ <tt>Rack::Thumb</tt> is file-server agnostic to provide maximum deployment
23
+ flexibility. Simply set it up in front of any application that's capable of
24
+ serving source images (I'm using it with an app that serves images from CouchDB).
25
+
26
+ See the example directory for more <tt>Rack</tt> configurations. Because
27
+ thumbnailing is an expensive operation, you should run <tt>Rack::Thumb</tt>
28
+ behind a cache, such as the excellent <tt>Rack::Cache</tt>.
29
+
30
+ == Rendering Options
31
+
32
+ <tt>Rack::Thumb</tt> intercepts requests for images that have urls of
33
+ the form <code>/path/to/image_{metadata}.ext</code> and returns rendered
34
+ thumbnails. Rendering options include +width+, +height+ and +gravity+. If
35
+ both +width+ and +height+ are supplied, images are cropped and resized
36
+ to fit the aspect ratio.
37
+
38
+ Link to thumbnails from your templates as follows:
39
+
40
+ /media/foobar_50x50.jpg # => Crop and resize to 50x50
41
+ /media/foobar_50x50-nw.jpg # => Crop and resize with northwest gravity
42
+ /media/foobar_50x.jpg # => Resize to a width of 50, preserving AR
43
+ /media/foobar_x50.jpg # => Resize to a height of 50, preserving AR
44
+
45
+ == URL Signing
46
+
47
+ To prevent pesky end-users and bots from flooding your application with
48
+ render requests you can set up <tt>Rack::Thumb</tt> to check for a <tt>SHA-1</tt>
49
+ signature that is unique to every url. Using this option, only thumbnails requested
50
+ by your templates will be valid. Example:
51
+
52
+ use Rack::Thumb(
53
+ :secret => "My secret", # => Don't tell anyone!
54
+ :key_length => "16" # => Only use 16 digits of the SHA-1 key
55
+ )
56
+
57
+ You can then use your +secret+ to generate secure links in your templates using
58
+ Ruby's built-in <tt>Digest::SHA1</tt> library:
59
+
60
+ /media/foobar_50x100-sw-a267c193a7eff046.jpg # => Successful
61
+ /media/foobar_120x250-a267c193a7eff046.jpg # => Returns a bad request error
62
+
63
+ There are no helper modules just yet but it's easy enough to roll your own.
64
+
65
+ == Deep Thoughts
66
+
67
+ <tt>Rack::Thumb</tt> respects any extra headers you set in your downstream app.
68
+ You are free to set caching policies, etc. however you like. Incoming HEAD requests
69
+ skip the rendering step.
70
+
71
+ There are a decent number of specs but the middleware isn't very strict at
72
+ checking options at the moment, and I'm sure there are a few edge cases that need
73
+ to be looked into. Comments, suggestions and bug reports are welcome.
74
+
75
+ == Meta
76
+
77
+ Written by Aleks Williams (http://github.com/akdubya)
78
+
79
+ Released under the MIT License: www.opensource.org/licenses/mit-license.php
80
+
81
+ github.com/akdubya/rack-thumb
@@ -0,0 +1,64 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'fileutils'
7
+
8
+ task :default => [:test]
9
+ task :spec => :test
10
+
11
+ name = 'rack-thumb'
12
+ version = '0.2.2'
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.name = name
16
+ s.version = version
17
+ s.summary = "Drop-in image thumbnailing for your Rack stack."
18
+ s.description = "Drop-in image thumbnailing middleware for your Rack stack (Merb, Sinatra, Rails, etc)."
19
+ s.author = "Aleksander Williams"
20
+ s.email = "alekswilliams@earthlink.net"
21
+ s.homepage = "http://github.com/akdubya/rack-thumb"
22
+ s.platform = Gem::Platform::RUBY
23
+ s.has_rdoc = true
24
+ s.files = %w(Rakefile README.rdoc) + Dir.glob("{lib,spec,example}/**/*")
25
+ s.require_path = "lib"
26
+ s.add_dependency("akdubya-mapel", ">= 0.1.1")
27
+ end
28
+
29
+ Rake::GemPackageTask.new(spec) do |p|
30
+ p.need_tar = true if RUBY_PLATFORM !~ /mswin/
31
+ end
32
+
33
+ desc "Install as a system gem"
34
+ task :install => [ :package ] do
35
+ sh %{sudo gem install pkg/#{name}-#{version}.gem}
36
+ end
37
+
38
+ desc "Uninstall as a system gem"
39
+ task :uninstall => [ :clean ] do
40
+ sh %{sudo gem uninstall #{name}}
41
+ end
42
+
43
+ desc "Create a gemspec file"
44
+ task :make_spec do
45
+ File.open("#{name}.gemspec", "w") do |file|
46
+ file.puts spec.to_ruby
47
+ end
48
+ end
49
+
50
+ Rake::TestTask.new(:test) do |t|
51
+ t.libs << "spec"
52
+ t.test_files = FileList['spec/*_spec.rb']
53
+ t.verbose = true
54
+ end
55
+
56
+ Rake::RDocTask.new do |t|
57
+ t.rdoc_dir = 'rdoc'
58
+ t.title = "Rack Thumb: Drop-in image thumbnailing for your Rack stack"
59
+ t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
60
+ t.options << '--charset' << 'utf-8'
61
+ t.rdoc_files.include('README.rdoc')
62
+ t.rdoc_files.include('lib/rack/rack-thumb.rb')
63
+ t.rdoc_files.include('lib/rack/rack-thumb/*.rb')
64
+ end
@@ -0,0 +1,7 @@
1
+ require 'rack/thumb'
2
+
3
+ use Rack::ShowExceptions
4
+ use Rack::CommonLogger
5
+ use Rack::Thumb
6
+
7
+ run Rack::File.new(::File.dirname(__FILE__) + '/public')
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'thin'
3
+ require 'sinatra'
4
+ require 'rack/cache'
5
+ require 'rack/thumb'
6
+
7
+ use Rack::Cache,
8
+ :metastore => 'file:/var/cache/rack/meta',
9
+ :entitystore => 'file:/var/cache/rack/body'
10
+ use Rack::Thumb
11
+
12
+ get '/' do
13
+ "Hello World!"
14
+ end
@@ -0,0 +1,10 @@
1
+ require 'rack/thumb'
2
+
3
+ use Rack::ShowExceptions
4
+ use Rack::CommonLogger
5
+ use Rack::Thumb
6
+ use Rack::Static, :urls => ["/images"], :root => "public"
7
+
8
+ app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello World!"]] }
9
+
10
+ run app
@@ -0,0 +1,270 @@
1
+ require 'rack'
2
+ require 'mapel'
3
+ require 'digest/sha1'
4
+
5
+ require 'tempfile'
6
+
7
+ Tempfile.class_eval do
8
+ def make_tmpname(basename, n)
9
+ ext = nil
10
+ sprintf("%s%d-%d%s", basename.to_s.gsub(/\.\w+$/) { |s| ext = s; '' }, $$, n, ext)
11
+ end
12
+ end
13
+
14
+ module Rack
15
+
16
+ # The Rack::Thumb middleware intercepts requests for images that have urls of
17
+ # the form <code>/path/to/image_{metadata}.ext</code> and returns rendered
18
+ # thumbnails. Rendering options include +width+, +height+ and +gravity+. If
19
+ # both +width+ and +height+ are supplied, images are cropped and resized
20
+ # to fit the aspect ratio.
21
+ #
22
+ # Rack::Thumb is file-server agnostic to provide maximum deployment
23
+ # flexibility. Simply set it up in front of any downstream application that
24
+ # can serve the source images. Example:
25
+ #
26
+ # # rackup.ru
27
+ # require 'rack/thumb'
28
+ #
29
+ # use Rack::Thumb
30
+ # use Rack::Static, :urls => ["/media"]
31
+ #
32
+ # run MyApp.new
33
+ #
34
+ # See the example directory for more <tt>Rack</tt> configurations. Because
35
+ # thumbnailing is an expensive operation, you should run Rack::Thumb
36
+ # behind a cache, such as <tt>Rack::Cache</tt>.
37
+ #
38
+ # Link to thumbnails from your templates as follows:
39
+ #
40
+ # /media/foobar_50x50.jpg # => Crop and resize to 50x50
41
+ # /media/foobar_50x50-nw.jpg # => Crop and resize with northwest gravity
42
+ # /media/foobar_50x.jpg # => Resize to a width of 50, preserving AR
43
+ # /media/foobar_x50.jpg # => Resize to a height of 50, preserving AR
44
+ #
45
+ # To prevent pesky end-users and bots from flooding your application with
46
+ # render requests you can set up Rack::Thumb to check for a <tt>SHA-1</tt> signature
47
+ # that is unique to every url. Using this option, only thumbnails requested
48
+ # by your templates will be valid. Example:
49
+ #
50
+ # use Rack::Thumb(
51
+ # :secret => "My secret",
52
+ # :key_length => "16" # => Only use 16 digits of the SHA-1 key
53
+ # )
54
+ #
55
+ # You can then use your +secret+ to generate secure links in your templates:
56
+ #
57
+ # /media/foobar_50x100-sw-a267c193a7eff046.jpg # => Successful
58
+ # /media/foobar_120x250-a267c193a7eff046.jpg # => Returns a bad request error
59
+ #
60
+
61
+ class Thumb
62
+ RE_TH_BASE = /_([0-9]+x|x[0-9]+|[0-9]+x[0-9]+)(-(?:nw|n|ne|w|c|e|sw|s|se))?/
63
+ RE_TH_EXT = /(\.(?:jpg|jpeg|png|gif))/i
64
+ TH_GRAV = {
65
+ '-nw' => :northwest,
66
+ '-n' => :north,
67
+ '-ne' => :northeast,
68
+ '-w' => :west,
69
+ '-c' => :center,
70
+ '-e' => :east,
71
+ '-sw' => :southwest,
72
+ '-s' => :south,
73
+ '-se' => :southeast
74
+ }
75
+
76
+ def initialize(app, options={})
77
+ @app = app
78
+ @keylen = options[:keylength]
79
+ @secret = options[:secret]
80
+ @routes = generate_routes(options[:urls] || ["/"], options[:prefix])
81
+ end
82
+
83
+ # Generates routes given a list of prefixes.
84
+ def generate_routes(urls, prefix = nil)
85
+ urls.map do |url|
86
+ prefix = prefix ? Regexp.escape(prefix) : ''
87
+ url = url == "/" ? '' : Regexp.escape(url)
88
+ if @keylen
89
+ /^#{prefix}(#{url}\/.+)#{RE_TH_BASE}-([0-9a-f]{#{@keylen}})#{RE_TH_EXT}$/
90
+ else
91
+ /^#{prefix}(#{url}\/.+)#{RE_TH_BASE}#{RE_TH_EXT}$/
92
+ end
93
+ end
94
+ end
95
+
96
+ def call(env)
97
+ dup._call(env)
98
+ end
99
+
100
+ def _call(env)
101
+ response = catch(:halt) do
102
+ throw :halt unless %w{GET HEAD}.include? env["REQUEST_METHOD"]
103
+ @env = env
104
+ @path = env["PATH_INFO"]
105
+ @routes.each do |regex|
106
+ if match = @path.match(regex)
107
+ @source, dim, grav = extract_meta(match)
108
+ @image = get_source_image
109
+ @thumb = render_thumbnail(dim, grav) unless head?
110
+ serve
111
+ end
112
+ end
113
+ nil
114
+ end
115
+
116
+ response || @app.call(env)
117
+ end
118
+
119
+ # Extracts filename and options from the path.
120
+ def extract_meta(match)
121
+ result = if @keylen
122
+ extract_signed_meta(match)
123
+ else
124
+ extract_unsigned_meta(match)
125
+ end
126
+
127
+ throw :halt unless result
128
+ result
129
+ end
130
+
131
+ # Extracts filename and options from a signed path.
132
+ def extract_signed_meta(match)
133
+ base, dim, grav, sig, ext = match.captures
134
+ digest = Digest::SHA1.hexdigest("#{base}_#{dim}#{grav}#{ext}#{@secret}")[0..@keylen-1]
135
+ throw(:halt, forbidden) unless sig && (sig == digest)
136
+ [base + ext, dim, grav]
137
+ end
138
+
139
+ # Extracts filename and options from an unsigned path.
140
+ def extract_unsigned_meta(match)
141
+ base, dim, grav, ext = match.captures
142
+ [base + ext, dim, grav]
143
+ end
144
+
145
+ # Fetch the source image from the downstream app, returning the downstream
146
+ # app's response if it is not a success.
147
+ def get_source_image
148
+ status, headers, body = @app.call(@env.merge(
149
+ "PATH_INFO" => @source
150
+ ))
151
+
152
+ unless (status >= 200 && status < 300) &&
153
+ (headers["Content-Type"].split("/").first == "image")
154
+ throw :halt, [status, headers, body]
155
+ end
156
+
157
+ @source_headers = headers
158
+
159
+ if !head?
160
+ if body.respond_to?(:path)
161
+ ::File.open(body.path, 'rb')
162
+ elsif body.respond_to?(:each)
163
+ data = ''
164
+ body.each { |part| data << part.to_s }
165
+ Tempfile.new(::File.basename(@path)).tap do |f|
166
+ f.binmode
167
+ f.write(data)
168
+ f.close
169
+ end
170
+ end
171
+ else
172
+ nil
173
+ end
174
+ end
175
+
176
+ # Renders a thumbnail from the source image. Returns a Tempfile.
177
+ def render_thumbnail(dim, grav)
178
+ gravity = grav ? TH_GRAV[grav] : :center
179
+ dimensions = parse_dimensions(dim)
180
+ output = create_tempfile
181
+ cmd = Mapel(@image.path).gravity(gravity)
182
+ width, height = dimensions
183
+ if width && height
184
+ cmd.resize!(width, height)
185
+ else
186
+ cmd.resize(width, height, 0, 0, :>)
187
+ end
188
+ cmd.to(output.path).run
189
+ output
190
+ end
191
+
192
+ # Serves the thumbnail. If this is a HEAD request we strip the body as well
193
+ # as the content length because the render was never run.
194
+ def serve
195
+ lastmod = Time.now.httpdate
196
+ # Use origin content type?
197
+ ctype = Mime.mime_type(::File.extname(@path), 'text/plain')
198
+
199
+ response = if head?
200
+ @source_headers.delete("Content-Length")
201
+ [200, @source_headers.merge(
202
+ "Last-Modified" => lastmod,
203
+ "Content-Type" => ctype
204
+ ), []]
205
+ else
206
+ [200, @source_headers.merge(
207
+ "Last-Modified" => lastmod,
208
+ "Content-Type" => ctype,
209
+ "Content-Length" => ::File.size(@thumb.path).to_s
210
+ ), self]
211
+ end
212
+
213
+ throw :halt, response
214
+ end
215
+
216
+ # Parses the rendering options; returns false if rendering options are invalid
217
+ def parse_dimensions(meta)
218
+ dimensions = meta.split('x').map do |dim|
219
+ if dim.empty?
220
+ nil
221
+ elsif dim[0].to_i == 0
222
+ throw :halt, bad_request
223
+ else
224
+ dim.to_i
225
+ end
226
+ end
227
+ dimensions.any? ? dimensions : throw(:halt, bad_request)
228
+ end
229
+
230
+ def resolve(uri)
231
+ uri = URI.parse(uri) unless uri.respond_to?(:scheme)
232
+ if uri.scheme == "file"
233
+ ::File.expand_path(uri.opaque || uri.path)
234
+ else
235
+ uri.to_s
236
+ end
237
+ end
238
+
239
+ # Creates a new tempfile
240
+ def create_tempfile
241
+ Tempfile.new(::File.basename(@path)).tap { |f| f.close }
242
+ end
243
+
244
+ def bad_request
245
+ body = "Bad thumbnail parameters in #{@path}\n"
246
+ [400, {"Content-Type" => "text/plain",
247
+ "Content-Length" => body.size.to_s},
248
+ [body]]
249
+ end
250
+
251
+ def forbidden
252
+ body = "Bad thumbnail signature in #{@path}\n"
253
+ [403, {"Content-Type" => "text/plain",
254
+ "Content-Length" => body.size.to_s},
255
+ [body]]
256
+ end
257
+
258
+ def head?
259
+ @env["REQUEST_METHOD"] == "HEAD"
260
+ end
261
+
262
+ def each
263
+ ::File.open(@thumb.path, "rb") { |file|
264
+ while part = file.read(8192)
265
+ yield part
266
+ end
267
+ }
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,167 @@
1
+ require File.dirname(__FILE__) + '/helpers'
2
+
3
+ describe "Rack::Thumb Base" do
4
+ before do
5
+ @app = Rack::File.new(::File.dirname(__FILE__))
6
+ end
7
+
8
+ it "should render a thumbnail with width only" do
9
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
10
+
11
+ res = request.get("/media/imagick_50x.jpg")
12
+ res.should.be.ok
13
+ res.content_type.should == "image/jpeg"
14
+ res.content_length.should == 6221
15
+ res.body.bytesize.should == 6221
16
+ end
17
+
18
+ it "should render a thumbnail with height only" do
19
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
20
+
21
+ res = request.get("/media/imagick_x50.jpg")
22
+ res.should.be.ok
23
+ res.content_type.should == "image/jpeg"
24
+ res.content_length.should == 5912
25
+ res.body.bytesize.should == 5912
26
+ end
27
+
28
+ it "should render a thumbnail with width and height (crop-resize)" do
29
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
30
+
31
+ res = request.get("/media/imagick_50x50.jpg")
32
+ res.should.be.ok
33
+ res.content_type.should == "image/jpeg"
34
+ res.content_length.should == 6074
35
+ res.body.bytesize.should == 6074
36
+ end
37
+
38
+ it "should render a thumbnail with width, height and gravity (crop-resize)" do
39
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
40
+
41
+ res = request.get("/media/imagick_50x100-sw.jpg")
42
+ res.should.be.ok
43
+ res.content_type.should == "image/jpeg"
44
+ res.content_length.should == 6696
45
+ res.body.bytesize.should == 6696
46
+ end
47
+
48
+ it "should render a thumbnail with a signature" do
49
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app, :keylength => 16,
50
+ :secret => "test"))
51
+
52
+ sig = Digest::SHA1.hexdigest("/media/imagick_50x100-sw.jpgtest")[0..15]
53
+ res = request.get("/media/imagick_50x100-sw-#{sig}.jpg")
54
+ res.should.be.ok
55
+ res.content_type.should == "image/jpeg"
56
+ res.content_length.should == 6696
57
+ res.body.bytesize.should == 6696
58
+ end
59
+
60
+ it "should not render a thumbnail that exceeds the original image's dimensions" do
61
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
62
+
63
+ res = request.get("/media/imagick_1000x1000.jpg")
64
+ res.should.be.ok
65
+ res.content_type.should == "image/jpeg"
66
+ # There is a miniscule difference between this and the original
67
+ # because this is run through the processor.
68
+ res.content_length.should == 97373
69
+ res.body.bytesize.should == 97373
70
+ end
71
+
72
+ it "should work with non-file source bodies" do
73
+ app = lambda { |env| [200, {"Content-Type" => "image/jpeg"},
74
+ [::File.read(::File.dirname(__FILE__) + "/media/imagick.jpg")]] }
75
+
76
+ request = Rack::MockRequest.new(Rack::Thumb.new(app))
77
+
78
+ res = request.get("/media/imagick_50x.jpg")
79
+ res.should.be.ok
80
+ res.content_type.should == "image/jpeg"
81
+ res.content_length.should == 6221
82
+ res.body.bytesize.should == 6221
83
+ end
84
+
85
+ it "should return forbidden if the signature is invalid" do
86
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app, :keylength => 16,
87
+ :secret => "test"))
88
+
89
+ res = request.get("/media/imagick_50x100-sw-9922d04b14049f85.jpg")
90
+ res.should.be.forbidden
91
+ res.body.should == "Bad thumbnail signature in /media/imagick_50x100-sw-9922d04b14049f85.jpg\n"
92
+ end
93
+
94
+ it "should return bad request if the dimensions are bad" do
95
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
96
+
97
+ res = request.get("/media/imagick_0x50.jpg")
98
+ res.should.be.client_error
99
+ res.body.should == "Bad thumbnail parameters in /media/imagick_0x50.jpg\n"
100
+ end
101
+
102
+ it "should return bad request if dimensions contain leading zeroes" do
103
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
104
+
105
+ res = request.get("/media/imagick_50x050.jpg")
106
+ res.should.be.client_error
107
+ res.body.should == "Bad thumbnail parameters in /media/imagick_50x050.jpg\n"
108
+ end
109
+
110
+ it "should return the application's response if the source file is not found" do
111
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
112
+
113
+ res = request.get("/media/dummy_50x50.jpg")
114
+ res.should.be.not_found
115
+ res.body.should == "File not found: /media/dummy.jpg\n"
116
+ end
117
+
118
+ it "should return the application's response if it does not recognize render options" do
119
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app, :keylength => 16,
120
+ :secret => "test"))
121
+
122
+ res = request.get("/media/imagick_50x50!.jpg")
123
+ res.should.be.not_found
124
+ res.body.should == "File not found: /media/imagick_50x50!.jpg\n"
125
+ end
126
+
127
+ it "should pass non-thumbnail image requests to the application" do
128
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app, :keylength => 16,
129
+ :secret => "test"))
130
+
131
+ res = request.get("/media/imagick.jpg")
132
+ res.should.be.ok
133
+ res.content_type.should == "image/jpeg"
134
+ res.content_length.should == 97374
135
+ res.body.bytesize.should == 97374
136
+ end
137
+
138
+ it "should not render on a HEAD request" do
139
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
140
+
141
+ res = request.request("HEAD", "/media/imagick_50x50.jpg")
142
+ res.should.be.ok
143
+ res.content_type.should == "image/jpeg"
144
+ res.content_length.should.be.nil
145
+ res.body.bytesize.should == 0
146
+ end
147
+
148
+ it "should preserve any extra headers provided by the downstream app" do
149
+ app = lambda { |env| [200, {"X-Foo" => "bar", "Content-Type" => "image/jpeg"},
150
+ ::File.open(::File.dirname(__FILE__) + "/media/imagick.jpg")] }
151
+
152
+ request = Rack::MockRequest.new(Rack::Thumb.new(app))
153
+
154
+ res = request.request("HEAD", "/media/imagick_50x50.jpg")
155
+ res.should.be.ok
156
+ res.content_type.should == "image/jpeg"
157
+ res.content_length.should.be.nil
158
+ res.headers["X-Foo"].should == "bar"
159
+ end
160
+
161
+ it "should forward POST/PUT/DELETE requests to the downstream app" do
162
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
163
+
164
+ res = request.post("/media/imagick_50x50.jpg")
165
+ res.should.not.be.successful
166
+ end
167
+ end
@@ -0,0 +1,9 @@
1
+ require 'bacon'
2
+ require File.dirname(File.dirname(__FILE__)) + '/lib/rack/thumb'
3
+ require 'rack/mock'
4
+
5
+ class String
6
+ def each(*args, &block)
7
+ each_line(*args, &block)
8
+ end
9
+ end
Binary file
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: akdubya-rack-thumb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Aleksander Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-11 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: akdubya-mapel
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.1.1
24
+ version:
25
+ description: Drop-in image thumbnailing middleware for your Rack stack (Merb, Sinatra, Rails, etc).
26
+ email: alekswilliams@earthlink.net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - Rakefile
35
+ - README.rdoc
36
+ - lib/rack
37
+ - lib/rack/thumb.rb
38
+ - spec/media
39
+ - spec/media/imagick.jpg
40
+ - spec/base_spec.rb
41
+ - spec/helpers.rb
42
+ - example/frank.rb
43
+ - example/static.ru
44
+ - example/public
45
+ - example/public/images
46
+ - example/public/images/imagick.jpg
47
+ - example/file.ru
48
+ has_rdoc: true
49
+ homepage: http://github.com/akdubya/rack-thumb
50
+ post_install_message:
51
+ rdoc_options: []
52
+
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.2.0
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: Drop-in image thumbnailing for your Rack stack.
74
+ test_files: []
75
+