rack-contrib 0.9.0 → 0.9.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.

Potentially problematic release.


This version of rack-contrib might be problematic. Click here for more details.

@@ -0,0 +1,44 @@
1
+ require 'rack'
2
+
3
+ # Rack::RelativeRedirect is a simple middleware that converts relative paths in
4
+ # redirects in absolute urls, so they conform to RFC2616. It allows the user to
5
+ # specify the absolute path to use (with a sensible default), and handles
6
+ # relative paths (those that don't start with a slash) as well.
7
+ class Rack::RelativeRedirect
8
+ SCHEME_MAP = {'http'=>'80', 'https'=>'443'}
9
+ # The default proc used if a block is not provided to .new
10
+ # Just uses the url scheme of the request and the server name.
11
+ DEFAULT_ABSOLUTE_PROC = proc do |env, res|
12
+ port = env['SERVER_PORT']
13
+ scheme = env['rack.url_scheme']
14
+ "#{scheme}://#{env['SERVER_NAME']}#{":#{port}" unless SCHEME_MAP[scheme] == port}"
15
+ end
16
+
17
+ # Initialize a new RelativeRedirect object with the given arguments. Arguments:
18
+ # * app : The next middleware in the chain. This is always called.
19
+ # * &block : If provided, it is called with the environment and the response
20
+ # from the next middleware. It should return a string representing the scheme
21
+ # and server name (such as 'http://example.org').
22
+ def initialize(app, &block)
23
+ @app = app
24
+ @absolute_proc = block || DEFAULT_ABSOLUTE_PROC
25
+ end
26
+
27
+ # Call the next middleware with the environment. If the request was a
28
+ # redirect (response status 301, 302, or 303), and the location header does
29
+ # not start with an http or https url scheme, call the block provided by new
30
+ # and use that to make the Location header an absolute url. If the Location
31
+ # does not start with a slash, make location relative to the path requested.
32
+ def call(env)
33
+ res = @app.call(env)
34
+ if [301,302,303].include?(res[0]) and loc = res[1]['Location'] and !%r{\Ahttps?://}o.match(loc)
35
+ absolute = @absolute_proc.call(env, res)
36
+ res[1]['Location'] = if %r{\A/}.match(loc)
37
+ "#{absolute}#{loc}"
38
+ else
39
+ "#{absolute}#{File.dirname(Rack::Utils.unescape(env['PATH_INFO']))}/#{loc}"
40
+ end
41
+ end
42
+ res
43
+ end
44
+ end
@@ -0,0 +1,59 @@
1
+ require 'fileutils'
2
+ require 'rack'
3
+
4
+ # Rack::ResponseCache is a Rack middleware that caches responses for successful
5
+ # GET requests with no query string to disk or any ruby object that has an
6
+ # []= method (so it works with memcached). When caching to disk, it works similar to
7
+ # Rails' page caching, allowing you to cache dynamic pages to static files that can
8
+ # be served directly by a front end webserver.
9
+ class Rack::ResponseCache
10
+ # The default proc used if a block is not provided to .new
11
+ # It unescapes the PATH_INFO of the environment, and makes sure that it doesn't
12
+ # include '..'. If the Content-Type of the response is text/(html|css|xml),
13
+ # return a path with the appropriate extension (.html, .css, or .xml).
14
+ # If the path ends with a / and the Content-Type is text/html, change the basename
15
+ # of the path to index.html.
16
+ DEFAULT_PATH_PROC = proc do |env, res|
17
+ path = Rack::Utils.unescape(env['PATH_INFO'])
18
+ if !path.include?('..') and match = /text\/((?:x|ht)ml|css)/o.match(res[1]['Content-Type'])
19
+ type = match[1]
20
+ path = "#{path}.#{type}" unless /\.#{type}\z/.match(path)
21
+ path = File.join(File.dirname(path), 'index.html') if type == 'html' and File.basename(path) == '.html'
22
+ path
23
+ end
24
+ end
25
+
26
+ # Initialize a new ReponseCache object with the given arguments. Arguments:
27
+ # * app : The next middleware in the chain. This is always called.
28
+ # * cache : The place to cache responses. If a string is provided, a disk
29
+ # cache is used, and all cached files will use this directory as the root directory.
30
+ # If anything other than a string is provided, it should respond to []=, which will
31
+ # be called with a path string and a body value (the 3rd element of the response).
32
+ # * &block : If provided, it is called with the environment and the response from the next middleware.
33
+ # It should return nil or false if the path should not be cached, and should return
34
+ # the pathname to use as a string if the result should be cached.
35
+ # If not provided, the DEFAULT_PATH_PROC is used.
36
+ def initialize(app, cache, &block)
37
+ @app = app
38
+ @cache = cache
39
+ @path_proc = block || DEFAULT_PATH_PROC
40
+ end
41
+
42
+ # Call the next middleware with the environment. If the request was successful (response status 200),
43
+ # was a GET request, and had an empty query string, call the block set up in initialize to get the path.
44
+ # If the cache is a string, create any necessary middle directories, and cache the file in the appropriate
45
+ # subdirectory of cache. Otherwise, cache the body of the reponse as the value with the path as the key.
46
+ def call(env)
47
+ res = @app.call(env)
48
+ if env['REQUEST_METHOD'] == 'GET' and env['QUERY_STRING'] == '' and res[0] == 200 and path = @path_proc.call(env, res)
49
+ if @cache.is_a?(String)
50
+ path = File.join(@cache, path) if @cache
51
+ FileUtils.mkdir_p(File.dirname(path))
52
+ File.open(path, 'wb'){|f| res[2].each{|c| f.write(c)}}
53
+ else
54
+ @cache[path] = res[2]
55
+ end
56
+ end
57
+ res
58
+ end
59
+ end
@@ -4,45 +4,46 @@ module Rack
4
4
  [Exception, '/error/internal']
5
5
  ]
