rack-thumb 0.2.3

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.
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