rack 1.6.13 → 2.0.0.alpha

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 (138) hide show
  1. checksums.yaml +5 -5
  2. data/HISTORY.md +139 -18
  3. data/README.rdoc +17 -25
  4. data/Rakefile +6 -14
  5. data/SPEC +8 -9
  6. data/contrib/rack_logo.svg +164 -111
  7. data/lib/rack.rb +70 -21
  8. data/lib/rack/auth/digest/request.rb +1 -1
  9. data/lib/rack/body_proxy.rb +14 -9
  10. data/lib/rack/builder.rb +3 -3
  11. data/lib/rack/chunked.rb +5 -5
  12. data/lib/rack/{commonlogger.rb → common_logger.rb} +2 -2
  13. data/lib/rack/{conditionalget.rb → conditional_get.rb} +0 -0
  14. data/lib/rack/content_length.rb +2 -2
  15. data/lib/rack/deflater.rb +4 -4
  16. data/lib/rack/directory.rb +49 -55
  17. data/lib/rack/etag.rb +2 -1
  18. data/lib/rack/events.rb +154 -0
  19. data/lib/rack/file.rb +55 -40
  20. data/lib/rack/handler.rb +2 -24
  21. data/lib/rack/handler/cgi.rb +15 -16
  22. data/lib/rack/handler/fastcgi.rb +13 -14
  23. data/lib/rack/handler/lsws.rb +11 -11
  24. data/lib/rack/handler/scgi.rb +15 -15
  25. data/lib/rack/handler/thin.rb +3 -0
  26. data/lib/rack/handler/webrick.rb +22 -24
  27. data/lib/rack/head.rb +15 -17
  28. data/lib/rack/lint.rb +38 -38
  29. data/lib/rack/lobster.rb +1 -1
  30. data/lib/rack/lock.rb +6 -10
  31. data/lib/rack/logger.rb +2 -2
  32. data/lib/rack/media_type.rb +38 -0
  33. data/lib/rack/{methodoverride.rb → method_override.rb} +4 -11
  34. data/lib/rack/mime.rb +18 -5
  35. data/lib/rack/mock.rb +35 -52
  36. data/lib/rack/multipart.rb +35 -6
  37. data/lib/rack/multipart/generator.rb +4 -4
  38. data/lib/rack/multipart/parser.rb +273 -158
  39. data/lib/rack/multipart/uploaded_file.rb +1 -2
  40. data/lib/rack/{nulllogger.rb → null_logger.rb} +1 -1
  41. data/lib/rack/query_parser.rb +174 -0
  42. data/lib/rack/recursive.rb +8 -8
  43. data/lib/rack/reloader.rb +1 -2
  44. data/lib/rack/request.rb +370 -304
  45. data/lib/rack/response.rb +129 -56
  46. data/lib/rack/rewindable_input.rb +1 -12
  47. data/lib/rack/runtime.rb +10 -18
  48. data/lib/rack/sendfile.rb +5 -7
  49. data/lib/rack/server.rb +31 -25
  50. data/lib/rack/session/abstract/id.rb +93 -135
  51. data/lib/rack/session/cookie.rb +26 -28
  52. data/lib/rack/session/memcache.rb +8 -14
  53. data/lib/rack/session/pool.rb +14 -21
  54. data/lib/rack/show_exceptions.rb +386 -0
  55. data/lib/rack/{showstatus.rb → show_status.rb} +3 -3
  56. data/lib/rack/static.rb +30 -5
  57. data/lib/rack/tempfile_reaper.rb +2 -2
  58. data/lib/rack/urlmap.rb +13 -14
  59. data/lib/rack/utils.rb +128 -221
  60. data/rack.gemspec +9 -5
  61. data/test/builder/an_underscore_app.rb +5 -0
  62. data/test/builder/options.ru +1 -1
  63. data/test/cgi/test.fcgi +1 -0
  64. data/test/cgi/test.gz +0 -0
  65. data/test/helper.rb +31 -0
  66. data/test/multipart/filename_with_encoded_words +7 -0
  67. data/test/multipart/{filename_with_null_byte → filename_with_single_quote} +1 -1
  68. data/test/multipart/quoted +15 -0
  69. data/test/multipart/rack-logo.png +0 -0
  70. data/test/registering_handler/rack/handler/registering_myself.rb +1 -1
  71. data/test/spec_auth_basic.rb +20 -19
  72. data/test/spec_auth_digest.rb +47 -46
  73. data/test/spec_body_proxy.rb +27 -27
  74. data/test/spec_builder.rb +51 -41
  75. data/test/spec_cascade.rb +24 -22
  76. data/test/spec_cgi.rb +49 -67
  77. data/test/spec_chunked.rb +36 -34
  78. data/test/{spec_commonlogger.rb → spec_common_logger.rb} +23 -21
  79. data/test/{spec_conditionalget.rb → spec_conditional_get.rb} +29 -28
  80. data/test/spec_config.rb +3 -2
  81. data/test/spec_content_length.rb +18 -17
  82. data/test/spec_content_type.rb +13 -12
  83. data/test/spec_deflater.rb +66 -40
  84. data/test/spec_directory.rb +72 -27
  85. data/test/spec_etag.rb +32 -31
  86. data/test/spec_events.rb +133 -0
  87. data/test/spec_fastcgi.rb +50 -72
  88. data/test/spec_file.rb +96 -77
  89. data/test/spec_handler.rb +19 -34
  90. data/test/spec_head.rb +15 -14
  91. data/test/spec_lint.rb +162 -197
  92. data/test/spec_lobster.rb +24 -23
  93. data/test/spec_lock.rb +69 -39
  94. data/test/spec_logger.rb +4 -3
  95. data/test/spec_media_type.rb +42 -0
  96. data/test/spec_method_override.rb +83 -0
  97. data/test/spec_mime.rb +19 -19
  98. data/test/spec_mock.rb +196 -151
  99. data/test/spec_multipart.rb +310 -202
  100. data/test/{spec_nulllogger.rb → spec_null_logger.rb} +5 -4
  101. data/test/spec_recursive.rb +17 -14
  102. data/test/spec_request.rb +763 -607
  103. data/test/spec_response.rb +209 -156
  104. data/test/spec_rewindable_input.rb +50 -40
  105. data/test/spec_runtime.rb +11 -10
  106. data/test/spec_sendfile.rb +30 -35
  107. data/test/spec_server.rb +78 -52
  108. data/test/spec_session_abstract_id.rb +11 -33
  109. data/test/spec_session_cookie.rb +97 -65
  110. data/test/spec_session_memcache.rb +63 -101
  111. data/test/spec_session_pool.rb +48 -84
  112. data/test/spec_show_exceptions.rb +80 -0
  113. data/test/{spec_showstatus.rb → spec_show_status.rb} +36 -35
  114. data/test/spec_static.rb +71 -32
  115. data/test/spec_tempfile_reaper.rb +11 -10
  116. data/test/spec_thin.rb +55 -50
  117. data/test/spec_urlmap.rb +79 -78
  118. data/test/spec_utils.rb +417 -345
  119. data/test/spec_version.rb +2 -8
  120. data/test/spec_webrick.rb +77 -67
  121. data/test/static/foo.html +1 -0
  122. data/test/testrequest.rb +1 -1
  123. data/test/unregistered_handler/rack/handler/unregistered.rb +1 -1
  124. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +1 -1
  125. metadata +116 -71
  126. data/KNOWN-ISSUES +0 -44
  127. data/lib/rack/backports/uri/common_18.rb +0 -56
  128. data/lib/rack/backports/uri/common_192.rb +0 -52
  129. data/lib/rack/backports/uri/common_193.rb +0 -29
  130. data/lib/rack/handler/evented_mongrel.rb +0 -8
  131. data/lib/rack/handler/mongrel.rb +0 -106
  132. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  133. data/lib/rack/showexceptions.rb +0 -387
  134. data/lib/rack/utils/okjson.rb +0 -600
  135. data/test/spec_methodoverride.rb +0 -111
  136. data/test/spec_mongrel.rb +0 -182
  137. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  138. data/test/spec_showexceptions.rb +0 -98