6
6
 
7
- ROUTE_EXCEPTIONS_PATH_INFO = 'rack.route_exceptions.path_info'.freeze
8
- ROUTE_EXCEPTIONS_EXCEPTION = 'rack.route_exceptions.exception'.freeze
9
- ROUTE_EXCEPTIONS_RESPONSE = 'rack.route_exceptions.response'.freeze
7
+ PATH_INFO = 'rack.route_exceptions.path_info'.freeze
8
+ EXCEPTION = 'rack.route_exceptions.exception'.freeze
9
+ RETURNED = 'rack.route_exceptions.returned'.freeze
10
+
11
+ class << self
12
+ def route(exception, to)
13
+ ROUTES.delete_if{|k,v| k == exception }
14
+ ROUTES << [exception, to]
15
+ end
16
+
17
+ alias []= route
18
+ end
10
19
 
11
20
  def initialize(app)
12
21
  @app = app
13
22
  end
14
23
 
15
24
  def call(env, try_again = true)
16
- status, header, body = response = @app.call(env)
17
-
18
- response
25
+ returned = @app.call(env)
19
26
  rescue Exception => exception
20
27
  raise(exception) unless try_again
21
28
 
22
29
  ROUTES.each do |klass, to|
23
30
  next unless klass === exception
24
- return route(to, env, response, exception)
31
+ return route(to, env, returned, exception)
25
32
  end
26
33
 
27
34
  raise(exception)
28
35
  end
29
36
 
30
- def route(to, env, response, exception)
31
- hash = {
32
- ROUTE_EXCEPTIONS_PATH_INFO => env['PATH_INFO'],
33
- ROUTE_EXCEPTIONS_EXCEPTION => exception,
34
- ROUTE_EXCEPTIONS_RESPONSE => response
35
- }
36
- env.merge!(hash)
37
+ def route(to, env, returned, exception)
38
+ env.merge!(
39
+ PATH_INFO => env['PATH_INFO'],
40
+ EXCEPTION => exception,
41
+ RETURNED => returned
42
+ )
37
43
 
