rack 1.3.10 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (83) hide show
  1. data/COPYING +1 -1
  2. data/KNOWN-ISSUES +0 -9
  3. data/README.rdoc +4 -118
  4. data/Rakefile +15 -0
  5. data/SPEC +3 -5
  6. data/lib/rack.rb +0 -12
  7. data/lib/rack/auth/abstract/request.rb +1 -5
  8. data/lib/rack/auth/basic.rb +1 -1
  9. data/lib/rack/auth/digest/nonce.rb +1 -1
  10. data/lib/rack/backports/uri/common_18.rb +28 -14
  11. data/lib/rack/backports/uri/common_192.rb +17 -14
  12. data/lib/rack/body_proxy.rb +0 -10
  13. data/lib/rack/builder.rb +26 -18
  14. data/lib/rack/cascade.rb +1 -12
  15. data/lib/rack/chunked.rb +2 -0
  16. data/lib/rack/content_type.rb +7 -1
  17. data/lib/rack/deflater.rb +1 -5
  18. data/lib/rack/directory.rb +5 -1
  19. data/lib/rack/file.rb +26 -9
  20. data/lib/rack/handler.rb +2 -2
  21. data/lib/rack/head.rb +0 -1
  22. data/lib/rack/lint.rb +3 -5
  23. data/lib/rack/methodoverride.rb +10 -4
  24. data/lib/rack/mime.rb +606 -171
  25. data/lib/rack/mock.rb +2 -1
  26. data/lib/rack/multipart.rb +2 -2
  27. data/lib/rack/multipart/parser.rb +3 -10
  28. data/lib/rack/reloader.rb +1 -1
  29. data/lib/rack/request.rb +45 -13
  30. data/lib/rack/response.rb +15 -14
  31. data/lib/rack/sendfile.rb +8 -6
  32. data/lib/rack/server.rb +4 -30
  33. data/lib/rack/session/abstract/id.rb +25 -6
  34. data/lib/rack/session/cookie.rb +12 -16
  35. data/lib/rack/static.rb +21 -8
  36. data/lib/rack/urlmap.rb +28 -13
  37. data/lib/rack/utils.rb +22 -28
  38. data/rack.gemspec +5 -5
  39. data/test/builder/end.ru +2 -0
  40. data/test/cgi/lighttpd.conf +1 -0
  41. data/test/cgi/sample_rackup.ru +1 -1
  42. data/test/cgi/test+directory/test+file +1 -0
  43. data/test/cgi/test.ru +1 -1
  44. data/test/gemloader.rb +6 -2
  45. data/test/spec_auth_basic.rb +4 -9
  46. data/test/spec_auth_digest.rb +3 -16
  47. data/test/spec_body_proxy.rb +0 -4
  48. data/test/spec_builder.rb +63 -20
  49. data/test/spec_cascade.rb +10 -13
  50. data/test/spec_cgi.rb +1 -1
  51. data/test/spec_chunked.rb +39 -12
  52. data/test/spec_commonlogger.rb +4 -3
  53. data/test/spec_conditionalget.rb +16 -12
  54. data/test/spec_content_length.rb +1 -1
  55. data/test/spec_content_type.rb +6 -0
  56. data/test/spec_deflater.rb +2 -2
  57. data/test/spec_directory.rb +12 -0
  58. data/test/spec_fastcgi.rb +1 -1
  59. data/test/spec_file.rb +58 -8
  60. data/test/spec_head.rb +6 -18
  61. data/test/spec_lint.rb +2 -2
  62. data/test/spec_methodoverride.rb +15 -0
  63. data/test/spec_mock.rb +6 -2
  64. data/test/spec_mongrel.rb +8 -8
  65. data/test/spec_multipart.rb +10 -63
  66. data/test/spec_request.rb +94 -21
  67. data/test/spec_response.rb +22 -24
  68. data/test/spec_sendfile.rb +3 -0
  69. data/test/spec_server.rb +2 -49
  70. data/test/spec_session_cookie.rb +58 -22
  71. data/test/spec_session_memcache.rb +31 -1
  72. data/test/spec_session_pool.rb +10 -4
  73. data/test/spec_static.rb +8 -0
  74. data/test/spec_thin.rb +2 -2
  75. data/test/spec_utils.rb +38 -35
  76. data/test/spec_webrick.rb +5 -3
  77. data/test/static/index.html +1 -0
  78. metadata +13 -18
  79. data/contrib/rack.png +0 -0
  80. data/contrib/rack.svg +0 -150
  81. data/lib/rack/backports/uri/common_193.rb +0 -29
  82. data/test/builder/line.ru +0 -1
  83. data/test/spec_auth.rb +0 -57