@@ -1,3 +1,4 @@
1
+ require 'rack'
1
2
  require 'digest/md5'
2
3
 
3
4
  module Rack
@@ -11,7 +12,7 @@ module Rack
11
12
  # used when Etag is absent and a directive when it is present. The first
12
13
  # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate"
13
14
  class ETag
14
- ETAG_STRING = 'ETag'.freeze
15
+ ETAG_STRING = Rack::ETAG
15
16
  DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
16
17
 
17
18
  def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
@@ -0,0 +1,154 @@
1
+ require 'rack/response'
2
+ require 'rack/body_proxy'
3
+
4
+ module Rack
5
+ ### This middleware provides hooks to certain places in the request /
6
+ #response lifecycle. This is so that middleware that don't need to filter
7
+ #the response data can safely leave it alone and not have to send messages
8
+ #down the traditional "rack stack".
9
+ #
10
+ # The events are:
11
+ #
12
+ # * on_start(request, response)
13
+ #
14
+ # This event is sent at the start of the request, before the next
15
+ # middleware in the chain is called. This method is called with a request
16
+ # object, and a response object. Right now, the response object is always
17
+ # nil, but in the future it may actually be a real response object.
18
+ #
19
+ # * on_commit(request, response)
20
+ #
21
+ # The response has been committed. The application has returned, but the
22
+ # response has not been sent to the webserver yet. This method is always
23
+ # called with a request object and the response object. The response
24
+ # object is constructed from the rack triple that the application returned.
25
+ # Changes may still be made to the response object at this point.
26
+ #
27
+ # * on_send(request, response)
28
+ #
29
+ # The webserver has started iterating over the response body and presumably
30
+ # has started sending data over the wire. This method is always called with
31
+ # a request object and the response object. The response object is
32
+ # constructed from the rack triple that the application returned. Changes
33
+ # SHOULD NOT be made to the response object as the webserver has already
34
+ # started sending data. Any mutations will likely result in an exception.
35
+ #
36
+ # * on_finish(request, response)
37
+ #
38
+ # The webserver has closed the response, and all data has been written to
39
+ # the response socket. The request and response object should both be
40
+ # read-only at this point. The body MAY NOT be available on the response
41
+ # object as it may have been flushed to the socket.
42
+ #
43
+ # * on_error(request, response, error)
44
+ #
45
+ # An exception has occurred in the application or an `on_commit` event.
46
+ # This method will get the request, the response (if available) and the
47
+ # exception that was raised.
48
+ #
49
+ # ## Order
50
+ #
51
+ # `on_start` is called on the handlers in the order that they were passed to
52
+ # the constructor. `on_commit`, on_send`, `on_finish`, and `on_error` are
53
+ # called in the reverse order. `on_finish` handlers are called inside an
54
+ # `ensure` block, so they are guaranteed to be called even if something
55
+ # raises an exception. If something raises an exception in a `on_finish`
56
+ # method, then nothing is guaranteed.
57
+
58
+ class Events
59
+ module Abstract
60
+ def on_start req, res
61
+ end
62
+
63
+ def on_commit req, res
64
+ end
65
+
66
+ def on_send req, res
67
+ end
68
+
69
+ def on_finish req, res
70
+ end
71
+
72
+ def on_error req, res, e
73
+ end
74
+ end
75
+
76
+ class EventedBodyProxy < Rack::BodyProxy # :nodoc:
77
+ attr_reader :request, :response
78
+
79
+ def initialize body, request, response, handlers, &block
80
+ super(body, &block)
81
+ @request = request
82
+ @response = response
83
+ @handlers = handlers
84
+ end
85
+
86
+ def each
87
+ @handlers.reverse_each { |handler| handler.on_send request, response }
88
+ super
89
+ end
90
+ end
91
+
92
+ class BufferedResponse < Rack::Response::Raw # :nodoc:
93
+ attr_reader :body
94
+
95
+ def initialize status, headers, body
96
+ super(status, headers)
97
+ @body = body
98
+ end
99
+
100
+ def to_a; [status, headers, body]; end
101
+ end
102
+
103
+ def initialize app, handlers
104
+ @app = app
105
+ @handlers = handlers
106
+ end
107
+
108
+ def call env
109
+ request = make_request env
110
+ on_start request, nil
111
+
112
+ begin
113
+ status, headers, body = @app.call request.env
114
+ response = make_response status, headers, body
115
+ on_commit request, response
116
+ rescue StandardError => e
117
+ on_error request, response, e
118
+ on_finish request, response
119
+ raise
120
+ end
121
+
122
+ body = EventedBodyProxy.new(body, request, response, @handlers) do
123
+ on_finish request, response
124
+ end
125
+ [response.status, response.headers, body]
126
+ end
127
+
128
+ private
129
+
130
+ def on_error request, response, e
131
+ @handlers.reverse_each { |handler| handler.on_error request, response, e }
132
+ end
133
+
134
+ def on_commit request, response
135
+ @handlers.reverse_each { |handler| handler.on_commit request, response }
136
+ end
137
+
138
+ def on_start request, response
139
+ @handlers.each { |handler| handler.on_start request, nil }
140
+ end
141
+
142
+ def on_finish request, response
143
+ @handlers.reverse_each { |handler| handler.on_finish request, response }
144
+ end
145
+
146
+ def make_request env
147
+ Rack::Request.new env
148
+ end
149
+
150
+ def make_response status, headers, body
151
+ BufferedResponse.new status, headers, body
152
+ end
153
+ end
154
+ end
@@ -1,6 +1,7 @@
1
1
  require 'time'