38
44
  env['PATH_INFO'] = to
39
45
 
40
46
  call(env, try_again = false)
41
47
  end
42
-
43
- def self.route(exception, to)
44
- ROUTES.delete_if{|k,v| k == exception }
45
- ROUTES << [exception, to]
46
- end
47
48
  end
48
49
  end
@@ -0,0 +1,63 @@
1
+ module Rack
2
+ # Installs signal handlers that are safely processed after a request
3
+ #
4
+ # NOTE: This middleware should not be used in a threaded environment
5
+ #
6
+ # use Rack::Signals.new do
7
+ # trap 'INT', lambda {
8
+ # puts "Exiting now"
9
+ # exit
10
+ # }
11
+ #
12
+ # trap_when_ready 'USR1', lambda {
13
+ # puts "Exiting when ready"
14
+ # exit
15
+ # }
16
+ # end
17
+ class Signals
18
+ class BodyWithCallback
19
+ def initialize(body, callback)
20
+ @body, @callback = body, callback
21
+ end
22
+
23
+ def each(&block)
24
+ @body.each(&block)
25
+ @callback.call
26
+ end
27
+ end
28
+
29
+ def initialize(app, &block)
30
+ @app = app
31
+ @processing = false
32
+ @when_ready = nil
33
+ instance_eval(&block)
34
+ end
35
+
36
+ def call(env)
37
+ begin
38
+ @processing, @when_ready = true, nil
39
+ status, headers, body = @app.call(env)
40
+
41
+ if handler = @when_ready
42
+ body = BodyWithCallback.new(body, handler)
43
+ @when_ready = nil
44
+ end
45
+ ensure
46
+ @processing = false
47
+ end
48
+
49
+ [status, headers, body]
50
+ end
51
+
52
+ def trap_when_ready(signal, handler)
53
+ when_ready_handler = lambda { |signal|
54
+ if @processing
55
+ @when_ready = lambda { handler.call(signal) }
56
+ else
57
+ handler.call(signal)
58
+ end
59
+ }
60
+ trap(signal, when_ready_handler)
61
+ end
62
+ end
63
+ end
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
3
3
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
4
 
5
5
  s.name = 'rack-contrib'
6
- s.version = '0.9.0'
7
- s.date = '2009-01-23'
6
+ s.version = '0.9.2'
7
+ s.date = '2009-03-07'
8
8
 
9
9
  s.description = "Contributed Rack Middleware and Utilities"
10
10
  s.summary = "Contributed Rack Middleware and Utilities"
@@ -18,8 +18,13 @@ Gem::Specification.new do |s|
18
18
  README.rdoc
19
19
  Rakefile
20
20
  lib/rack/contrib.rb
21
+ lib/rack/contrib/accept_format.rb
22
+ lib/rack/contrib/backstage.rb
21
23
  lib/rack/contrib/bounce_favicon.rb
22
24
  lib/rack/contrib/callbacks.rb
25
+ lib/rack/contrib/config.rb
26
+ lib/rack/contrib/csshttprequest.rb
27
+ lib/rack/contrib/deflect.rb
23
28
  lib/rack/contrib/etag.rb
24
29
  lib/rack/contrib/evil.rb
25
30
  lib/rack/contrib/garbagecollector.rb
@@ -28,16 +33,27 @@ Gem::Specification.new do |s|
28
33
  lib/rack/contrib/locale.rb
29
34
  lib/rack/contrib/mailexceptions.rb
30
35
  lib/rack/contrib/nested_params.rb
36
+ lib/rack/contrib/not_found.rb
31
37
  lib/rack/contrib/post_body_content_type_parser.rb
32
38
  lib/rack/contrib/proctitle.rb
33
39
  lib/rack/contrib/profiler.rb