@@ -22,6 +22,12 @@ module Rack
22
22
  #
23
23
  # use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public'
24
24
  #
25
+ # Serve all requests normally from the folder "public" in the current
26
+ # directory but uses index.html as default route for "/"
27
+ #
28
+ # use Rack::Static, :urls => [""], :root => 'public', :index =>
29
+ # 'public/index.html'
30
+ #
25
31
  # Set a fixed Cache-Control header for all served files:
26
32
  #
27
33
  # use Rack::Static, :root => 'public', :cache_control => 'public'
@@ -32,22 +38,29 @@ module Rack
32
38
  def initialize(app, options={})
33
39
  @app = app
34
40
  @urls = options[:urls] || ["/favicon.ico"]
41
+ @index = options[:index] || "index.html"
35
42
  root = options[:root] || Dir.pwd
36
43
  cache_control = options[:cache_control]
37
44
  @file_server = Rack::File.new(root, cache_control)
38
45
  end
39
46
 
47
+ def overwrite_file_path(path)
48
+ @urls.kind_of?(Hash) && @urls.key?(path) || @index && path == '/'
49
+ end
50
+
51
+ def route_file(path)
52
+ @urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 }
53
+ end
54
+
55
+ def can_serve(path)
56
+ route_file(path) || overwrite_file_path(path)
57
+ end
58
+
40
59
  def call(env)
41
60
  path = env["PATH_INFO"]
42
61
 
43
- unless @urls.kind_of? Hash
44
- can_serve = @urls.any? { |url| path.index(url) == 0 }
45
- else
46
- can_serve = @urls.key? path
47
- end
48
-
49
- if can_serve
50
- env["PATH_INFO"] = @urls[path] if @urls.kind_of? Hash
62
+ if can_serve(path)
63
+ env["PATH_INFO"] = (path == '/' ? @index : @urls[path]) if overwrite_file_path(path)
51
64
  @file_server.call(env)
52
65
  else
53
66
  @app.call(env)
@@ -19,9 +19,6 @@ module Rack
19
19
  end
20
20
 
21
21
  def remap(map)
22
- longest_path_first = lambda do |(host, location, _, _)|
23
- [host ? -host.size : NEGATIVE_INFINITY, -location.size]
24
- end
25
22
  @mapping = map.map { |location, app|
26
23
  if location =~ %r{\Ahttps?://(.*?)(/.*)}
27
24
  host, location = $1, $2
@@ -32,28 +29,46 @@ module Rack
32
29
  unless location[0] == ?/
33
30
  raise ArgumentError, "paths need to start with /"
34
31
  end
32
+
35
33
  location = location.chomp('/')
36
34
  match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
37
35
 
38
36
  [host, location, match, app]
39
- }.sort_by(&longest_path_first)
37
+ }.sort_by do |(host, location, _, _)|
38
+ [host ? -host.size : NEGATIVE_INFINITY, -location.size]
39
+ end
40
40
  end
41
41
 
42
42
  def call(env)
43
43
  path = env["PATH_INFO"]
44
44
  script_name = env['SCRIPT_NAME']
