Skip to content
This repository
Newer
Older
100644 568 lines (490 sloc) 21.654 kb
d6fa6afd » josh
2009-01-14 Explicitly require request, response, and utils
1 require 'rack/utils'
2
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
3 module Rack
376fa1e3 » chneukirchen
2007-03-01 Add RDocs
4 # Rack::Lint validates your application and the requests and
5 # responses according to the Rack spec.
230d62c7 » chneukirchen
2007-05-16 Fix trailing whitespace. Sigh.
6
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
7 class Lint
8 def initialize(app)
9 @app = app
c2cffe0b » rtomayko
2010-04-29 avoid uninitialized ivar warning
10 @content_length = nil
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
11 end
12
376fa1e3 » chneukirchen
2007-03-01 Add RDocs
13 # :stopdoc:
14
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
15 class LintError < RuntimeError; end
16 module Assertion
17 def assert(message, &block)
18 unless block.call
19 raise LintError, message
20 end
21 end
22 end
23 include Assertion
24
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
25 ## This specification aims to formalize the Rack protocol. You
26 ## can (and should) use Rack::Lint to enforce it.
27 ##
28 ## When you develop middleware, be sure to add a Lint before and
29 ## after to catch all mistakes.
30
31 ## = Rack applications
32
f5dfd342 » raggi
2011-05-22 Grammatical corrections (thanks digitalally)
33 ## A Rack application is a Ruby object (not a class) that
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
34 ## responds to +call+.
61ce750c » josh
2008-12-23 Delegate Lint::InputWrapper#rewind to underlying IO object
35 def call(env=nil)
36 dup._call(env)
f58c3a4c » chneukirchen
2008-08-01 Make Rack::Lint threadsafe
37 end
38
39 def _call(env)
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
40 ## It takes exactly one argument, the *environment*
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
41 assert("No env given") { env }
42 check_env env
43
44 env['rack.input'] = InputWrapper.new(env['rack.input'])
45 env['rack.errors'] = ErrorWrapper.new(env['rack.errors'])
46
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
47 ## and returns an Array of exactly three values:
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
48 status, headers, @body = @app.call(env)
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
49 ## The *status*,
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
50 check_status status
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
51 ## the *headers*,
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
52 check_headers headers
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
53 ## and the *body*.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
54 check_content_type status, headers
5d349d06 » jeremy
2009-05-13 Eliminate assumption that body.each may be called more than once
55 check_content_length status, headers
56 @head_request = env["REQUEST_METHOD"] == "HEAD"
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
57 [status, headers, self]
58 end
230d62c7 » chneukirchen
2007-05-16 Fix trailing whitespace. Sigh.
59
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
60 ## == The Environment
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
61 def check_env(env)
799fa0d6 » chneukirchen
2010-01-10 Fix SPEC documentation to say subclasses are allowed
62 ## The environment must be an instance of Hash that includes
63 ## CGI-like headers. The application is free to modify the
64 ## environment.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
65 assert("env #{env.inspect} is not a Hash, but #{env.class}") {
de668df0 » raggi
2009-10-05 Relax Lint slightly to allow subclasses of the required types
66 env.kind_of? Hash
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
67 }
116f0ef5 » chneukirchen
2007-02-17 Remove trailing whitespace *sigh*
68
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
69 ##
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
70 ## The environment is required to include these variables
71 ## (adopted from PEP333), except when they'd be empty, but see
72 ## below.
73
74 ## <tt>REQUEST_METHOD</tt>:: The HTTP request method, such as
75 ## "GET" or "POST". This cannot ever
76 ## be an empty string, and so is
77 ## always required.
78
79 ## <tt>SCRIPT_NAME</tt>:: The initial portion of the request
80 ## URL's "path" that corresponds to the
81 ## application object, so that the
82 ## application knows its virtual
83 ## "location". This may be an empty
84 ## string, if the application corresponds
85 ## to the "root" of the server.
86
87 ## <tt>PATH_INFO</tt>:: The remainder of the request URL's
88 ## "path", designating the virtual
89 ## "location" of the request's target
90 ## within the application. This may be an
91 ## empty string, if the request URL targets
92 ## the application root and does not have a
06b37fcd » chneukirchen
2009-03-25 SPEC: Clarify percent-encoding of PATH_INFO
93 ## trailing slash. This value may be
94 ## percent-encoded when I originating from
95 ## a URL.
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
96
97 ## <tt>QUERY_STRING</tt>:: The portion of the request URL that
98 ## follows the <tt>?</tt>, if any. May be
99 ## empty, but is always required!
100
101 ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>:: When combined with <tt>SCRIPT_NAME</tt> and <tt>PATH_INFO</tt>, these variables can be used to complete the URL. Note, however, that <tt>HTTP_HOST</tt>, if present, should be used in preference to <tt>SERVER_NAME</tt> for reconstructing the request URL. <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt> can never be empty strings, and so are always required.
102
103 ## <tt>HTTP_</tt> Variables:: Variables corresponding to the
104 ## client-supplied HTTP request
105 ## headers (i.e., variables whose
106 ## names begin with <tt>HTTP_</tt>). The
107 ## presence or absence of these
108 ## variables should correspond with
109 ## the presence or absence of the
110 ## appropriate HTTP header in the
9c0c41de » raggi
2012-03-17 Add link to RFC3875 in Lint and SPEC. Closes #352.
111 ## request. See <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
112 ## RFC3875 section 4.1.18</a> for specific behavior.
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
113
114 ## In addition to this, the Rack environment must include these
115 ## Rack-specific variables:
116
2a8c937a » josh
2009-12-26 Bump version and release to 1.1
117 ## <tt>rack.version</tt>:: The Array [1,1], representing this version of Rack.
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
118 ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
119 ## <tt>rack.input</tt>:: See below, the input stream.
120 ## <tt>rack.errors</tt>:: See below, the error stream.
121 ## <tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
122 ## <tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
123 ## <tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
e678c6ad » josh
2009-04-18 Add 'rack.session' specification
124 ##
125
126 ## Additional environment specifications have approved to
127 ## standardized middleware APIs. None of these are required to
128 ## be implemented by the server.
129
56112181 » FooBarWidget
2009-04-25 Fix styling the Rack specification.
130 ## <tt>rack.session</tt>:: A hash like interface for storing request session data.
e678c6ad » josh
2009-04-18 Add 'rack.session' specification
131 ## The store must implement:
132 if session = env['rack.session']
56112181 » FooBarWidget
2009-04-25 Fix styling the Rack specification.
133 ## store(key, value) (aliased as []=);
e678c6ad » josh
2009-04-18 Add 'rack.session' specification
134 assert("session #{session.inspect} must respond to store and []=") {
135 session.respond_to?(:store) && session.respond_to?(:[]=)
136 }
137
56112181 » FooBarWidget
2009-04-25 Fix styling the Rack specification.
138 ## fetch(key, default = nil) (aliased as []);
e678c6ad » josh
2009-04-18 Add 'rack.session' specification
139 assert("session #{session.inspect} must respond to fetch and []") {
140 session.respond_to?(:fetch) && session.respond_to?(:[])
141 }
142
56112181 » FooBarWidget
2009-04-25 Fix styling the Rack specification.
143 ## delete(key);
e678c6ad » josh
2009-04-18 Add 'rack.session' specification
144 assert("session #{session.inspect} must respond to delete") {
145 session.respond_to?(:delete)
146 }
147
56112181 » FooBarWidget
2009-04-25 Fix styling the Rack specification.
148 ## clear;
e678c6ad » josh
2009-04-18 Add 'rack.session' specification
149 assert("session #{session.inspect} must respond to clear") {
150 session.respond_to?(:clear)
151 }
152 end
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
153
df22480a » josh
2009-12-11 rack.logger specification
154 ## <tt>rack.logger</tt>:: A common object interface for logging messages.
155 ## The object must implement:
156 if logger = env['rack.logger']
157 ## info(message, &block)
158 assert("logger #{logger.inspect} must respond to info") {
159 logger.respond_to?(:info)
160 }
161
162 ## debug(message, &block)
163 assert("logger #{logger.inspect} must respond to debug") {
164 logger.respond_to?(:debug)
165 }
166
167 ## warn(message, &block)
168 assert("logger #{logger.inspect} must respond to warn") {
169 logger.respond_to?(:warn)
170 }
171
172 ## error(message, &block)
173 assert("logger #{logger.inspect} must respond to error") {
174 logger.respond_to?(:error)
175 }
176
177 ## fatal(message, &block)
178 assert("logger #{logger.inspect} must respond to fatal") {
179 logger.respond_to?(:fatal)
180 }
181 end
182
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
183 ## The server or the application can store their own data in the
184 ## environment, too. The keys must contain at least one dot,
185 ## and should be prefixed uniquely. The prefix <tt>rack.</tt>
e678c6ad » josh
2009-04-18 Add 'rack.session' specification
186 ## is reserved for use with the Rack core distribution and other
187 ## accepted specifications and must not be used otherwise.
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
188 ##
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
189
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
190 %w[REQUEST_METHOD SERVER_NAME SERVER_PORT
191 QUERY_STRING
192 rack.version rack.input rack.errors
193 rack.multithread rack.multiprocess rack.run_once].each { |header|
194 assert("env missing required key #{header}") { env.include? header }
195 }
196
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
197 ## The environment must not contain the keys
198 ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
199 ## (use the versions without <tt>HTTP_</tt>).
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
200 %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
201 assert("env contains #{header}, must use #{header[5,-1]}") {
202 not env.include? header
203 }
204 }
205
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
206 ## The CGI keys (named without a period) must have String values.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
207 env.each { |key, value|
208 next if key.include? "." # Skip extensions
209 assert("env variable #{key} has non-string value #{value.inspect}") {
de668df0 » raggi
2009-10-05 Relax Lint slightly to allow subclasses of the required types
210 value.kind_of? String
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
211 }
212 }
213
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
214 ##
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
215 ## There are the following restrictions:
216
217 ## * <tt>rack.version</tt> must be an array of Integers.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
218 assert("rack.version must be an Array, was #{env["rack.version"].class}") {
de668df0 » raggi
2009-10-05 Relax Lint slightly to allow subclasses of the required types
219 env["rack.version"].kind_of? Array
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
220 }
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
221 ## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
222 assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") {
223 %w[http https].include? env["rack.url_scheme"]
224 }
225
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
226 ## * There must be a valid input stream in <tt>rack.input</tt>.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
227 check_input env["rack.input"]
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
228 ## * There must be a valid error stream in <tt>rack.errors</tt>.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
229 check_error env["rack.errors"]
230
9bf5c136 » chneukirchen
2008-08-19 REQUEST_METHOD only must be a valid token
231 ## * The <tt>REQUEST_METHOD</tt> must be a valid token.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
232 assert("REQUEST_METHOD unknown: #{env["REQUEST_METHOD"]}") {
9bf5c136 » chneukirchen
2008-08-19 REQUEST_METHOD only must be a valid token
233 env["REQUEST_METHOD"] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
234 }
235
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
236 ## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
237 assert("SCRIPT_NAME must start with /") {
fe9ddfbd » chneukirchen
2007-02-16 Fix lint to allow empty SCRIPT_NAME and PATH_INFO
238 !env.include?("SCRIPT_NAME") ||
239 env["SCRIPT_NAME"] == "" ||
240 env["SCRIPT_NAME"] =~ /\A\//
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
241 }
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
242 ## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
243 assert("PATH_INFO must start with /") {
fe9ddfbd » chneukirchen
2007-02-16 Fix lint to allow empty SCRIPT_NAME and PATH_INFO
244 !env.include?("PATH_INFO") ||
245 env["PATH_INFO"] == "" ||
246 env["PATH_INFO"] =~ /\A\//
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
247 }
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
248 ## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
249 assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
250 !env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/
251 }
252
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
253 ## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
254 ## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
255 ## <tt>SCRIPT_NAME</tt> is empty.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
256 assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
257 env["SCRIPT_NAME"] || env["PATH_INFO"]
258 }
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
259 ## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
260 assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
261 env["SCRIPT_NAME"] != "/"
262 }
263 end
264
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
265 ## === The Input Stream
6dad8ac3 » FooBarWidget
2009-04-14 Describe some things in the specification more clearly, and add some …
266 ##
267 ## The input stream is an IO-like object which contains the raw HTTP
fd1c1d2e » FooBarWidget
2009-06-17 Document more clearly that rack.input must be opened in binary mode, …
268 ## POST data.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
269 def check_input(input)
fd1c1d2e » FooBarWidget
2009-06-17 Document more clearly that rack.input must be opened in binary mode, …
270 ## When applicable, its external encoding must be "ASCII-8BIT" and it
271 ## must be opened in binary mode, for Ruby 1.9 compatibility.
272 assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
273 input.external_encoding.name == "ASCII-8BIT"
274 } if input.respond_to?(:external_encoding)
275 assert("rack.input #{input} is not opened in binary mode") {
276 input.binmode?
277 } if input.respond_to?(:binmode?)
df22480a » josh
2009-12-11 rack.logger specification
278
8ff87d4a » FooBarWidget
2009-04-14 Update Rack specification: require rack.input to be rewindable.
279 ## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
280 [:gets, :each, :read, :rewind].each { |method|
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
281 assert("rack.input #{input} does not respond to ##{method}") {
282 input.respond_to? method
283 }
284 }
285 end
286
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
287 class InputWrapper
288 include Assertion
289
290 def initialize(input)
291 @input = input
292 end
230d62c7 » chneukirchen
2007-05-16 Fix trailing whitespace. Sigh.
293
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
294 ## * +gets+ must be called without arguments and return a string,
295 ## or +nil+ on EOF.
296 def gets(*args)
297 assert("rack.input#gets called with arguments") { args.size == 0 }
298 v = @input.gets
299 assert("rack.input#gets didn't return a String") {
de668df0 » raggi
2009-10-05 Relax Lint slightly to allow subclasses of the required types
300 v.nil? or v.kind_of? String
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
301 }
302 v
303 end
304
385f6216 » FooBarWidget
2009-04-14 Specify rack.input#read to behave the same like IO#read.
305 ## * +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
f5dfd342 » raggi
2011-05-22 Grammatical corrections (thanks digitalally)
306 ## If given, +length+ must be a non-negative Integer (>= 0) or +nil+, and +buffer+ must
385f6216 » FooBarWidget
2009-04-14 Specify rack.input#read to behave the same like IO#read.
307 ## be a String and may not be nil. If +length+ is given and not nil, then this method
308 ## reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
309 ## then this method reads all data until EOF.
310 ## When EOF is reached, this method returns nil if +length+ is given and not nil, or ""
311 ## if +length+ is not given or is nil.
312 ## If +buffer+ is given, then the read data will be placed into +buffer+ instead of a
313 ## newly created String object.
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
314 def read(*args)
758f9287 » chneukirchen
2007-02-28 Allow rack.input.read(integer), needed for safe multipart parsing
315 assert("rack.input#read called with too many arguments") {
385f6216 » FooBarWidget
2009-04-14 Specify rack.input#read to behave the same like IO#read.
316 args.size <= 2
758f9287 » chneukirchen
2007-02-28 Allow rack.input.read(integer), needed for safe multipart parsing
317 }
385f6216 » FooBarWidget
2009-04-14 Specify rack.input#read to behave the same like IO#read.
318 if args.size >= 1
319 assert("rack.input#read called with non-integer and non-nil length") {
320 args.first.kind_of?(Integer) || args.first.nil?
321 }
322 assert("rack.input#read called with a negative length") {
323 args.first.nil? || args.first >= 0
324 }
325 end
326 if args.size >= 2
327 assert("rack.input#read called with non-String buffer") {
328 args[1].kind_of?(String)
758f9287 » chneukirchen
2007-02-28 Allow rack.input.read(integer), needed for safe multipart parsing
329 }
330 end
df22480a » josh
2009-12-11 rack.logger specification
331
758f9287 » chneukirchen
2007-02-28 Allow rack.input.read(integer), needed for safe multipart parsing
332 v = @input.read(*args)
df22480a » josh
2009-12-11 rack.logger specification
333
385f6216 » FooBarWidget
2009-04-14 Specify rack.input#read to behave the same like IO#read.
334 assert("rack.input#read didn't return nil or a String") {
de668df0 » raggi
2009-10-05 Relax Lint slightly to allow subclasses of the required types
335 v.nil? or v.kind_of? String
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
336 }
385f6216 » FooBarWidget
2009-04-14 Specify rack.input#read to behave the same like IO#read.
337 if args[0].nil?
338 assert("rack.input#read(nil) returned nil on EOF") {
339 !v.nil?
340 }
341 end
df22480a » josh
2009-12-11 rack.logger specification
342
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
343 v
344 end
345
346 ## * +each+ must be called without arguments and only yield Strings.
347 def each(*args)
348 assert("rack.input#each called with arguments") { args.size == 0 }
349 @input.each { |line|
350 assert("rack.input#each didn't yield a String") {
de668df0 » raggi
2009-10-05 Relax Lint slightly to allow subclasses of the required types
351 line.kind_of? String
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
352 }
353 yield line
354 }
355 end
df22480a » josh
2009-12-11 rack.logger specification
356
8ff87d4a » FooBarWidget
2009-04-14 Update Rack specification: require rack.input to be rewindable.
357 ## * +rewind+ must be called without arguments. It rewinds the input
358 ## stream back to the beginning. It must not raise Errno::ESPIPE:
359 ## that is, it may not be a pipe or a socket. Therefore, handler
360 ## developers must buffer the input data into some rewindable object
361 ## if the underlying input stream is not rewindable.
362 def rewind(*args)
363 assert("rack.input#rewind called with arguments") { args.size == 0 }
364 assert("rack.input#rewind raised Errno::ESPIPE") {
365 begin
366 @input.rewind
367 true
368 rescue Errno::ESPIPE
369 false
370 end
371 }
372 end
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
373
374 ## * +close+ must never be called on the input stream.
375 def close(*args)
376 assert("rack.input#close must not be called") { false }
377 end
378 end
379
380 ## === The Error Stream
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
381 def check_error(error)
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
382 ## The error stream must respond to +puts+, +write+ and +flush+.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
383 [:puts, :write, :flush].each { |method|
384 assert("rack.error #{error} does not respond to ##{method}") {
385 error.respond_to? method
386 }
387 }
388 end
389
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
390 class ErrorWrapper
391 include Assertion
392
393 def initialize(error)
394 @error = error
395 end
396
397 ## * +puts+ must be called with a single argument that responds to +to_s+.
398 def puts(str)
399 @error.puts str
400 end
401
402 ## * +write+ must be called with a single argument that is a String.
403 def write(str)
de668df0 » raggi
2009-10-05 Relax Lint slightly to allow subclasses of the required types
404 assert("rack.errors#write not called with a String") { str.kind_of? String }
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
405 @error.write str
406 end
407
408 ## * +flush+ must be called without arguments and must be called
409 ## in order to make the error appear for sure.
410 def flush
411 @error.flush
412 end
413
414 ## * +close+ must never be called on the error stream.
415 def close(*args)
416 assert("rack.errors#close must not be called") { false }
417 end
418 end
419
420 ## == The Response
421
422 ## === The Status
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
423 def check_status(status)
6dad8ac3 » FooBarWidget
2009-04-14 Describe some things in the specification more clearly, and add some …
424 ## This is an HTTP status. When parsed as integer (+to_i+), it must be
425 ## greater than or equal to 100.
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
426 assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
427 end
428
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
429 ## === The Headers
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
430 def check_headers(header)
6dad8ac3 » FooBarWidget
2009-04-14 Describe some things in the specification more clearly, and add some …
431 ## The header must respond to +each+, and yield values of key and value.
6f500ddd » julik
2008-07-12 Make Lint show proper errors for headers
432 assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
433 header.respond_to? :each
434 }
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
435 header.each { |key, value|
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
436 ## The header keys must be Strings.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
437 assert("header key must be a string, was #{key.class}") {
de668df0 » raggi
2009-10-05 Relax Lint slightly to allow subclasses of the required types
438 key.kind_of? String
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
439 }
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
440 ## The header must not contain a +Status+ key,
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
441 assert("header must not contain Status") { key.downcase != "status" }
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
442 ## contain keys with <tt>:</tt> or newlines in their name,
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
443 assert("header names must not contain : or \\n") { key !~ /[:\n]/ }
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
444 ## contain keys names that end in <tt>-</tt> or <tt>_</tt>,
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
445 assert("header names must not end in - or _") { key !~ /[-_]\z/ }
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
446 ## but only contain keys that consist of
447 ## letters, digits, <tt>_</tt> or <tt>-</tt> and start with a letter.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
448 assert("invalid header name: #{key}") { key =~ /\A[a-zA-Z][a-zA-Z0-9_-]*\z/ }
732a4fa2 » rtomayko
2009-02-01 SPEC: header values must be Strings instead of responding to #each [#27]
449
450 ## The values of the header must be Strings,
451 assert("a header value must be a String, but the value of " +
452 "'#{key}' is a #{value.class}") { value.kind_of? String }
6dad8ac3 » FooBarWidget
2009-04-14 Describe some things in the specification more clearly, and add some …
453 ## consisting of lines (for multiple header values, e.g. multiple
454 ## <tt>Set-Cookie</tt> values) seperated by "\n".
732a4fa2 » rtomayko
2009-02-01 SPEC: header values must be Strings instead of responding to #each [#27]
455 value.split("\n").each { |item|
456 ## The lines must not contain characters below 037.
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
457 assert("invalid header value #{key}: #{item.inspect}") {
458 item !~ /[\000-\037]/
459 }
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
460 }
461 }
462 end
463
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
464 ## === The Content-Type
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
465 def check_content_type(status, headers)
466 headers.each { |key, value|
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
467 ## There must be a <tt>Content-Type</tt>, except when the
2c5b076a » dkubb
2011-08-15 Add 205 Reset Content to the list of statuses without a message body
468 ## +Status+ is 1xx, 204, 205 or 304, in which case there must be none
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
469 ## given.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
470 if key.downcase == "content-type"
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
471 assert("Content-Type header found in #{status} response, not allowed") {
77725c7d » Dan Kubb
2008-12-20 Moved STATUS_WITH_NO_ENTITY_BODY into Rack::Utils
472 not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
473 }
474 return
475 end
476 }
f0059a72 » chneukirchen
2007-02-22 Lint fix
477 assert("No Content-Type header found") {
77725c7d » Dan Kubb
2008-12-20 Moved STATUS_WITH_NO_ENTITY_BODY into Rack::Utils
478 Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
479 }
480 end
481
482 ## === The Content-Length
5d349d06 » jeremy
2009-05-13 Eliminate assumption that body.each may be called more than once
483 def check_content_length(status, headers)
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
484 headers.each { |key, value|
485 if key.downcase == 'content-length'
86ddc7a6 » rtomayko
2009-03-05 Rack::Lint no longer requires a Content-Length response header
486 ## There must not be a <tt>Content-Length</tt> header when the
2c5b076a » dkubb
2011-08-15 Add 205 Reset Content to the list of statuses without a message body
487 ## +Status+ is 1xx, 204, 205 or 304.
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
488 assert("Content-Length header found in #{status} response, not allowed") {
77725c7d » Dan Kubb
2008-12-20 Moved STATUS_WITH_NO_ENTITY_BODY into Rack::Utils
489 not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
490 }
5d349d06 » jeremy
2009-05-13 Eliminate assumption that body.each may be called more than once
491 @content_length = value
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
492 end
493 }
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
494 end
495
5d349d06 » jeremy
2009-05-13 Eliminate assumption that body.each may be called more than once
496 def verify_content_length(bytes)
497 if @head_request
498 assert("Response body was given for HEAD request, but should be empty") {
499 bytes == 0
500 }
501 elsif @content_length
502 assert("Content-Length header was #{@content_length}, but should be #{bytes}") {
503 @content_length == bytes.to_s
504 }
505 end
506 end
507
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
508 ## === The Body
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
509 def each
510 @closed = false
5d349d06 » jeremy
2009-05-13 Eliminate assumption that body.each may be called more than once
511 bytes = 0
512
6dad8ac3 » FooBarWidget
2009-04-14 Describe some things in the specification more clearly, and add some …
513 ## The Body must respond to +each+
5d349d06 » jeremy
2009-05-13 Eliminate assumption that body.each may be called more than once
514 assert("Response body must respond to each") do
515 @body.respond_to?(:each)
516 end
517
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
518 @body.each { |part|
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
519 ## and must only yield String values.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
520 assert("Body yielded non-string value #{part.inspect}") {
de668df0 » raggi
2009-10-05 Relax Lint slightly to allow subclasses of the required types
521 part.kind_of? String
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
522 }
5d349d06 » jeremy
2009-05-13 Eliminate assumption that body.each may be called more than once
523 bytes += Rack::Utils.bytesize(part)
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
524 yield part
525 }
5d349d06 » jeremy
2009-05-13 Eliminate assumption that body.each may be called more than once
526 verify_content_length(bytes)
527
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
528 ##
6dad8ac3 » FooBarWidget
2009-04-14 Describe some things in the specification more clearly, and add some …
529 ## The Body itself should not be an instance of String, as this will
95c7d81d » chneukirchen
2009-03-25 Document SPEC changes, officially deprecate String bodies
530 ## break in Ruby 1.9.
531 ##
6dad8ac3 » FooBarWidget
2009-04-14 Describe some things in the specification more clearly, and add some …
532 ## If the Body responds to +close+, it will be called after iteration.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
533 # XXX howto: assert("Body has not been closed") { @closed }
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
534
2a506e81 » rtomayko
2008-12-08 Add body.to_path to SPEC; implement in Rack::File
535
536 ##
6dad8ac3 » FooBarWidget
2009-04-14 Describe some things in the specification more clearly, and add some …
537 ## If the Body responds to +to_path+, it must return a String
2a506e81 » rtomayko
2008-12-08 Add body.to_path to SPEC; implement in Rack::File
538 ## identifying the location of a file whose contents are identical
e7b91232 » FooBarWidget
2009-04-25 Explain the usefulness of the to_path method of the body.
539 ## to that produced by calling +each+; this may be used by the
540 ## server as an alternative, possibly more efficient way to
541 ## transport the response.
2a506e81 » rtomayko
2008-12-08 Add body.to_path to SPEC; implement in Rack::File
542
543 if @body.respond_to?(:to_path)
544 assert("The file identified by body.to_path does not exist") {
545 ::File.exist? @body.to_path
546 }
547 end
548
b1818614 » Dan Kubb
2008-07-24 Updated Rake::Lint to ensure Content-Length header is present for non…
549 ##
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
550 ## The Body commonly is an Array of Strings, the application
551 ## instance itself, or a File-like object.
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
552 end
553
554 def close
555 @closed = true
556 @body.close if @body.respond_to?(:close)
557 end
376fa1e3 » chneukirchen
2007-03-01 Add RDocs
558
559 # :startdoc:
560
9eda64e7 » chneukirchen
2007-02-16 Add Rack::Lint
561 end
562 end
df0c8d01 » chneukirchen
2007-02-21 Add a first draft of the specification to Rack::Lint
563
564 ## == Thanks
565 ## Some parts of this specification are adopted from PEP333: Python
566 ## Web Server Gateway Interface
567 ## v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank
568 ## everyone involved in that effort.
Something went wrong with that request. Please try again.