rack 2.1.0 → 2.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +110 -5
- data/CONTRIBUTING.md +136 -0
- data/README.rdoc +83 -39
- data/Rakefile +14 -7
- data/{SPEC → SPEC.rdoc} +35 -6
- data/lib/rack/auth/abstract/request.rb +0 -2
- data/lib/rack/auth/basic.rb +3 -3
- data/lib/rack/auth/digest/md5.rb +4 -4
- data/lib/rack/auth/digest/request.rb +3 -3
- data/lib/rack/body_proxy.rb +13 -9
- data/lib/rack/builder.rb +78 -8
- data/lib/rack/cascade.rb +23 -8
- data/lib/rack/chunked.rb +48 -23
- data/lib/rack/common_logger.rb +25 -18
- data/lib/rack/conditional_get.rb +18 -16
- data/lib/rack/content_length.rb +6 -7
- data/lib/rack/content_type.rb +3 -4
- data/lib/rack/deflater.rb +49 -35
- data/lib/rack/directory.rb +77 -60
- data/lib/rack/etag.rb +2 -3
- data/lib/rack/events.rb +15 -18
- data/lib/rack/file.rb +1 -2
- data/lib/rack/files.rb +97 -57
- data/lib/rack/handler/cgi.rb +1 -4
- data/lib/rack/handler/fastcgi.rb +1 -3
- data/lib/rack/handler/lsws.rb +1 -3
- data/lib/rack/handler/scgi.rb +1 -3
- data/lib/rack/handler/thin.rb +15 -11
- data/lib/rack/handler/webrick.rb +12 -5
- data/lib/rack/head.rb +0 -2
- data/lib/rack/lint.rb +57 -14
- data/lib/rack/lobster.rb +3 -5
- data/lib/rack/lock.rb +0 -1
- data/lib/rack/mock.rb +22 -4
- data/lib/rack/multipart/generator.rb +11 -6
- data/lib/rack/multipart/parser.rb +10 -18
- data/lib/rack/multipart/uploaded_file.rb +13 -7
- data/lib/rack/multipart.rb +1 -1
- data/lib/rack/query_parser.rb +7 -8
- data/lib/rack/recursive.rb +1 -1
- data/lib/rack/reloader.rb +1 -3
- data/lib/rack/request.rb +172 -76
- data/lib/rack/response.rb +62 -19
- data/lib/rack/rewindable_input.rb +0 -1
- data/lib/rack/runtime.rb +3 -3
- data/lib/rack/sendfile.rb +0 -3
- data/lib/rack/server.rb +9 -10
- data/lib/rack/session/abstract/id.rb +22 -28
- data/lib/rack/session/cookie.rb +2 -3
- data/lib/rack/session/pool.rb +1 -1
- data/lib/rack/show_exceptions.rb +2 -4
- data/lib/rack/show_status.rb +1 -3
- data/lib/rack/static.rb +13 -6
- data/lib/rack/tempfile_reaper.rb +0 -2
- data/lib/rack/urlmap.rb +1 -4
- data/lib/rack/utils.rb +58 -54
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +7 -16
- data/rack.gemspec +31 -29
- metadata +11 -12
data/lib/rack/events.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'rack/response'
|
|
4
|
-
require 'rack/body_proxy'
|
|
5
|
-
|
|
6
3
|
module Rack
|
|
7
4
|
### This middleware provides hooks to certain places in the request /
|
|
8
5
|
# response lifecycle. This is so that middleware that don't need to filter
|
|
@@ -59,26 +56,26 @@ module Rack
|
|
|
59
56
|
|
|
60
57
|
class Events
|
|
61
58
|
module Abstract
|
|
62
|
-
def on_start
|
|
59
|
+
def on_start(req, res)
|
|
63
60
|
end
|
|
64
61
|
|
|
65
|
-
def on_commit
|
|
62
|
+
def on_commit(req, res)
|
|
66
63
|
end
|
|
67
64
|
|
|
68
|
-
def on_send
|
|
65
|
+
def on_send(req, res)
|
|
69
66
|
end
|
|
70
67
|
|
|
71
|
-
def on_finish
|
|
68
|
+
def on_finish(req, res)
|
|
72
69
|
end
|
|
73
70
|
|
|
74
|
-
def on_error
|
|
71
|
+
def on_error(req, res, e)
|
|
75
72
|
end
|
|
76
73
|
end
|
|
77
74
|
|
|
78
75
|
class EventedBodyProxy < Rack::BodyProxy # :nodoc:
|
|
79
76
|
attr_reader :request, :response
|
|
80
77
|
|
|
81
|
-
def initialize
|
|
78
|
+
def initialize(body, request, response, handlers, &block)
|
|
82
79
|
super(body, &block)
|
|
83
80
|
@request = request
|
|
84
81
|
@response = response
|
|
@@ -94,7 +91,7 @@ module Rack
|
|
|
94
91
|
class BufferedResponse < Rack::Response::Raw # :nodoc:
|
|
95
92
|
attr_reader :body
|
|
96
93
|
|
|
97
|
-
def initialize
|
|
94
|
+
def initialize(status, headers, body)
|
|
98
95
|
super(status, headers)
|
|
99
96
|
@body = body
|
|
100
97
|
end
|
|
@@ -102,12 +99,12 @@ module Rack
|
|
|
102
99
|
def to_a; [status, headers, body]; end
|
|
103
100
|
end
|
|
104
101
|
|
|
105
|
-
def initialize
|
|
102
|
+
def initialize(app, handlers)
|
|
106
103
|
@app = app
|
|
107
104
|
@handlers = handlers
|
|
108
105
|
end
|
|
109
106
|
|
|
110
|
-
def call
|
|
107
|
+
def call(env)
|
|
111
108
|
request = make_request env
|
|
112
109
|
on_start request, nil
|
|
113
110
|
|
|
@@ -129,27 +126,27 @@ module Rack
|
|
|
129
126
|
|
|
130
127
|
private
|
|
131
128
|
|
|
132
|
-
def on_error
|
|
129
|
+
def on_error(request, response, e)
|
|
133
130
|
@handlers.reverse_each { |handler| handler.on_error request, response, e }
|
|
134
131
|
end
|
|
135
132
|
|
|
136
|
-
def on_commit
|
|
133
|
+
def on_commit(request, response)
|
|
137
134
|
@handlers.reverse_each { |handler| handler.on_commit request, response }
|
|
138
135
|
end
|
|
139
136
|
|
|
140
|
-
def on_start
|
|
137
|
+
def on_start(request, response)
|
|
141
138
|
@handlers.each { |handler| handler.on_start request, nil }
|
|
142
139
|
end
|
|
143
140
|
|
|
144
|
-
def on_finish
|
|
141
|
+
def on_finish(request, response)
|
|
145
142
|
@handlers.reverse_each { |handler| handler.on_finish request, response }
|
|
146
143
|
end
|
|
147
144
|
|
|
148
|
-
def make_request
|
|
145
|
+
def make_request(env)
|
|
149
146
|
Rack::Request.new env
|
|
150
147
|
end
|
|
151
148
|
|
|
152
|
-
def make_response
|
|
149
|
+
def make_response(status, headers, body)
|
|
153
150
|
BufferedResponse.new status, headers, body
|
|
154
151
|
end
|
|
155
152
|
end
|
data/lib/rack/file.rb
CHANGED
data/lib/rack/files.rb
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'time'
|
|
4
|
-
require 'rack/utils'
|
|
5
|
-
require 'rack/mime'
|
|
6
|
-
require 'rack/request'
|
|
7
|
-
require 'rack/head'
|
|
8
4
|
|
|
9
5
|
module Rack
|
|
10
6
|
# Rack::Files serves files below the +root+ directory given, according to the
|
|
@@ -18,11 +14,20 @@ module Rack
|
|
|
18
14
|
class Files
|
|
19
15
|
ALLOWED_VERBS = %w[GET HEAD OPTIONS]
|
|
20
16
|
ALLOW_HEADER = ALLOWED_VERBS.join(', ')
|
|
17
|
+
MULTIPART_BOUNDARY = 'AaB03x'
|
|
18
|
+
|
|
19
|
+
# @todo remove in 3.0
|
|
20
|
+
def self.method_added(name)
|
|
21
|
+
if name == :response_body
|
|
22
|
+
raise "#{self.class}\#response_body is no longer supported."
|
|
23
|
+
end
|
|
24
|
+
super
|
|
25
|
+
end
|
|
21
26
|
|
|
22
27
|
attr_reader :root
|
|
23
28
|
|
|
24
29
|
def initialize(root, headers = {}, default_mime = 'text/plain')
|
|
25
|
-
@root = ::File.expand_path root
|
|
30
|
+
@root = (::File.expand_path(root) if root)
|
|
26
31
|
@headers = headers
|
|
27
32
|
@default_mime = default_mime
|
|
28
33
|
@head = Rack::Head.new(lambda { |env| get env })
|
|
@@ -48,7 +53,11 @@ module Rack
|
|
|
48
53
|
available = begin
|
|
49
54
|
::File.file?(path) && ::File.readable?(path)
|
|
50
55
|
rescue SystemCallError
|
|
56
|
+
# Not sure in what conditions this exception can occur, but this
|
|
57
|
+
# is a safe way to handle such an error.
|
|
58
|
+
# :nocov:
|
|
51
59
|
false
|
|
60
|
+
# :nocov:
|
|
52
61
|
end
|
|
53
62
|
|
|
54
63
|
if available
|
|
@@ -70,75 +79,116 @@ module Rack
|
|
|
70
79
|
headers[CONTENT_TYPE] = mime_type if mime_type
|
|
71
80
|
|
|
72
81
|
# Set custom headers
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
response = [ 200, headers ]
|
|
82
|
+
headers.merge!(@headers) if @headers
|
|
76
83
|
|
|
84
|
+
status = 200
|
|
77
85
|
size = filesize path
|
|
78
86
|
|
|
79
|
-
range = nil
|
|
80
87
|
ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size)
|
|
81
|
-
if ranges.nil?
|
|
82
|
-
# No ranges
|
|
83
|
-
|
|
84
|
-
response[0] = 200
|
|
85
|
-
range = 0..size - 1
|
|
88
|
+
if ranges.nil?
|
|
89
|
+
# No ranges:
|
|
90
|
+
ranges = [0..size - 1]
|
|
86
91
|
elsif ranges.empty?
|
|
87
92
|
# Unsatisfiable. Return error, and file size:
|
|
88
93
|
response = fail(416, "Byte range unsatisfiable")
|
|
89
94
|
response[1]["Content-Range"] = "bytes */#{size}"
|
|
90
95
|
return response
|
|
91
|
-
|
|
92
|
-
# Partial content
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
elsif ranges.size >= 1
|
|
97
|
+
# Partial content
|
|
98
|
+
partial_content = true
|
|
99
|
+
|
|
100
|
+
if ranges.size == 1
|
|
101
|
+
range = ranges[0]
|
|
102
|
+
headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
|
|
103
|
+
else
|
|
104
|
+
headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
status = 206
|
|
108
|
+
body = BaseIterator.new(path, ranges, mime_type: mime_type, size: size)
|
|
109
|
+
size = body.bytesize
|
|
97
110
|
end
|
|
98
111
|
|
|
99
|
-
|
|
112
|
+
headers[CONTENT_LENGTH] = size.to_s
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
if request.head?
|
|
115
|
+
body = []
|
|
116
|
+
elsif !partial_content
|
|
117
|
+
body = Iterator.new(path, ranges, mime_type: mime_type, size: size)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
[status, headers, body]
|
|
104
121
|
end
|
|
105
122
|
|
|
106
|
-
class
|
|
107
|
-
attr_reader :path, :
|
|
108
|
-
alias :to_path :path
|
|
123
|
+
class BaseIterator
|
|
124
|
+
attr_reader :path, :ranges, :options
|
|
109
125
|
|
|
110
|
-
def initialize
|
|
111
|
-
@path
|
|
112
|
-
@
|
|
126
|
+
def initialize(path, ranges, options)
|
|
127
|
+
@path = path
|
|
128
|
+
@ranges = ranges
|
|
129
|
+
@options = options
|
|
113
130
|
end
|
|
114
131
|
|
|
115
132
|
def each
|
|
116
133
|
::File.open(path, "rb") do |file|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
yield part
|
|
134
|
+
ranges.each do |range|
|
|
135
|
+
yield multipart_heading(range) if multipart?
|
|
136
|
+
|
|
137
|
+
each_range_part(file, range) do |part|
|
|
138
|
+
yield part
|
|
139
|
+
end
|
|
125
140
|
end
|
|
141
|
+
|
|
142
|
+
yield "\r\n--#{MULTIPART_BOUNDARY}--\r\n" if multipart?
|
|
126
143
|
end
|
|
127
144
|
end
|
|
128
145
|
|
|
146
|
+
def bytesize
|
|
147
|
+
size = ranges.inject(0) do |sum, range|
|
|
148
|
+
sum += multipart_heading(range).bytesize if multipart?
|
|
149
|
+
sum += range.size
|
|
150
|
+
end
|
|
151
|
+
size += "\r\n--#{MULTIPART_BOUNDARY}--\r\n".bytesize if multipart?
|
|
152
|
+
size
|
|
153
|
+
end
|
|
154
|
+
|
|
129
155
|
def close; end
|
|
130
|
-
end
|
|
131
156
|
|
|
132
|
-
|
|
157
|
+
private
|
|
133
158
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
159
|
+
def multipart?
|
|
160
|
+
ranges.size > 1
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def multipart_heading(range)
|
|
164
|
+
<<-EOF
|
|
165
|
+
\r
|
|
166
|
+
--#{MULTIPART_BOUNDARY}\r
|
|
167
|
+
Content-Type: #{options[:mime_type]}\r
|
|
168
|
+
Content-Range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r
|
|
169
|
+
\r
|
|
170
|
+
EOF
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def each_range_part(file, range)
|
|
174
|
+
file.seek(range.begin)
|
|
175
|
+
remaining_len = range.end - range.begin + 1
|
|
176
|
+
while remaining_len > 0
|
|
177
|
+
part = file.read([8192, remaining_len].min)
|
|
178
|
+
break unless part
|
|
179
|
+
remaining_len -= part.length
|
|
180
|
+
|
|
181
|
+
yield part
|
|
182
|
+
end
|
|
139
183
|
end
|
|
140
184
|
end
|
|
141
185
|
|
|
186
|
+
class Iterator < BaseIterator
|
|
187
|
+
alias :to_path :path
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
142
192
|
def fail(status, body, headers = {})
|
|
143
193
|
body += "\n"
|
|
144
194
|
|
|
@@ -154,25 +204,15 @@ module Rack
|
|
|
154
204
|
end
|
|
155
205
|
|
|
156
206
|
# The MIME type for the contents of the file located at @path
|
|
157
|
-
def mime_type
|
|
207
|
+
def mime_type(path, default_mime)
|
|
158
208
|
Mime.mime_type(::File.extname(path), default_mime)
|
|
159
209
|
end
|
|
160
210
|
|
|
161
|
-
def filesize
|
|
162
|
-
# If response_body is present, use its size.
|
|
163
|
-
return response_body.bytesize if response_body
|
|
164
|
-
|
|
211
|
+
def filesize(path)
|
|
165
212
|
# We check via File::size? whether this file provides size info
|
|
166
213
|
# via stat (e.g. /proc files often don't), otherwise we have to
|
|
167
214
|
# figure it out by reading the whole file into memory.
|
|
168
215
|
::File.size?(path) || ::File.read(path).bytesize
|
|
169
216
|
end
|
|
170
|
-
|
|
171
|
-
# By default, the response body for file requests is nil.
|
|
172
|
-
# In this case, the response body will be generated later
|
|
173
|
-
# from the file at @path
|
|
174
|
-
def response_body
|
|
175
|
-
nil
|
|
176
|
-
end
|
|
177
217
|
end
|
|
178
218
|
end
|
data/lib/rack/handler/cgi.rb
CHANGED
data/lib/rack/handler/fastcgi.rb
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'fcgi'
|
|
4
4
|
require 'socket'
|
|
5
|
-
require 'rack/content_length'
|
|
6
|
-
require 'rack/rewindable_input'
|
|
7
5
|
|
|
8
6
|
if defined? FCGI::Stream
|
|
9
7
|
class FCGI::Stream
|
|
@@ -20,7 +18,7 @@ end
|
|
|
20
18
|
module Rack
|
|
21
19
|
module Handler
|
|
22
20
|
class FastCGI
|
|
23
|
-
def self.run(app, options
|
|
21
|
+
def self.run(app, **options)
|
|
24
22
|
if options[:File]
|
|
25
23
|
STDIN.reopen(UNIXServer.new(options[:File]))
|
|
26
24
|
elsif options[:Port]
|
data/lib/rack/handler/lsws.rb
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'lsapi'
|
|
4
|
-
require 'rack/content_length'
|
|
5
|
-
require 'rack/rewindable_input'
|
|
6
4
|
|
|
7
5
|
module Rack
|
|
8
6
|
module Handler
|
|
9
7
|
class LSWS
|
|
10
|
-
def self.run(app, options
|
|
8
|
+
def self.run(app, **options)
|
|
11
9
|
while LSAPI.accept != nil
|
|
12
10
|
serve app
|
|
13
11
|
end
|
data/lib/rack/handler/scgi.rb
CHANGED
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require 'scgi'
|
|
4
4
|
require 'stringio'
|
|
5
|
-
require 'rack/content_length'
|
|
6
|
-
require 'rack/chunked'
|
|
7
5
|
|
|
8
6
|
module Rack
|
|
9
7
|
module Handler
|
|
10
8
|
class SCGI < ::SCGI::Processor
|
|
11
9
|
attr_accessor :app
|
|
12
10
|
|
|
13
|
-
def self.run(app, options
|
|
11
|
+
def self.run(app, **options)
|
|
14
12
|
options[:Socket] = UNIXServer.new(options[:File]) if options[:File]
|
|
15
13
|
new(options.merge(app: app,
|
|
16
14
|
host: options[:Host],
|
data/lib/rack/handler/thin.rb
CHANGED
|
@@ -4,24 +4,28 @@ require "thin"
|
|
|
4
4
|
require "thin/server"
|
|
5
5
|
require "thin/logging"
|
|
6
6
|
require "thin/backends/tcp_server"
|
|
7
|
-
require "rack/content_length"
|
|
8
|
-
require "rack/chunked"
|
|
9
7
|
|
|
10
8
|
module Rack
|
|
11
9
|
module Handler
|
|
12
10
|
class Thin
|
|
13
|
-
def self.run(app, options
|
|
11
|
+
def self.run(app, **options)
|
|
14
12
|
environment = ENV['RACK_ENV'] || 'development'
|
|
15
13
|
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
if block_given?
|
|
16
|
+
host = options.delete(:Host) || default_host
|
|
17
|
+
port = options.delete(:Port) || 8080
|
|
18
|
+
args = [host, port, app, options]
|
|
19
|
+
# Thin versions below 0.8.0 do not support additional options
|
|
20
|
+
args.pop if ::Thin::VERSION::MAJOR < 1 && ::Thin::VERSION::MINOR < 8
|
|
21
|
+
server = ::Thin::Server.new(*args)
|
|
22
|
+
yield server
|
|
23
|
+
server.start
|
|
24
|
+
else
|
|
25
|
+
options[:address] = options[:Host] || default_host
|
|
26
|
+
options[:port] = options[:Port] || 8080
|
|
27
|
+
::Thin::Controllers::Controller.new(options).start
|
|
28
|
+
end
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def self.valid_options
|
data/lib/rack/handler/webrick.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'webrick'
|
|
4
4
|
require 'stringio'
|
|
5
|
-
require 'rack/content_length'
|
|
6
5
|
|
|
7
6
|
# This monkey patch allows for applications to perform their own chunking
|
|
8
7
|
# through WEBrick::HTTPResponse if rack is set to true.
|
|
@@ -24,12 +23,18 @@ end
|
|
|
24
23
|
module Rack
|
|
25
24
|
module Handler
|
|
26
25
|
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
|
|
27
|
-
def self.run(app, options
|
|
26
|
+
def self.run(app, **options)
|
|
28
27
|
environment = ENV['RACK_ENV'] || 'development'
|
|
29
28
|
default_host = environment == 'development' ? 'localhost' : nil
|
|
30
29
|
|
|
31
|
-
options[:BindAddress]
|
|
30
|
+
if !options[:BindAddress] || options[:Host]
|
|
31
|
+
options[:BindAddress] = options.delete(:Host) || default_host
|
|
32
|
+
end
|
|
32
33
|
options[:Port] ||= 8080
|
|
34
|
+
if options[:SSLEnable]
|
|
35
|
+
require 'webrick/https'
|
|
36
|
+
end
|
|
37
|
+
|
|
33
38
|
@server = ::WEBrick::HTTPServer.new(options)
|
|
34
39
|
@server.mount "/", Rack::Handler::WEBrick, app
|
|
35
40
|
yield @server if block_given?
|
|
@@ -47,8 +52,10 @@ module Rack
|
|
|
47
52
|
end
|
|
48
53
|
|
|
49
54
|
def self.shutdown
|
|
50
|
-
@server
|
|
51
|
-
|
|
55
|
+
if @server
|
|
56
|
+
@server.shutdown
|
|
57
|
+
@server = nil
|
|
58
|
+
end
|
|
52
59
|
end
|
|
53
60
|
|
|
54
61
|
def initialize(server, app)
|
data/lib/rack/head.rb
CHANGED
data/lib/rack/lint.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'rack/utils'
|
|
4
3
|
require 'forwardable'
|
|
5
4
|
|
|
6
5
|
module Rack
|
|
@@ -48,13 +47,24 @@ module Rack
|
|
|
48
47
|
env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS])
|
|
49
48
|
|
|
50
49
|
## and returns an Array of exactly three values:
|
|
51
|
-
|
|
50
|
+
ary = @app.call(env)
|
|
51
|
+
assert("response #{ary.inspect} is not an Array , but #{ary.class}") {
|
|
52
|
+
ary.kind_of? Array
|
|
53
|
+
}
|
|
54
|
+
assert("response array #{ary.inspect} has #{ary.size} elements instead of 3") {
|
|
55
|
+
ary.size == 3
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
status, headers, @body = ary
|
|
52
59
|
## The *status*,
|
|
53
60
|
check_status status
|
|
54
61
|
## the *headers*,
|
|
55
62
|
check_headers headers
|
|
56
63
|
|
|
57
|
-
check_hijack_response headers, env
|
|
64
|
+
hijack_proc = check_hijack_response headers, env
|
|
65
|
+
if hijack_proc && headers.is_a?(Hash)
|
|
66
|
+
headers[RACK_HIJACK] = hijack_proc
|
|
67
|
+
end
|
|
58
68
|
|
|
59
69
|
## and the *body*.
|
|
60
70
|
check_content_type status, headers
|
|
@@ -65,12 +75,15 @@ module Rack
|
|
|
65
75
|
|
|
66
76
|
## == The Environment
|
|
67
77
|
def check_env(env)
|
|
68
|
-
## The environment must be an instance of Hash that includes
|
|
78
|
+
## The environment must be an unfrozen instance of Hash that includes
|
|
69
79
|
## CGI-like headers. The application is free to modify the
|
|
70
80
|
## environment.
|
|
71
81
|
assert("env #{env.inspect} is not a Hash, but #{env.class}") {
|
|
72
82
|
env.kind_of? Hash
|
|
73
83
|
}
|
|
84
|
+
assert("env should not be frozen, but is") {
|
|
85
|
+
!env.frozen?
|
|
86
|
+
}
|
|
74
87
|
|
|
75
88
|
##
|
|
76
89
|
## The environment is required to include these variables
|
|
@@ -104,17 +117,19 @@ module Rack
|
|
|
104
117
|
## follows the <tt>?</tt>, if any. May be
|
|
105
118
|
## empty, but is always required!
|
|
106
119
|
|
|
107
|
-
## <tt>SERVER_NAME</tt
|
|
108
|
-
## When combined with <tt>SCRIPT_NAME</tt> and
|
|
120
|
+
## <tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
|
|
109
121
|
## <tt>PATH_INFO</tt>, these variables can be
|
|
110
122
|
## used to complete the URL. Note, however,
|
|
111
123
|
## that <tt>HTTP_HOST</tt>, if present,
|
|
112
124
|
## should be used in preference to
|
|
113
125
|
## <tt>SERVER_NAME</tt> for reconstructing
|
|
114
126
|
## the request URL.
|
|
115
|
-
## <tt>SERVER_NAME</tt>
|
|
116
|
-
##
|
|
117
|
-
|
|
127
|
+
## <tt>SERVER_NAME</tt> can never be an empty
|
|
128
|
+
## string, and so is always required.
|
|
129
|
+
|
|
130
|
+
## <tt>SERVER_PORT</tt>:: An optional +Integer+ which is the port the
|
|
131
|
+
## server is running on. Should be specified if
|
|
132
|
+
## the server is running on a non-standard port.
|
|
118
133
|
|
|
119
134
|
## <tt>HTTP_</tt> Variables:: Variables corresponding to the
|
|
120
135
|
## client-supplied HTTP request
|
|
@@ -198,6 +213,11 @@ module Rack
|
|
|
198
213
|
assert("session #{session.inspect} must respond to clear") {
|
|
199
214
|
session.respond_to?(:clear)
|
|
200
215
|
}
|
|
216
|
+
|
|
217
|
+
## to_hash (returning unfrozen Hash instance);
|
|
218
|
+
assert("session #{session.inspect} must respond to to_hash and return unfrozen Hash instance") {
|
|
219
|
+
session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
|
|
220
|
+
}
|
|
201
221
|
end
|
|
202
222
|
|
|
203
223
|
## <tt>rack.logger</tt>:: A common object interface for logging messages.
|
|
@@ -253,13 +273,28 @@ module Rack
|
|
|
253
273
|
## accepted specifications and must not be used otherwise.
|
|
254
274
|
##
|
|
255
275
|
|
|
256
|
-
%w[REQUEST_METHOD SERVER_NAME
|
|
257
|
-
QUERY_STRING
|
|
276
|
+
%w[REQUEST_METHOD SERVER_NAME QUERY_STRING
|
|
258
277
|
rack.version rack.input rack.errors
|
|
259
278
|
rack.multithread rack.multiprocess rack.run_once].each { |header|
|
|
260
279
|
assert("env missing required key #{header}") { env.include? header }
|
|
261
280
|
}
|
|
262
281
|
|
|
282
|
+
## The <tt>SERVER_PORT</tt> must be an Integer if set.
|
|
283
|
+
assert("env[SERVER_PORT] is not an Integer") do
|
|
284
|
+
server_port = env["SERVER_PORT"]
|
|
285
|
+
server_port.nil? || (Integer(server_port) rescue false)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
|
|
289
|
+
assert("#{env[SERVER_NAME]} must be a valid authority") do
|
|
290
|
+
URI.parse("http://#{env[SERVER_NAME]}/") rescue false
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
|
|
294
|
+
assert("#{env[HTTP_HOST]} must be a valid authority") do
|
|
295
|
+
URI.parse("http://#{env[HTTP_HOST]}/") rescue false
|
|
296
|
+
end
|
|
297
|
+
|
|
263
298
|
## The environment must not contain the keys
|
|
264
299
|
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
|
|
265
300
|
## (use the versions without <tt>HTTP_</tt>).
|
|
@@ -270,11 +305,17 @@ module Rack
|
|
|
270
305
|
}
|
|
271
306
|
|
|
272
307
|
## The CGI keys (named without a period) must have String values.
|
|
308
|
+
## If the string values for CGI keys contain non-ASCII characters,
|
|
309
|
+
## they should use ASCII-8BIT encoding.
|
|
273
310
|
env.each { |key, value|
|
|
274
311
|
next if key.include? "." # Skip extensions
|
|
275
312
|
assert("env variable #{key} has non-string value #{value.inspect}") {
|
|
276
313
|
value.kind_of? String
|
|
277
314
|
}
|
|
315
|
+
next if value.encoding == Encoding::ASCII_8BIT
|
|
316
|
+
assert("env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}") {
|
|
317
|
+
value.b !~ /[\x80-\xff]/n
|
|
318
|
+
}
|
|
278
319
|
}
|
|
279
320
|
|
|
280
321
|
## There are the following restrictions:
|
|
@@ -337,7 +378,7 @@ module Rack
|
|
|
337
378
|
## When applicable, its external encoding must be "ASCII-8BIT" and it
|
|
338
379
|
## must be opened in binary mode, for Ruby 1.9 compatibility.
|
|
339
380
|
assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
|
|
340
|
-
input.external_encoding
|
|
381
|
+
input.external_encoding == Encoding::ASCII_8BIT
|
|
341
382
|
} if input.respond_to?(:external_encoding)
|
|
342
383
|
assert("rack.input #{input} is not opened in binary mode") {
|
|
343
384
|
input.binmode?
|
|
@@ -569,7 +610,7 @@ module Rack
|
|
|
569
610
|
|
|
570
611
|
# this check uses headers like a hash, but the spec only requires
|
|
571
612
|
# headers respond to #each
|
|
572
|
-
headers = Rack::Utils::HeaderHash
|
|
613
|
+
headers = Rack::Utils::HeaderHash[headers]
|
|
573
614
|
|
|
574
615
|
## In order to do this, an application may set the special header
|
|
575
616
|
## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
|
|
@@ -593,7 +634,7 @@ module Rack
|
|
|
593
634
|
headers[RACK_HIJACK].respond_to? :call
|
|
594
635
|
}
|
|
595
636
|
original_hijack = headers[RACK_HIJACK]
|
|
596
|
-
|
|
637
|
+
proc do |io|
|
|
597
638
|
original_hijack.call HijackWrapper.new(io)
|
|
598
639
|
end
|
|
599
640
|
else
|
|
@@ -603,6 +644,8 @@ module Rack
|
|
|
603
644
|
assert('rack.hijack header must not be present if server does not support hijacking') {
|
|
604
645
|
headers[RACK_HIJACK].nil?
|
|
605
646
|
}
|
|
647
|
+
|
|
648
|
+
nil
|
|
606
649
|
end
|
|
607
650
|
end
|
|
608
651
|
## ==== Conventions
|