45
- hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
46
- @mapping.each { |host, location, match, app|
47
- next unless (hHost == host || sName == host \
48
- || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
49
- next unless path.to_s =~ match && rest = $1
50
- next unless rest.empty? || rest[0] == ?/
51
- env.merge!('SCRIPT_NAME' => (script_name + location), 'PATH_INFO' => rest)
45
+ hHost = env['HTTP_HOST']
46
+ sName = env['SERVER_NAME']
47
+ sPort = env['SERVER_PORT']
48
+
49
+ @mapping.each do |host, location, match, app|
50
+ unless hHost == host \
51
+ || sName == host \
52
+ || (!host && (hHost == sName || hHost == sName+':'+sPort))
53
+ next
54
+ end
55
+
56
+ next unless m = match.match(path.to_s)
57
+
58
+ rest = m[1]
59
+ next unless !rest || rest.empty? || rest[0] == ?/
60
+
61
+ env['SCRIPT_NAME'] = (script_name + location)
62
+ env['PATH_INFO'] = rest
63
+
52
64
  return app.call(env)
53
- }
65
+ end
66
+
54
67
  [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
68
+
55
69
  ensure
56
- env.merge! 'PATH_INFO' => path, 'SCRIPT_NAME' => script_name
70
+ env['PATH_INFO'] = path
71
+ env['SCRIPT_NAME'] = script_name
57
72
  end
58
73
  end
59
74
  end
@@ -5,14 +5,11 @@ require 'tempfile'
5
5
  require 'rack/multipart'
6
6
 
7
7
  major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
8
- ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
9
8
 
10
9
  if major == 1 && minor < 9
11
10
  require 'rack/backports/uri/common_18'
12
- elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 320 && RUBY_ENGINE != 'jruby'
11
+ elsif major == 1 && minor == 9 && patch < 3
13
12
  require 'rack/backports/uri/common_192'
14
- elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
15
- require 'rack/backports/uri/common_193'
16
13
  else
17
14
  require 'uri/common'
18
15
  end
@@ -35,9 +32,16 @@ module Rack
35
32
  end
36
33
  module_function :escape_path
37
34
 
38
- # Unescapes a URI escaped string.
39
- def unescape(s)
40
- URI.decode_www_form_component(s)
35
+ # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
36
+ # target encoding of the string returned, and it defaults to UTF-8
37
+ if defined?(::Encoding)
38
+ def unescape(s, encoding = Encoding::UTF_8)
39
+ URI.decode_www_form_component(s, encoding)
40
+ end
41
+ else
42
+ def unescape(s, encoding = nil)
43
+ URI.decode_www_form_component(s, encoding)
44
+ end
41
45
  end
42
46
  module_function :unescape
43
47
 
@@ -63,7 +67,6 @@ module Rack
63
67
  bytes = 0
64
68
 
65
69
  (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
66
- next if p.empty?
67
70
  k, v = p.split('=', 2).map { |x| unescape(x) }
68
71
 
69
72
  if k
@@ -148,7 +151,7 @@ module Rack
148
151
  if v.class == Array
149
152
  build_query(v.map { |x| [k, x] })
150
153
  else
151
- "#{escape(k)}=#{escape(v)}"
154
+ v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
152
155
  end
153
156
  }.join("&")
154
157
  end
@@ -269,6 +272,8 @@ module Rack
269
272
  cookies.reject! { |cookie|
270
273
  if value[:domain]
271
274
  cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
275
+ elsif value[:path]
276
+ cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
272
277
  else
273
278
  cookie =~ /\A#{escape(key)}=/
274
279
  end
@@ -319,16 +324,16 @@ module Rack
319
324
  def byte_ranges(env, size)
320
325
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
321
326
  http_range = env['HTTP_RANGE']
322
- return nil unless http_range && http_range =~ /bytes=([^;]+)/
327
+ return nil unless http_range
323
328
  ranges = []
324
- $1.split(/,\s*/).each do |range_spec|
325
- return nil unless range_spec =~ /(\d*)-(\d*)/
326
- r0,r1 = $1, $2
329
+ http_range.split(/,\s*/).each do |range_spec|
330
+ matches = range_spec.match(/bytes=(\d*)-(\d*)/)
331
+ return nil unless matches
332
+ r0,r1 = matches[1], matches[2]
327
333
  if r0.empty?
328
334
  return nil if r1.empty?
329
335
  # suffix-byte-range-spec, represents trailing suffix of file
330
- r0 = size - r1.to_i
331
- r0 = 0 if r0 < 0
336
+ r0 = [size - r1.to_i, 0].max
332
337
  r1 = size - 1
333
338
  else
334
339
  r0 = r0.to_i
@@ -346,18 +351,6 @@ module Rack
346
351
  end
347
352
  module_function :byte_ranges
348
353
 
349
- # Constant time string comparison.
350
- def secure_compare(a, b)
351
- return false unless bytesize(a) == bytesize(b)
352
-
353
- l = a.unpack("C*")
354
-
355
- r, i = 0, -1
356
- b.each_byte { |v| r |= v ^ l[i+=1] }
357
- r == 0
358
- end
359
- module_function :secure_compare
360
-
361
354
  # Context allows the use of a compatible middleware at different points
362
355
  # in a request handling stack. A compatible middleware must define
363
356
  # #context which should take the arguments env and app. The first of which
@@ -496,6 +489,7 @@ module Rack
496
489
  415 => 'Unsupported Media Type',
497
490
  416 => 'Requested Range Not Satisfiable',
498
491
  417 => 'Expectation Failed',
492
+ 418 => "I'm a Teapot",
499
493
  422 => 'Unprocessable Entity',
500
494
  423 => 'Locked',
501
495
  424 => 'Failed Dependency',
@@ -512,7 +506,7 @@ module Rack
512
506
  }
513
507
 
514
508
  # Responses with HTTP status codes that should not have an entity body
515
- STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
509
+ STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)
516
510
 
517
511
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
518
512
  [message.downcase.gsub(/\s|-/, '_').to_sym, code]
@@ -1,17 +1,17 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rack"
3
- s.version = "1.3.10"
3
+ s.version = "1.4.0"
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.summary = "a modular Ruby webserver interface"
6
6
 
7
7
  s.description = <<-EOF
8
- Rack provides minimal, modular and adaptable interface for developing
8
+ Rack provides a minimal, modular and adaptable interface for developing
9
9
  web applications in Ruby. By wrapping HTTP requests and responses in
10
10
  the simplest way possible, it unifies and distills the API for web
11
11
  servers, web frameworks, and software in between (the so-called
12
12
  middleware) into a single method call.
13
13
 
14
- Also see http://rack.github.com/.
14
+ Also see http://rack.rubyforge.org.
15
15
  EOF
16
16
 
17
17
  s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*,test/**/*}'] +
