rack 1.0.1 → 1.1.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 (82) hide show
  1. data/COPYING +1 -1
  2. data/KNOWN-ISSUES +3 -0
  3. data/RDOX +0 -428
  4. data/README +61 -26
  5. data/SPEC +8 -1
  6. data/bin/rackup +2 -174
  7. data/lib/rack.rb +10 -8
  8. data/lib/rack/builder.rb +17 -0
  9. data/lib/rack/cascade.rb +17 -12
  10. data/lib/rack/chunked.rb +2 -2
  11. data/lib/rack/commonlogger.rb +31 -43
  12. data/lib/rack/config.rb +15 -0
  13. data/lib/rack/content_type.rb +1 -1
  14. data/lib/rack/directory.rb +6 -2
  15. data/lib/rack/etag.rb +23 -0
  16. data/lib/rack/file.rb +4 -2
  17. data/lib/rack/handler.rb +19 -0
  18. data/lib/rack/handler/cgi.rb +1 -1
  19. data/lib/rack/handler/fastcgi.rb +2 -3
  20. data/lib/rack/handler/lsws.rb +4 -1
  21. data/lib/rack/handler/mongrel.rb +8 -5
  22. data/lib/rack/handler/scgi.rb +4 -4
  23. data/lib/rack/handler/webrick.rb +2 -4
  24. data/lib/rack/lint.rb +44 -15
  25. data/lib/rack/logger.rb +20 -0
  26. data/lib/rack/mime.rb +3 -1
  27. data/lib/rack/mock.rb +30 -4
  28. data/lib/rack/nulllogger.rb +18 -0
  29. data/lib/rack/reloader.rb +4 -1
  30. data/lib/rack/request.rb +40 -15
  31. data/lib/rack/response.rb +5 -39
  32. data/lib/rack/runtime.rb +27 -0
  33. data/lib/rack/sendfile.rb +142 -0
  34. data/lib/rack/server.rb +212 -0
  35. data/lib/rack/session/abstract/id.rb +3 -5
  36. data/lib/rack/session/cookie.rb +3 -4
  37. data/lib/rack/session/memcache.rb +53 -43
  38. data/lib/rack/session/pool.rb +1 -1
  39. data/lib/rack/urlmap.rb +9 -8
  40. data/lib/rack/utils.rb +230 -11
  41. data/rack.gemspec +33 -49
  42. data/test/spec_rack_cascade.rb +3 -5
  43. data/test/spec_rack_cgi.rb +3 -3
  44. data/test/spec_rack_commonlogger.rb +39 -10
  45. data/test/spec_rack_config.rb +24 -0
  46. data/test/spec_rack_directory.rb +1 -1
  47. data/test/spec_rack_etag.rb +17 -0
  48. data/test/spec_rack_fastcgi.rb +2 -2
  49. data/test/spec_rack_file.rb +1 -1
  50. data/test/spec_rack_lint.rb +26 -19
  51. data/test/spec_rack_logger.rb +21 -0
  52. data/test/spec_rack_mock.rb +87 -1
  53. data/test/spec_rack_mongrel.rb +4 -4
  54. data/test/spec_rack_nulllogger.rb +13 -0
  55. data/test/spec_rack_request.rb +47 -6
  56. data/test/spec_rack_response.rb +3 -0
  57. data/test/spec_rack_runtime.rb +35 -0
  58. data/test/spec_rack_sendfile.rb +86 -0
  59. data/test/spec_rack_session_cookie.rb +1 -10
  60. data/test/spec_rack_session_memcache.rb +53 -20
  61. data/test/spec_rack_urlmap.rb +30 -0
  62. data/test/spec_rack_utils.rb +171 -6
  63. data/test/spec_rack_webrick.rb +4 -4
  64. data/test/spec_rackup.rb +154 -0
  65. metadata +37 -79
  66. data/Rakefile +0 -164
  67. data/lib/rack/auth/openid.rb +0 -480
  68. data/test/cgi/lighttpd.conf +0 -20
  69. data/test/cgi/test +0 -9
  70. data/test/cgi/test.fcgi +0 -8
  71. data/test/cgi/test.ru +0 -7
  72. data/test/multipart/binary +0 -0
  73. data/test/multipart/empty +0 -10
  74. data/test/multipart/ie +0 -6
  75. data/test/multipart/nested +0 -10
  76. data/test/multipart/none +0 -9
  77. data/test/multipart/semicolon +0 -6
  78. data/test/multipart/text +0 -10
  79. data/test/spec_rack_auth_openid.rb +0 -84
  80. data/test/testrequest.rb +0 -57
  81. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  82. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -0,0 +1,15 @@