2
2
  require 'rack/utils'
3
3
  require 'rack/mime'
4
+ require 'rack/request'
4
5
 
5
6
  module Rack
6
7
  # Rack::File serves files below the +root+ directory given, according to the
@@ -15,11 +16,7 @@ module Rack
15
16
  ALLOWED_VERBS = %w[GET HEAD OPTIONS]
16
17
  ALLOW_HEADER = ALLOWED_VERBS.join(', ')
17
18
 
18
- attr_accessor :root
19
- attr_accessor :path
20
- attr_accessor :cache_control
21
-
22
- alias :to_path :path
19
+ attr_reader :root
23
20
 
24
21
  def initialize(root, headers={}, default_mime = 'text/plain')
25
22
  @root = root
@@ -28,57 +25,54 @@ module Rack
28
25
  end
29
26
 
30
27
  def call(env)
31
- dup._call(env)
32
- end
33
-
34
- F = ::File
35
-
36
- def _call(env)
37
- unless ALLOWED_VERBS.include? env[REQUEST_METHOD]
28
+ request = Rack::Request.new env
29
+ unless ALLOWED_VERBS.include? request.request_method
38
30
  return fail(405, "Method Not Allowed", {'Allow' => ALLOW_HEADER})
39
31
  end
40
32
 
41
- path_info = Utils.unescape(env[PATH_INFO])
33
+ path_info = Utils.unescape_path request.path_info
42
34
  clean_path_info = Utils.clean_path_info(path_info)