@@ -24,13 +24,13 @@ EOF
24
24
 
25
25
  s.author = 'Christian Neukirchen'
26
26
  s.email = 'chneukirchen@gmail.com'
27
- s.homepage = 'http://rack.github.com/'
27
+ s.homepage = 'http://rack.rubyforge.org'
28
28
  s.rubyforge_project = 'rack'
29
29
 
30
30
  s.add_development_dependency 'bacon'
31
31
  s.add_development_dependency 'rake'
32
32
 
33
- s.add_development_dependency 'fcgi'
33
+ s.add_development_dependency 'ruby-fcgi'
34
34
  s.add_development_dependency 'memcache-client'
35
35
  s.add_development_dependency 'mongrel', '>= 1.2.0.pre2'
36
36
  s.add_development_dependency 'thin'
@@ -1,3 +1,5 @@
1
1
  run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
2
2
  __END__
3
3
  Should not be evaluated
4
+ Neither should
5
+ This
@@ -2,6 +2,7 @@ server.modules = ("mod_fastcgi", "mod_cgi")
2
2
  server.document-root = "."
3
3
  server.errorlog = var.CWD + "/lighttpd.errors"
4
4
  server.port = 9203
5
+ server.bind = "127.0.0.1"
5
6
 
6
7
  server.event-handler = "select"
7
8
 
@@ -2,4 +2,4 @@
2
2
 
3
3
  require '../testrequest'
4
4
 
5
- run TestRequest.new
5
+ run Rack::Lint.new(TestRequest.new)
@@ -0,0 +1 @@
1
+ this file has plusses!
@@ -2,4 +2,4 @@
2
2
  # -*- ruby -*-
3
3
 
4
4
  require '../testrequest'
5
- run TestRequest.new
5
+ run Rack::Lint.new(TestRequest.new)
@@ -2,5 +2,9 @@ require 'rubygems'
2
2
  project = 'rack'
3
3
  gemspec = File.expand_path("#{project}.gemspec", Dir.pwd)
4
4
  Gem::Specification.load(gemspec).dependencies.each do |dep|
5
- gem dep.name, *dep.requirement.as_list
6
- end
5
+ begin
6
+ gem dep.name, *dep.requirement.as_list
7
+ rescue Gem::LoadError
8
+ warn "Cannot load #{dep.name} #{dep.requirement.to_s}"
9
+ end
10
+ end
@@ -1,4 +1,5 @@
1
1
  require 'rack/auth/basic'
2
+ require 'rack/lint'
2
3
  require 'rack/mock'
3
4
 
4
5
  describe Rack::Auth::Basic do
@@ -7,7 +8,9 @@ describe Rack::Auth::Basic do
7
8
  end
8
9
 
9
10
  def unprotected_app
10
- lambda { |env| [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ] }
11
+ Rack::Lint.new lambda { |env|
12
+ [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ]
13
+ }
11
14
  end