1
+ module Rack
2
+ # Rack::Config modifies the environment using the block given during
3
+ # initialization.
4
+ class Config
5
+ def initialize(app, &block)
6
+ @app = app
7
+ @block = block
8
+ end
9
+
10
+ def call(env)
11
+ @block.call(env)
12
+ @app.call(env)
13
+ end
14
+ end
15
+ end
@@ -17,7 +17,7 @@ module Rack
17
17
  status, headers, body = @app.call(env)
18
18
  headers = Utils::HeaderHash.new(headers)
19
19
  headers['Content-Type'] ||= @content_type
20
- [status, headers.to_hash, body]
20
+ [status, headers, body]
21
21
  end
22
22
  end
23
23
  end
@@ -71,7 +71,9 @@ table { width:100%%; }
71
71
 
72
72
  body = "Forbidden\n"
73
73
  size = Rack::Utils.bytesize(body)
74
- return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]]
74
+ return [403, {"Content-Type" => "text/plain",
75
+ "Content-Length" => size.to_s,
76
+ "X-Cascade" => "pass"}, [body]]
75
77
  end
76
78
 
77
79
  def list_directory
@@ -123,7 +125,9 @@ table { width:100%%; }
123
125
  def entity_not_found
124
126
  body = "Entity not found: #{@path_info}\n"
125
127
  size = Rack::Utils.bytesize(body)
126
- return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]]
128
+ return [404, {"Content-Type" => "text/plain",
129
+ "Content-Length" => size.to_s,
130
+ "X-Cascade" => "pass"}, [body]]
127
131
  end
128
132
 
129
133
  def each
@@ -0,0 +1,23 @@
1
+ require 'digest/md5'
2
+
3
+ module Rack
4
+ # Automatically sets the ETag header on all String bodies
5
+ class ETag
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ status, headers, body = @app.call(env)
12
+
13
+ if !headers.has_key?('ETag')
14
+ parts = []
15
+ body.each { |part| parts << part.to_s }
16
+ headers['ETag'] = %("#{Digest::MD5.hexdigest(parts.join(""))}")
17
+ [status, headers, parts]
18
+ else
19
+ [status, headers, body]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -45,7 +45,8 @@ module Rack
45
45
  def forbidden
46
46
  body = "Forbidden\n"
47
47
  [403, {"Content-Type" => "text/plain",
48
- "Content-Length" => body.size.to_s},
48
+ "Content-Length" => body.size.to_s,
49
+ "X-Cascade" => "pass"},
49
50
  [body]]
50
51
  end
51
52
 
@@ -73,7 +74,8 @@ module Rack
73
74
  def not_found
74
75
  body = "File not found: #{@path_info}\n"
75
76
  [404, {"Content-Type" => "text/plain",
76
- "Content-Length" => body.size.to_s},
77
+ "Content-Length" => body.size.to_s,
78
+ "X-Cascade" => "pass"},
77
79
  [body]]
78
80
  end
79
81
 
@@ -22,6 +22,25 @@ module Rack
22
22
  end
23
23
  end
24
24
 
25
+ def self.default(options = {})
26
+ # Guess.
27
+ if ENV.include?("PHP_FCGI_CHILDREN")
28
+ # We already speak FastCGI
29
+ options.delete :File
30
+ options.delete :Port
31
+
32
+ Rack::Handler::FastCGI
33
+ elsif ENV.include?("REQUEST_METHOD")
34
+ Rack::Handler::CGI
35
+ else
36
+ begin
37
+ Rack::Handler::Mongrel
38
+ rescue LoadError => e
39
+ Rack::Handler::WEBrick
40
+ end
41
+ end
42
+ end
43
+
25
44
  # Transforms server-name constants to their canonical form as filenames,
26
45
  # then tries to require them but silences the LoadError if not found
27
46
  #
@@ -15,7 +15,7 @@ module Rack
15
15
 
16
16
  env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
17
17
 