43
35
 
44
- @path = F.join(@root, clean_path_info)
36
+ path = ::File.join(@root, clean_path_info)
45
37
 
46
38
  available = begin
47
- F.file?(@path) && F.readable?(@path)
39
+ ::File.file?(path) && ::File.readable?(path)
48
40
  rescue SystemCallError
49
41
  false
50
42
  end
51
43
 
52
44
  if available
53
- serving(env)
45
+ serving(request, path)
54
46
  else
55
47
  fail(404, "File not found: #{path_info}")
56
48
  end
57
49
  end
58
50
 
59
- def serving(env)
60
- if env["REQUEST_METHOD"] == "OPTIONS"
51
+ def serving(request, path)
52
+ if request.options?
61
53
  return [200, {'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0'}, []]
62
54
  end
63
- last_modified = F.mtime(@path).httpdate
64
- return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
55
+ last_modified = ::File.mtime(path).httpdate
56
+ return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
65
57
 
66
58
  headers = { "Last-Modified" => last_modified }
59
+ mime_type = mime_type path, @default_mime
67
60
  headers[CONTENT_TYPE] = mime_type if mime_type
68
61
 
69
62
  # Set custom headers
70
63
  @headers.each { |field, content| headers[field] = content } if @headers
71
64
 
72
- response = [ 200, headers, env[REQUEST_METHOD] == "HEAD" ? [] : self ]
65
+ response = [ 200, headers ]
73
66
 