40
+ lib/rack/contrib/relative_redirect.rb
41
+ lib/rack/contrib/response_cache.rb
34
42
  lib/rack/contrib/route_exceptions.rb
35
43
  lib/rack/contrib/sendfile.rb
44
+ lib/rack/contrib/signals.rb
36
45
  lib/rack/contrib/time_zone.rb
37
46
  rack-contrib.gemspec
47
+ test/404.html
48
+ test/Maintenance.html
38
49
  test/mail_settings.rb
50
+ test/spec_rack_accept_format.rb
51
+ test/spec_rack_backstage.rb
39
52
  test/spec_rack_callbacks.rb
53
+ test/spec_rack_config.rb
40
54
  test/spec_rack_contrib.rb
55
+ test/spec_rack_csshttprequest.rb
56
+ test/spec_rack_deflect.rb
41
57
  test/spec_rack_etag.rb
42
58
  test/spec_rack_evil.rb
43
59
  test/spec_rack_garbagecollector.rb
@@ -45,9 +61,12 @@ Gem::Specification.new do |s|
45
61
  test/spec_rack_lighttpd_script_name_fix.rb
46
62
  test/spec_rack_mailexceptions.rb
47
63
  test/spec_rack_nested_params.rb
64
+ test/spec_rack_not_found.rb
48
65
  test/spec_rack_post_body_content_type_parser.rb
49
66
  test/spec_rack_proctitle.rb
50
67
  test/spec_rack_profiler.rb
68
+ test/spec_rack_relative_redirect.rb
69
+ test/spec_rack_response_cache.rb
51
70
  test/spec_rack_sendfile.rb
52
71
  ]
53
72
  # = MANIFEST =
@@ -55,7 +74,7 @@ Gem::Specification.new do |s|
55
74
  s.test_files = s.files.select {|path| path =~ /^test\/spec_.*\.rb/}
56
75
 
57
76
  s.extra_rdoc_files = %w[README.rdoc COPYING]
58
- s.add_dependency 'rack', '~> 0.9.1'
77
+ s.add_dependency 'rack', '>= 0.9.1'
59
78
  s.add_dependency 'test-spec', '~> 0.9.0'
60
79
  s.add_development_dependency 'tmail', '>= 1.2'
61
80
  s.add_development_dependency 'json', '>= 1.1'
