rack-thumb 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ /nbproject
2
+ /pkg
3
+ /rdoc
data/README.rdoc ADDED
@@ -0,0 +1,85 @@
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
+ :keylength => "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 setup options at the moment, and I'm sure there are a few edge cases
73
+ that need 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
+ Credit goes to the repoze.bitblt (http://pypi.python.org/pypi/repoze.bitblt)
80
+ team for the clever url-signing implementation. My original security scheme
81
+ was stupidly complex.
82
+
83
+ Released under the MIT License: www.opensource.org/licenses/mit-license.php
84
+
85
+ github.com/akdubya/rack-thumb
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ name = 'rack-thumb'
5
+ version = '0.2.3'
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = name
11
+ gem.version = version
12
+ gem.summary = %Q{Drop-in image thumbnailing for your Rack stack.}
13
+ gem.email = "alekswilliams@earthlink.net"
14
+ gem.homepage = "http://github.com/akdubya/rack-thumb"
15
+ gem.authors = ["Aleksander Williams"]
16
+ gem.add_dependency "mapel", ">= 0.1.6"
17
+ gem.add_development_dependency "bacon", ">= 0"
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:spec) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.pattern = 'spec/**/*_spec.rb'
29
+ spec.verbose = true
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |spec|
35
+ spec.libs << 'spec'
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.verbose = true
38
+ end
39
+ rescue LoadError
40
+ task :rcov do
41
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
42
+ end
43
+ end
44
+
45
+ task :spec => :check_dependencies
46
+
47
+ task :default => :spec
48
+
49
+ require 'rake/rdoctask'
50
+ Rake::RDocTask.new do |rdoc|
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "rack-thumb #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/example/file.ru ADDED
@@ -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')
data/example/frank.rb ADDED
@@ -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
Binary file
data/example/static.ru ADDED
@@ -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
data/lib/rack/thumb.rb ADDED
@@ -0,0 +1,249 @@
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
+ # :keylength => "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, bad_request) 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
+ width, height = parse_dimensions(dim)
180
+ origin_width, origin_height = Mapel.info(@image.path)[:dimensions]
181
+ width = [width, origin_width].min if width
182
+ height = [height, origin_height].min if height
183
+ output = create_tempfile
184
+ cmd = Mapel(@image.path).gravity(gravity)
185
+ if width && height
186
+ cmd.resize!(width, height)
187
+ else
188
+ cmd.resize(width, height, 0, 0, '>')
189
+ end
190
+ cmd.to(output.path).run
191
+ output
192
+ end
193
+
194
+ # Serves the thumbnail. If this is a HEAD request we strip the body as well
195
+ # as the content length because the render was never run.
196
+ def serve
197
+ response = if head?
198
+ @source_headers.delete("Content-Length")
199
+ [200, @source_headers, []]
200
+ else
201
+ [200, @source_headers.merge("Content-Length" => ::File.size(@thumb.path).to_s), self]
202
+ end
203
+
204
+ throw :halt, response
205
+ end
206
+
207
+ # Parses the rendering options; returns false if rendering options are invalid
208
+ def parse_dimensions(meta)
209
+ dimensions = meta.split('x').map do |dim|
210
+ if dim.empty?
211
+ nil
212
+ elsif dim[0].to_i == 0
213
+ throw :halt, bad_request
214
+ else
215
+ dim.to_i
216
+ end
217
+ end
218
+ dimensions.any? ? dimensions : throw(:halt, bad_request)
219
+ end
220
+
221
+ # Creates a new tempfile
222
+ def create_tempfile
223
+ Tempfile.new(::File.basename(@path)).tap { |f| f.close }
224
+ end
225
+
226
+ def bad_request
227
+ body = "Bad thumbnail parameters in #{@path}\n"
228
+ [400, {"Content-Type" => "text/plain",
229
+ "Content-Length" => body.size.to_s},
230
+ [body]]
231
+ end
232
+
233
+ def head?
234
+ @env["REQUEST_METHOD"] == "HEAD"
235
+ end
236
+
237
+ def each
238
+ ::File.open(@thumb.path, "rb") { |file|
239
+ while part = file.read(8192)
240
+ yield part
241
+ end
242
+ }
243
+ end
244
+
245
+ def to_path
246
+ @thumb.path
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,57 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rack-thumb}
8
+ s.version = "0.2.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aleksander Williams"]
12
+ s.date = %q{2010-02-25}
13
+ s.email = %q{alekswilliams@earthlink.net}
14
+ s.extra_rdoc_files = [
15
+ "README.rdoc"
16
+ ]
17
+ s.files = [
18
+ ".gitignore",
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "example/file.ru",
22
+ "example/frank.rb",
23
+ "example/public/images/imagick.jpg",
24
+ "example/static.ru",
25
+ "lib/rack/thumb.rb",
26
+ "rack-thumb.gemspec",
27
+ "spec/base_spec.rb",
28
+ "spec/helpers.rb",
29
+ "spec/media/imagick.jpg"
30
+ ]
31
+ s.homepage = %q{http://github.com/akdubya/rack-thumb}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.5}
35
+ s.summary = %q{Drop-in image thumbnailing for your Rack stack.}
36
+ s.test_files = [
37
+ "spec/base_spec.rb",
38
+ "spec/helpers.rb"
39
+ ]
40
+
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 3
44
+
45
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
+ s.add_runtime_dependency(%q<mapel>, [">= 0.1.6"])
47
+ s.add_development_dependency(%q<bacon>, [">= 0"])
48
+ else
49
+ s.add_dependency(%q<mapel>, [">= 0.1.6"])
50
+ s.add_dependency(%q<bacon>, [">= 0"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<mapel>, [">= 0.1.6"])
54
+ s.add_dependency(%q<bacon>, [">= 0"])
55
+ end
56
+ end
57
+
data/spec/base_spec.rb ADDED
@@ -0,0 +1,165 @@
1
+ require File.dirname(__FILE__) + '/helpers'
2
+
3
+ describe Rack::Thumb 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
+ info = image_info(res.body)
15
+ info[:dimensions].should == [50, 52]
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
+ info = image_info(res.body)
25
+ info[:dimensions].should == [48, 50]
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
+ info = image_info(res.body)
35
+ info[:dimensions].should == [50, 50]
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
+ info = image_info(res.body)
45
+ info[:dimensions].should == [50, 100]
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
+ info = image_info(res.body)
57
+ info[:dimensions].should == [50, 100]
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
+ info = image_info(res.body)
67
+ info[:dimensions].should == [572, 591]
68
+ end
69
+
70
+ it "should work with non-file source bodies" do
71
+ app = lambda { |env| [200, {"Content-Type" => "image/jpeg"},
72
+ [::File.read(::File.dirname(__FILE__) + "/media/imagick.jpg")]] }
73
+
74
+ request = Rack::MockRequest.new(Rack::Thumb.new(app))
75
+
76
+ res = request.get("/media/imagick_50x.jpg")
77
+ res.should.be.ok
78
+ res.content_type.should == "image/jpeg"
79
+ info = image_info(res.body)
80
+ info[:dimensions].should == [50, 52]
81
+ end
82
+
83
+ it "should return bad request if the signature is invalid" do
84
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app, :keylength => 16,
85
+ :secret => "test"))
86
+
87
+ res = request.get("/media/imagick_50x100-sw-9922d04b14049f85.jpg")
88
+ res.should.be.client_error
89
+ res.body.should == "Bad thumbnail parameters in /media/imagick_50x100-sw-9922d04b14049f85.jpg\n"
90
+ end
91
+
92
+ it "should return bad request if the dimensions are bad" do
93
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
94
+
95
+ res = request.get("/media/imagick_0x50.jpg")
96
+ res.should.be.client_error
97
+ res.body.should == "Bad thumbnail parameters in /media/imagick_0x50.jpg\n"
98
+ end
99
+
100
+ it "should return bad request if dimensions contain leading zeroes" do
101
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
102
+
103
+ res = request.get("/media/imagick_50x050.jpg")
104
+ res.should.be.client_error
105
+ res.body.should == "Bad thumbnail parameters in /media/imagick_50x050.jpg\n"
106
+ end
107
+
108
+ it "should return the application's response if the source file is not found" do
109
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
110
+
111
+ res = request.get("/media/dummy_50x50.jpg")
112
+ res.should.be.not_found
113
+ res.body.should == "File not found: /media/dummy.jpg\n"
114
+ end
115
+
116
+ it "should return the application's response if it does not recognize render options" do
117
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app, :keylength => 16,
118
+ :secret => "test"))
119
+
120
+ res = request.get("/media/imagick_50x50!.jpg")
121
+ res.should.be.not_found
122
+ res.body.should == "File not found: /media/imagick_50x50!.jpg\n"
123
+ end
124
+
125
+ it "should pass non-thumbnail image requests to the application" do
126
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app, :keylength => 16,
127
+ :secret => "test"))
128
+
129
+ res = request.get("/media/imagick.jpg")
130
+ res.should.be.ok
131
+ res.content_type.should == "image/jpeg"
132
+ res.content_length.should == 97374
133
+ res.body.bytesize.should == 97374
134
+ end
135
+
136
+ it "should not render on a HEAD request" do
137
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
138
+
139
+ res = request.request("HEAD", "/media/imagick_50x50.jpg")
140
+ res.should.be.ok
141
+ res.content_type.should == "image/jpeg"
142
+ res.content_length.should.be.nil
143
+ res.body.bytesize.should == 0
144
+ end
145
+
146
+ it "should preserve any extra headers provided by the downstream app" do
147
+ app = lambda { |env| [200, {"X-Foo" => "bar", "Content-Type" => "image/jpeg"},
148
+ ::File.open(::File.dirname(__FILE__) + "/media/imagick.jpg")] }
149
+
150
+ request = Rack::MockRequest.new(Rack::Thumb.new(app))
151
+
152
+ res = request.request("HEAD", "/media/imagick_50x50.jpg")
153
+ res.should.be.ok
154
+ res.content_type.should == "image/jpeg"
155
+ res.content_length.should.be.nil
156
+ res.headers["X-Foo"].should == "bar"
157
+ end
158
+
159
+ it "should forward POST/PUT/DELETE requests to the downstream app" do
160
+ request = Rack::MockRequest.new(Rack::Thumb.new(@app))
161
+
162
+ res = request.post("/media/imagick_50x50.jpg")
163
+ res.should.not.be.successful
164
+ end
165
+ end
data/spec/helpers.rb ADDED
@@ -0,0 +1,16 @@
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
10
+
11
+ def image_info(body)
12
+ t = Tempfile.new('foo.jpg').tap {|f| f.binmode; f.write(body); f.close }
13
+ Mapel.info(t.path)
14
+ end
15
+
16
+ Bacon.summary_on_exit
Binary file
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-thumb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ platform: ruby
6
+ authors:
7
+ - Aleksander Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-25 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: 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.6
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: bacon
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description:
36
+ email: alekswilliams@earthlink.net
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - .gitignore
45
+ - README.rdoc
46
+ - Rakefile
47
+ - example/file.ru
48
+ - example/frank.rb
49
+ - example/public/images/imagick.jpg
50
+ - example/static.ru
51
+ - lib/rack/thumb.rb
52
+ - rack-thumb.gemspec
53
+ - spec/base_spec.rb
54
+ - spec/helpers.rb
55
+ - spec/media/imagick.jpg
56
+ has_rdoc: true
57
+ homepage: http://github.com/akdubya/rack-thumb
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options:
62
+ - --charset=UTF-8
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.3.5
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Drop-in image thumbnailing for your Rack stack.
84
+ test_files:
85
+ - spec/base_spec.rb
86
+ - spec/helpers.rb