12
15
 
13
16
  def protected_app
@@ -63,14 +66,6 @@ describe Rack::Auth::Basic do
63
66
  end
64
67
  end
65
68
 
66
- should 'return 400 Bad Request for a malformed authorization header' do
67
- request 'HTTP_AUTHORIZATION' => '' do |response|
68
- response.should.be.a.client_error
69
- response.status.should.equal 400
70
- response.should.not.include 'WWW-Authenticate'
71
- end
72
- end
73
-
74
69
  it 'takes realm as optional constructor arg' do
75
70
  app = Rack::Auth::Basic.new(unprotected_app, realm) { true }
76
71
  realm.should == app.realm
@@ -1,4 +1,5 @@
1
1
  require 'rack/auth/digest/md5'
2
+ require 'rack/lint'
2
3
  require 'rack/mock'
3
4
 
4
5
  describe Rack::Auth::Digest::MD5 do
@@ -7,10 +8,10 @@ describe Rack::Auth::Digest::MD5 do
7
8
  end
8
9
 
9
10
  def unprotected_app
10
- lambda do |env|
11
+ Rack::Lint.new lambda { |env|
11
12
  friend = Rack::Utils.parse_query(env["QUERY_STRING"])["friend"]
12
13
  [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}#{friend ? " and #{friend}" : ''}"] ]
13
- end
14
+ }
14
15
  end
15
16
 
16
17
  def protected_app
@@ -152,20 +153,6 @@ describe Rack::Auth::Digest::MD5 do
152
153
  end
153
154
  end
154
155
 
155
- should 'not rechallenge if nonce is not stale' do
156
- begin
157
- Rack::Auth::Digest::Nonce.time_limit = 10
158
-
159
- request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', :wait => 1 do |response|
160
- response.status.should.equal 200
161
- response.body.to_s.should.equal 'Hi Alice'
162
- response.headers['WWW-Authenticate'].should.not =~ /\bstale=true\b/
163
- end
164
- ensure
165
- Rack::Auth::Digest::Nonce.time_limit = nil
166
- end
167
- end
168
-
169
156
  should 'rechallenge with stale parameter if nonce is stale' do
170
157
  begin
171
158
  Rack::Auth::Digest::Nonce.time_limit = 1
@@ -45,8 +45,4 @@ describe Rack::BodyProxy do
45
45
  proxy.close
46
46
  closed.should.equal true
47
47
  end
48
-
49
- should 'provide an #each method' do
50
- Rack::BodyProxy.method_defined?(:each).should.equal true
51
- end
52
48
  end
@@ -1,4 +1,5 @@
1
1
  require 'rack/builder'
2
+ require 'rack/lint'
2
3
  require 'rack/mock'
3
4
  require 'rack/showexceptions'
4
5
  require 'rack/urlmap'
@@ -18,38 +19,46 @@ class NothingMiddleware
18
19
  end
19
20
 
20
21
  describe Rack::Builder do
22
+ def builder(&block)
23
+ Rack::Lint.new Rack::Builder.new(&block)
24
+ end
25
+
26
+ def builder_to_app(&block)
27
+ Rack::Lint.new Rack::Builder.new(&block).to_app
28
+ end
29
+
21
30
  it "supports mapping" do
22
- app = Rack::Builder.new do
31
+ app = builder_to_app do
23
32
  map '/' do |outer_env|
24
- run lambda { |inner_env| [200, {}, ['root']] }
33
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
25
34
  end
26
35
  map '/sub' do
27
- run lambda { |inner_env| [200, {}, ['sub']] }
36
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
28
37
  end
29
- end.to_app
38
+ end
30
39
  Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
31
40
  Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub'
32
41
  end
33
42
 
34
43
  it "doesn't dupe env even when mapping" do
35
- app = Rack::Builder.new do
44
+ app = builder_to_app do
36
45
  use NothingMiddleware
37
46
  map '/' do |outer_env|
38
47
  run lambda { |inner_env|
39
48
  inner_env['new_key'] = 'new_value'
40
- [200, {}, ['root']]
49
+ [200, {"Content-Type" => "text/plain"}, ['root']]
41
50
  }
42
51
  end
43
- end.to_app
52
+ end
44
53
  Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
45
54
  NothingMiddleware.env['new_key'].should.equal 'new_value'
46
55
  end
47
56
 
48
57
  it "chains apps by default" do