@@ -0,0 +1 @@
1
+ Not Found
@@ -0,0 +1 @@
1
+ Under maintenance.
@@ -0,0 +1,72 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/accept_format'
4
+ require 'rack/mime'
5
+
6
+ context "Rack::AcceptFormat" do
7
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, env['PATH_INFO']] }
8
+
9
+ specify "should do nothing when a format extension is already provided" do
10
+ request = Rack::MockRequest.env_for("/resource.json")
11
+ body = Rack::AcceptFormat.new(app).call(request).last
12
+ body.should == "/resource.json"
13
+ end
14
+
15
+ context "default extention" do
16
+ specify "should allow custom default" do
17
+ request = Rack::MockRequest.env_for("/resource")
18
+ body = Rack::AcceptFormat.new(app, '.xml').call(request).last
19
+ body.should == "/resource.xml"
20
+ end
21
+
22
+ specify "should default to html" do
23
+ request = Rack::MockRequest.env_for("/resource")
24
+ body = Rack::AcceptFormat.new(app).call(request).last
25
+ body.should == "/resource.html"
26
+ end
27
+
28
+ specify "should notmalize custom extention" do
29
+ request = Rack::MockRequest.env_for("/resource")
30
+
31
+ body = Rack::AcceptFormat.new(app,'xml').call(request).last #no dot prefix
32
+ body.should == "/resource.xml"
33
+
34
+ body = Rack::AcceptFormat.new(app, :xml).call(request).last
35
+ body.should == "/resource.xml"
36
+ end
37
+ end
38
+
39
+ context "there is no format extension" do
40
+ Rack::Mime::MIME_TYPES.clear
41
+
42
+ def mime(ext, type)
43
+ ext = ".#{ext}" unless ext.to_s[0] == ?.
44
+ Rack::Mime::MIME_TYPES[ext.to_s] = type
45
+ end
46
+
47
+ specify "should add the default extension if no Accept header" do
48
+ request = Rack::MockRequest.env_for("/resource")
49
+ body = Rack::AcceptFormat.new(app).call(request).last
50
+ body.should == "/resource.html"
51
+ end
52
+
53
+ specify "should add the default extension if the Accept header is not registered in the Mime::Types" do
54
+ request = Rack::MockRequest.env_for("/resource", 'HTTP_ACCEPT' => 'application/json;q=1.0, text/html;q=0.8, */*;q=0.1')
55
+ body = Rack::AcceptFormat.new(app).call(request).last
56
+ body.should == "/resource.html"
57
+ end
58
+
59
+ specify "should add the correct extension if the Accept header is registered in the Mime::Types" do
60
+ mime :json, 'application/json'
61
+ request = Rack::MockRequest.env_for("/resource", 'HTTP_ACCEPT' => 'application/json;q=1.0, text/html;q=0.8, */*;q=0.1')
62
+ body = Rack::AcceptFormat.new(app).call(request).last
63
+ body.should == "/resource.json"
64
+ end
65
+ end
66
+
67
+ specify "shouldn't confuse extention when there are dots in path" do
68
+ request = Rack::MockRequest.env_for("/parent.resource/resource")
69
+ body = Rack::AcceptFormat.new(app, '.html').call(request).last
70
+ body.should == "/parent.resource/resource.html"
71
+ end
72
+ end
@@ -0,0 +1,26 @@
1
+ require 'test/spec'
2
+ require 'rack/builder'
3
+ require 'rack/mock'
4
+ require 'rack/contrib/backstage'
5
+
6
+ context "Rack::Backstage" do
7
+ specify "shows maintenances page if present" do
8
+ app = Rack::Builder.new do
9
+ use Rack::Backstage, 'test/Maintenance.html'
10
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
11
+ end
12
+ response = Rack::MockRequest.new(app).get('/')
13
+ response.body.should.equal('Under maintenance.')
14
+ response.status.should.equal(503)
15
+ end
16
+
17
+ specify "passes on request if page is not present" do
18
+ app = Rack::Builder.new do
19
+ use Rack::Backstage, 'test/Nonsense.html'
20
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
21
+ end
22
+ response = Rack::MockRequest.new(app).get('/')
23
+ response.body.should.equal('Hello, World!')
24
+ response.status.should.equal(200)
25
+ end
26
+ end
@@ -43,7 +43,7 @@ context "Rack::Callbacks" do
43
43
  before Flame
44
44
  before Pacify, "with love"
45
45
 
46
- run lambda {|env| [200, {}, env['flame'] + env['peace']] }
46
+ run lambda {|env| [200, {}, [env['flame'], env['peace']]] }
47
47
 
48
48
  after Finale
49
49
  after TheEnd
@@ -62,4 +62,4 @@ context "Rack::Callbacks" do
62
62
 
63
63
  response.headers['last'].should.equal 'TheEnd'
64
64
  end
65
- end
65
+ end
@@ -0,0 +1,22 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/config'
4
+
5
+ context "Rack::Config" do
6
+
7
+ specify "should accept a block that modifies the environment" do
8
+ app = Rack::Builder.new do
9
+ use Rack::Lint
10
+ use Rack::ContentLength
11
+ use Rack::Config do |env|
12
+ env['greeting'] = 'hello'
13
+ end
14
+ run lambda { |env|
15
+ [200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
16
+ }
17
+ end
18
+ response = Rack::MockRequest.new(app).get('/')
19
+ response.body.should.equal('hello')
20
+ end
21
+
22
+ end