74
- size = filesize
67
+ size = filesize path
75
68
 
76
- ranges = Rack::Utils.byte_ranges(env, size)
69
+ range = nil
70
+ ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size)
77
71
  if ranges.nil? || ranges.length > 1
78
72
  # No ranges, or multiple ranges (which we don't support):
79
73
  # TODO: Support multiple byte-ranges
80
74
  response[0] = 200
81
- @range = 0..size-1
75
+ range = 0..size-1
82
76
  elsif ranges.empty?
83
77
  # Unsatisfiable. Return error, and file size:
84
78
  response = fail(416, "Byte range unsatisfiable")
@@ -86,34 +80,55 @@ module Rack
86
80
  return response
87
81
  else
88
82
  # Partial content:
89
- @range = ranges[0]
83
+ range = ranges[0]
90
84
  response[0] = 206
91
- response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
92
- size = @range.end - @range.begin + 1
85
+ response[1]["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
86
+ size = range.end - range.begin + 1
93
87
  end
94
88
 
95
89
  response[2] = [response_body] unless response_body.nil?
96
90
 
97
91
  response[1][CONTENT_LENGTH] = size.to_s
92
+ response[2] = make_body request, path, range
98
93
  response
99
94
  end
100
95
 
101
- def each
102
- F.open(@path, "rb") do |file|
103
- file.seek(@range.begin)
104
- remaining_len = @range.end-@range.begin+1
105
- while remaining_len > 0
106
- part = file.read([8192, remaining_len].min)
107
- break unless part
108
- remaining_len -= part.length
96
+ class Iterator
97
+ attr_reader :path, :range
98
+ alias :to_path :path
99
+
100
+ def initialize path, range
101
+ @path = path
102
+ @range = range
103
+ end
109
104
 
110
- yield part
105
+ def each
106
+ ::File.open(path, "rb") do |file|
107
+ file.seek(range.begin)
108
+ remaining_len = range.end-range.begin+1
109
+ while remaining_len > 0
110
+ part = file.read([8192, remaining_len].min)
111
+ break unless part
112
+ remaining_len -= part.length
113
+
114
+ yield part
115
+ end
111
116
  end
112
117
  end
118
+
119
+ def close; end
113
120
  end
114
121
 
115
122
  private
116
123
 
124
+ def make_body request, path, range
125
+ if request.head?
126
+ []
127
+ else
128
+ Iterator.new path, range
129
+ end
130
+ end
131
+
117
132
  def fail(status, body, headers = {})
118
133
  body += "\n"
119
134
  [
@@ -128,18 +143,18 @@ module Rack
128
143
  end
129
144
 
130
145
  # The MIME type for the contents of the file located at @path
131
- def mime_type
132
- Mime.mime_type(F.extname(@path), @default_mime)
146
+ def mime_type path, default_mime
147
+ Mime.mime_type(::File.extname(path), default_mime)
133
148
  end
134
149
 
135
- def filesize
150
+ def filesize path
136
151
  # If response_body is present, use its size.
137
152
  return Rack::Utils.bytesize(response_body) if response_body
138
153
 
139
154
  # We check via File::size? whether this file provides size info
140
155
  # via stat (e.g. /proc files often don't), otherwise we have to
141
156
  # figure it out by reading the whole file into memory.
142
- F.size?(@path) || Utils.bytesize(F.read(@path))
157
+ ::File.size?(path) || ::File.read(path).bytesize
143
158
  end
144
159
 
145
160
  # By default, the response body for file requests is nil.
@@ -19,25 +19,13 @@ module Rack
19
19
  if klass = @handlers[server]
20
20
  klass.split("::").inject(Object) { |o, x| o.const_get(x) }
21
21
  else
22
- _const_get(server, false)
22
+ const_get(server, false)
23
23
  end
24
24
 
25
25
  rescue NameError => name_error
26
26
  raise load_error || name_error
27
27
  end
28
28
 
29
- begin
30
- ::Object.const_get("Object", false)
31
- def self._const_get(str, inherit = true)
32
- const_get(str, inherit)
33
- end
34
- rescue
35
- def self._const_get(str, inherit = true)
36
- const_get(str)
37
- end
38
- end
39
-
40
-
41
29
  # Select first available Rack handler given an `Array` of server names.
42
30
  # Raises `LoadError` if no handler was found.
43
31
  #
@@ -55,13 +43,9 @@ module Rack
55
43
  raise LoadError, "Couldn't find handler for: #{server_names.join(', ')}."
56
44
  end
57
45
 
58
- def self.default(options = {})
46
+ def self.default
59
47
  # Guess.
60
48
  if ENV.include?("PHP_FCGI_CHILDREN")
61
- # We already speak FastCGI
62
- options.delete :File
63
- options.delete :Port
64
-
65
49
  Rack::Handler::FastCGI
66
50
  elsif ENV.include?(REQUEST_METHOD)
67
51
  Rack::Handler::CGI
@@ -100,9 +84,6 @@ module Rack
100
84
 
101
85
  autoload :CGI, "rack/handler/cgi"
102
86
  autoload :FastCGI, "rack/handler/fastcgi"
103
- autoload :Mongrel, "rack/handler/mongrel"
104
- autoload :EventedMongrel, "rack/handler/evented_mongrel"
105
- autoload :SwiftipliedMongrel, "rack/handler/swiftiplied_mongrel"
106
87
  autoload :WEBrick, "rack/handler/webrick"
107
88
  autoload :LSWS, "rack/handler/lsws"
108
89
  autoload :SCGI, "rack/handler/scgi"
@@ -110,9 +91,6 @@ module Rack
110
91
 
111
92
  register 'cgi', 'Rack::Handler::CGI'
112
93
  register 'fastcgi', 'Rack::Handler::FastCGI'
113
- register 'mongrel', 'Rack::Handler::Mongrel'
114
- register 'emongrel', 'Rack::Handler::EventedMongrel'
115
- register 'smongrel', 'Rack::Handler::SwiftipliedMongrel'
116
94
  register 'webrick', 'Rack::Handler::WEBrick'
117
95
  register 'lsws', 'Rack::Handler::LSWS'
118
96
  register 'scgi', 'Rack::Handler::SCGI'
@@ -13,22 +13,21 @@ module Rack
13
13
  env = ENV.to_hash
14
14
  env.delete "HTTP_CONTENT_LENGTH"
15
15
 
16
- env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
17
-
18
- env.update({"rack.version" => Rack::VERSION,
19
- "rack.input" => Rack::RewindableInput.new($stdin),
20
- "rack.errors" => $stderr,
21
-
22
- "rack.multithread" => false,
23
- "rack.multiprocess" => true,
24
- "rack.run_once" => true,
25
-
26
- "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
27
- })
28
-
29
- env[QUERY_STRING] ||= ""
30
- env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
31
- env["REQUEST_PATH"] ||= "/"
16
+ env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/"
17
+
18
+ env.update(
19
+ RACK_VERSION => Rack::VERSION,
20
+ RACK_INPUT => Rack::RewindableInput.new($stdin),
21
+ RACK_ERRORS => $stderr,
22
+ RACK_MULTITHREAD => false,
23
+ RACK_MULTIPROCESS => true,
24
+ RACK_RUNONCE => true,
25
+ RACK_URL_SCHEME => ["yes", "on", "1"].include?(ENV[HTTPS]) ? "https" : "http"
26
+ )
27
+
28
+ env[QUERY_STRING] ||= ""
29
+ env[HTTP_VERSION] ||= env[SERVER_PROTOCOL]
30
+ env[REQUEST_PATH] ||= "/"
32
31
 
33
32
  status, headers, body = app.call(env)
34
33
  begin