49
- app = Rack::Builder.new do
58
+ app = builder_to_app do
50
59
  use Rack::ShowExceptions
51
60
  run lambda { |env| raise "bzzzt" }
52
- end.to_app
61
+ end
53
62
 
54
63
  Rack::MockRequest.new(app).get("/").should.be.server_error
55
64
  Rack::MockRequest.new(app).get("/").should.be.server_error
@@ -57,7 +66,7 @@ describe Rack::Builder do
57
66
  end
58
67
 
59
68
  it "has implicit #to_app" do
60
- app = Rack::Builder.new do
69
+ app = builder do
61
70
  use Rack::ShowExceptions
62
71
  run lambda { |env| raise "bzzzt" }
63
72
  end
@@ -68,13 +77,13 @@ describe Rack::Builder do
68
77
  end
69
78
 
70
79
  it "supports blocks on use" do
71
- app = Rack::Builder.new do
80
+ app = builder do
72
81
  use Rack::ShowExceptions
73
82
  use Rack::Auth::Basic do |username, password|
74
83
  'secret' == password
75
84
  end
76
85
 
77
- run lambda { |env| [200, {}, ['Hi Boss']] }
86
+ run lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hi Boss']] }
78
87
  end
79
88
 
80
89
  response = Rack::MockRequest.new(app).get("/")
@@ -89,7 +98,7 @@ describe Rack::Builder do
89
98
  end
90
99
 
91
100
  it "has explicit #to_app" do
92
- app = Rack::Builder.app do
101
+ app = builder do
93
102
  use Rack::ShowExceptions
94
103
  run lambda { |env| raise "bzzzt" }
95
104
  end
@@ -99,8 +108,30 @@ describe Rack::Builder do
99
108
  Rack::MockRequest.new(app).get("/").should.be.server_error
100
109
  end
101
110
 
111
+ it "can mix map and run for endpoints" do
112
+ app = builder do
113
+ map '/sub' do
114
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] }
115
+ end
116
+ run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] }
117
+ end
118
+
119
+ Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
120
+ Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub'
121
+ end
122
+
123
+ it "accepts middleware-only map blocks" do
124
+ app = builder do
125
+ map('/foo') { use Rack::ShowExceptions }
126
+ run lambda { |env| raise "bzzzt" }
127
+ end
128
+
129
+ proc { Rack::MockRequest.new(app).get("/") }.should.raise(RuntimeError)
130
+ Rack::MockRequest.new(app).get("/foo").should.be.server_error
131
+ end
132
+
102
133
  should "initialize apps once" do
103
- app = Rack::Builder.new do
134
+ app = builder do
104
135
  class AppClass
105
136
  def initialize
106
137
  @called = 0
@@ -120,6 +151,23 @@ describe Rack::Builder do
120
151
  Rack::MockRequest.new(app).get("/").should.be.server_error
121
152
  end
122
153
 
154
+ it "allows use after run" do
155
+ app = builder do
156
+ run lambda { |env| raise "bzzzt" }
157
+ use Rack::ShowExceptions
158
+ end
159
+
160
+ Rack::MockRequest.new(app).get("/").should.be.server_error
161
+ Rack::MockRequest.new(app).get("/").should.be.server_error
162
+ Rack::MockRequest.new(app).get("/").should.be.server_error
163
+ end
164
+
165
+ it 'complains about a missing run' do
166
+ proc do
167
+ Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions }
168
+ end.should.raise(RuntimeError)
169
+ end
170
+
123
171
  describe "parse_file" do
124
172
  def config_file(name)
125
173
  File.join(File.dirname(__FILE__), 'builder', name)
@@ -133,6 +181,7 @@ describe Rack::Builder do
133
181
 
134
182
  it "removes __END__ before evaluating app" do
135
183
  app, options = Rack::Builder.parse_file config_file('end.ru')
184
+ options = nil # ignored, prevents warning
136
185
  Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
137
186
  end
138
187
 
@@ -148,11 +197,5 @@ describe Rack::Builder do
148
197
  Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
149
198
  $:.pop
150
199
  end
151
-
152
- it "sets __LINE__ correctly" do
153
- app, options = Rack::Builder.parse_file config_file('line.ru')
154
- options = nil # ignored, prevents warning
155
- Rack::MockRequest.new(app).get("/").body.to_s.should.equal '1'
156
- end
157
200
  end
158
201
  end