18
- env.update({"rack.version" => [1,0],
18
+ env.update({"rack.version" => [1,1],
19
19
  "rack.input" => $stdin,
20
20
  "rack.errors" => $stderr,
21
21
 
@@ -33,10 +33,10 @@ module Rack
33
33
  env.delete "HTTP_CONTENT_LENGTH"
34
34
 
35
35
  env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
36
-
36
+
37
37
  rack_input = RewindableInput.new(request.in)
38
38
 
39
- env.update({"rack.version" => [1,0],
39
+ env.update({"rack.version" => [1,1],
40
40
  "rack.input" => rack_input,
41
41
  "rack.errors" => request.err,
42
42
 
@@ -50,7 +50,6 @@ module Rack
50
50
  env["QUERY_STRING"] ||= ""
51
51
  env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
52
52
  env["REQUEST_PATH"] ||= "/"
53
- env.delete "PATH_INFO" if env["PATH_INFO"] == ""
54
53
  env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
55
54
  env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
56
55
 
@@ -1,5 +1,6 @@
1
1
  require 'lsapi'
2
2
  require 'rack/content_length'
3
+ require 'rack/rewindable_input'
3
4
 
4
5
  module Rack
5
6
  module Handler
@@ -19,7 +20,7 @@ module Rack
19
20
  rack_input = RewindableInput.new($stdin.read.to_s)
20
21
 
21
22
  env.update(
22
- "rack.version" => [1,0],
23
+ "rack.version" => [1,1],
23
24
  "rack.input" => rack_input,
24
25
  "rack.errors" => $stderr,
25
26
  "rack.multithread" => false,
@@ -38,6 +39,8 @@ module Rack
38
39
  ensure
39
40
  body.close if body.respond_to? :close
40
41
  end
42
+ ensure
43
+ rack_input.close
41
44
  end
42
45
  def self.send_headers(status, headers)
43
46
  print "Status: #{status}\r\n"
@@ -7,10 +7,14 @@ module Rack
7
7
  module Handler
8
8
  class Mongrel < ::Mongrel::HttpHandler
9
9
  def self.run(app, options={})
10
- server = ::Mongrel::HttpServer.new(options[:Host] || '0.0.0.0',
11
- options[:Port] || 8080)
10
+ server = ::Mongrel::HttpServer.new(
11
+ options[:Host] || '0.0.0.0',
12
+ options[:Port] || 8080,
13
+ options[:num_processors] || 950,
14
+ options[:throttle] || 0,
15
+ options[:timeout] || 60)
12
16
  # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
13
- # Use is similar to #run, replacing the app argument with a hash of
17
+ # Use is similar to #run, replacing the app argument with a hash of
14
18
  # { path=>app, ... } or an instance of Rack::URLMap.
15
19
  if options[:map]
16
20
  if app.is_a? Hash
@@ -48,7 +52,7 @@ module Rack
48
52
  rack_input = request.body || StringIO.new('')
49
53
  rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
50
54
 
51
- env.update({"rack.version" => [1,0],
55
+ env.update({"rack.version" => [1,1],
52
56
  "rack.input" => rack_input,
53
57
  "rack.errors" => $stderr,
54
58
 
@@ -59,7 +63,6 @@ module Rack
59
63
  "rack.url_scheme" => "http",
60
64
  })
61
65
  env["QUERY_STRING"] ||= ""
62
- env.delete "PATH_INFO" if env["PATH_INFO"] == ""
63
66
 
64
67
  status, headers, body = @app.call(env)
65
68
 
@@ -7,14 +7,14 @@ module Rack
7
7
  module Handler
8
8
  class SCGI < ::SCGI::Processor
9
9
  attr_accessor :app
10
-
10
+
11
11
  def self.run(app, options=nil)
12
12
  new(options.merge(:app=>app,
13
13
  :host=>options[:Host],
14
14
  :port=>options[:Port],
15
15
  :socket=>options[:Socket])).listen
16
16
  end
17
-
17
+
18
18
  def initialize(settings = {})
19
19
  @app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app]))
20
20
  @log = Object.new
@@ -22,7 +22,7 @@ module Rack
22
22
  def @log.error(*args); end
23
23
  super(settings)
24
24
  end
25
-
25
+
26
26
  def process_request(request, input_body, socket)
27
27
  env = {}.replace(request)
28
28
  env.delete "HTTP_CONTENT_TYPE"
@@ -36,7 +36,7 @@ module Rack
36
36
  rack_input = StringIO.new(input_body)
37
37
  rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
38
38
 
39
- env.update({"rack.version" => [1,0],
39
+ env.update({"rack.version" => [1,1],
40
40
  "rack.input" => rack_input,
41
41
  "rack.errors" => $stderr,
42
42
  "rack.multithread" => true,
@@ -26,7 +26,7 @@ module Rack
26
26
  rack_input = StringIO.new(req.body.to_s)
27
27
  rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
28
28
 
29
- env.update({"rack.version" => [1,0],
29
+ env.update({"rack.version" => [1,1],
30
30
  "rack.input" => rack_input,
31
31
  "rack.errors" => $stderr,
32
32
 
@@ -40,9 +40,7 @@ module Rack
40
40
  env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
41
41
  env["QUERY_STRING"] ||= ""
42
42
  env["REQUEST_PATH"] ||= "/"
43
- if env["PATH_INFO"] == ""
44
- env.delete "PATH_INFO"
45
- else
43
+ unless env["PATH_INFO"] == ""
46
44
  path, n = req.request_uri.path, env["SCRIPT_NAME"].length
47
45
  env["PATH_INFO"] = path[n, path.length-n]
48
46
  end
@@ -61,7 +61,7 @@ module Rack
61
61
  ## subclassing allowed) that includes CGI-like headers.
62
62
  ## The application is free to modify the environment.
63
63
  assert("env #{env.inspect} is not a Hash, but #{env.class}") {
64
- env.instance_of? Hash
64
+ env.kind_of? Hash
65
65
  }
66
66
 
67
67
  ##
@@ -111,7 +111,7 @@ module Rack
111
111
  ## In addition to this, the Rack environment must include these
112
112
  ## Rack-specific variables:
113
113
 
114
- ## <tt>rack.version</tt>:: The Array [1,0], representing this version of Rack.
114
+ ## <tt>rack.version</tt>:: The Array [1,1], representing this version of Rack.
115
115
  ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
116
116
  ## <tt>rack.input</tt>:: See below, the input stream.
117
117
  ## <tt>rack.errors</tt>:: See below, the error stream.
@@ -148,6 +148,35 @@ module Rack
148
148
  }
149
149
  end
150
150
 
151
+ ## <tt>rack.logger</tt>:: A common object interface for logging messages.
152
+ ## The object must implement:
153
+ if logger = env['rack.logger']
154
+ ## info(message, &block)
155
+ assert("logger #{logger.inspect} must respond to info") {
156
+ logger.respond_to?(:info)
157
+ }
158
+
159
+ ## debug(message, &block)
160
+ assert("logger #{logger.inspect} must respond to debug") {
161
+ logger.respond_to?(:debug)
162
+ }
163
+
164
+ ## warn(message, &block)
165
+ assert("logger #{logger.inspect} must respond to warn") {
166
+ logger.respond_to?(:warn)
167
+ }
168
+
169
+ ## error(message, &block)
170
+ assert("logger #{logger.inspect} must respond to error") {
171
+ logger.respond_to?(:error)
172
+ }
173
+
174
+ ## fatal(message, &block)
175
+ assert("logger #{logger.inspect} must respond to fatal") {
176
+ logger.respond_to?(:fatal)
177
+ }
178
+ end
179
+
151
180
  ## The server or the application can store their own data in the
152
181
  ## environment, too. The keys must contain at least one dot,
153
182
  ## and should be prefixed uniquely. The prefix <tt>rack.</tt>
@@ -175,7 +204,7 @@ module Rack
175
204
  env.each { |key, value|
176
205
  next if key.include? "." # Skip extensions
177
206
  assert("env variable #{key} has non-string value #{value.inspect}") {
178
- value.instance_of? String
207
+ value.kind_of? String
179
208
  }
180
209
  }
181
210
 
@@ -184,7 +213,7 @@ module Rack
184
213
 
185
214
  ## * <tt>rack.version</tt> must be an array of Integers.
186
215
  assert("rack.version must be an Array, was #{env["rack.version"].class}") {
187
- env["rack.version"].instance_of? Array
216
+ env["rack.version"].kind_of? Array
188
217
  }
189
218
  ## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
190
219
  assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") {
@@ -243,7 +272,7 @@ module Rack
243
272
  assert("rack.input #{input} is not opened in binary mode") {
244
273
  input.binmode?
245
274
  } if input.respond_to?(:binmode?)
246
-
275
+
247
276
  ## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
248
277
  [:gets, :each, :read, :rewind].each { |method|
249
278
  assert("rack.input #{input} does not respond to ##{method}") {
@@ -269,7 +298,7 @@ module Rack
269
298
  assert("rack.input#gets called with arguments") { args.size == 0 }
270
299
  v = @input.gets
271
300
  assert("rack.input#gets didn't return a String") {
272
- v.nil? or v.instance_of? String
301
+ v.nil? or v.kind_of? String
273
302
  }
274
303
  v
275
304
  end
@@ -300,18 +329,18 @@ module Rack
300
329
  args[1].kind_of?(String)
301
330
  }
302
331
  end
303
-
332
+
304
333
  v = @input.read(*args)
305
-
334
+
306
335
  assert("rack.input#read didn't return nil or a String") {
307
- v.nil? or v.instance_of? String
336
+ v.nil? or v.kind_of? String
308
337
  }
309
338
  if args[0].nil?
310
339
  assert("rack.input#read(nil) returned nil on EOF") {
311
340
  !v.nil?
312
341
  }
313
342
  end
314
-
343
+
315
344
  v
316
345
  end
317
346
 
@@ -320,12 +349,12 @@ module Rack
320
349
  assert("rack.input#each called with arguments") { args.size == 0 }
321
350
  @input.each { |line|
322
351
  assert("rack.input#each didn't yield a String") {
323
- line.instance_of? String
352
+ line.kind_of? String
324
353
  }
325
354
  yield line
326
355
  }
327
356
  end
328
-
357
+
329
358
  ## * +rewind+ must be called without arguments. It rewinds the input
330
359
  ## stream back to the beginning. It must not raise Errno::ESPIPE:
331
360
  ## that is, it may not be a pipe or a socket. Therefore, handler
@@ -373,7 +402,7 @@ module Rack
373
402
 
374
403
  ## * +write+ must be called with a single argument that is a String.
375
404
  def write(str)
376
- assert("rack.errors#write not called with a String") { str.instance_of? String }
405
+ assert("rack.errors#write not called with a String") { str.kind_of? String }
377
406
  @error.write str
378
407
  end
379
408
 
@@ -407,7 +436,7 @@ module Rack
407
436
  header.each { |key, value|
408
437
  ## The header keys must be Strings.
409
438
  assert("header key must be a string, was #{key.class}") {
410
- key.instance_of? String
439
+ key.kind_of? String
411
440
  }
412
441
  ## The header must not contain a +Status+ key,
413
442
  assert("header must not contain Status") { key.downcase != "status" }
@@ -499,7 +528,7 @@ module Rack
499
528
  @body.each { |part|
500
529
  ## and must only yield String values.
501
530
  assert("Body yielded non-string value #{part.inspect}") {
502
- part.instance_of? String
531
+ part.kind_of? String
503
532
  }
504
533
  yield part
505
534
  }
@@ -0,0 +1,20 @@
1
+ require 'logger'
2
+
3
+ module Rack
4
+ # Sets up rack.logger to write to rack.errors stream
5
+ class Logger
6
+ def initialize(app, level = ::Logger::INFO)
7
+ @app, @level = app, level
8
+ end
9
+
10
+ def call(env)
11
+ logger = ::Logger.new(env['rack.errors'])
12
+ logger.level = @level
13
+
14
+ env['rack.logger'] = logger
15
+ @app.call(env)
16
+ ensure
17
+ logger.close
18
+ end
19
+ end
20
+ end
@@ -14,7 +14,7 @@ module Rack
14
14
  # Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
15
15
 
16
16
  def mime_type(ext, fallback='application/octet-stream')
17
- MIME_TYPES.fetch(ext, fallback)
17
+ MIME_TYPES.fetch(ext.to_s.downcase, fallback)
18
18
  end
19
19
  module_function :mime_type
20
20
 
@@ -105,6 +105,7 @@ module Rack
105
105
  ".m3u" => "audio/x-mpegurl",
106
106
  ".m4v" => "video/mp4",
107
107
  ".man" => "text/troff",
108
+ ".manifest"=> "text/cache-manifest",
108
109
  ".mathml" => "application/mathml+xml",
109
110
  ".mbox" => "application/mbox",
110
111
  ".mdoc" => "text/troff",
@@ -126,6 +127,7 @@ module Rack
126
127
  ".ods" => "application/vnd.oasis.opendocument.spreadsheet",
127
128
  ".odt" => "application/vnd.oasis.opendocument.text",
128
129
  ".ogg" => "application/ogg",
130
+ ".ogv" => "video/ogg",
129
131
  ".p" => "text/x-pascal",
130
132
  ".pas" => "text/x-pascal",
131
133
  ".pbm" => "image/x-portable-bitmap",