rack 0.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 (54) hide show
  1. data/AUTHORS +3 -0
  2. data/COPYING +18 -0
  3. data/KNOWN-ISSUES +18 -0
  4. data/RDOX +144 -0
  5. data/README +154 -0
  6. data/Rakefile +174 -0
  7. data/SPEC +132 -0
  8. data/bin/rackup +148 -0
  9. data/contrib/rack_logo.svg +111 -0
  10. data/example/lobster.ru +4 -0
  11. data/lib/rack.rb +67 -0
  12. data/lib/rack/adapter/camping.rb +16 -0
  13. data/lib/rack/adapter/rails.rb +65 -0
  14. data/lib/rack/builder.rb +52 -0
  15. data/lib/rack/cascade.rb +26 -0
  16. data/lib/rack/commonlogger.rb +56 -0
  17. data/lib/rack/file.rb +108 -0
  18. data/lib/rack/handler/cgi.rb +57 -0
  19. data/lib/rack/handler/fastcgi.rb +81 -0
  20. data/lib/rack/handler/mongrel.rb +57 -0
  21. data/lib/rack/handler/webrick.rb +56 -0
  22. data/lib/rack/lint.rb +394 -0
  23. data/lib/rack/lobster.rb +65 -0
  24. data/lib/rack/mock.rb +183 -0
  25. data/lib/rack/recursive.rb +57 -0
  26. data/lib/rack/reloader.rb +64 -0
  27. data/lib/rack/request.rb +112 -0
  28. data/lib/rack/response.rb +114 -0
  29. data/lib/rack/showexceptions.rb +344 -0
  30. data/lib/rack/urlmap.rb +50 -0
  31. data/lib/rack/utils.rb +176 -0
  32. data/test/cgi/lighttpd.conf +20 -0
  33. data/test/cgi/test +9 -0
  34. data/test/cgi/test.fcgi +9 -0
  35. data/test/cgi/test.ru +7 -0
  36. data/test/spec_rack_camping.rb +44 -0
  37. data/test/spec_rack_cascade.rb +35 -0
  38. data/test/spec_rack_cgi.rb +82 -0
  39. data/test/spec_rack_commonlogger.rb +32 -0
  40. data/test/spec_rack_fastcgi.rb +82 -0
  41. data/test/spec_rack_file.rb +32 -0
  42. data/test/spec_rack_lint.rb +317 -0
  43. data/test/spec_rack_lobster.rb +45 -0
  44. data/test/spec_rack_mock.rb +150 -0
  45. data/test/spec_rack_mongrel.rb +87 -0
  46. data/test/spec_rack_recursive.rb +77 -0
  47. data/test/spec_rack_request.rb +219 -0
  48. data/test/spec_rack_response.rb +110 -0
  49. data/test/spec_rack_showexceptions.rb +21 -0
  50. data/test/spec_rack_urlmap.rb +140 -0
  51. data/test/spec_rack_utils.rb +57 -0
  52. data/test/spec_rack_webrick.rb +89 -0
  53. data/test/testrequest.rb +43 -0
  54. metadata +117 -0
@@ -0,0 +1,16 @@
1
+ module Rack
2
+ module Adapter
3
+ class Camping
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ env["PATH_INFO"] ||= ""
10
+ env["SCRIPT_NAME"] ||= ""
11
+ controller = @app.run(env['rack.input'], env)
12
+ [controller.status, controller.headers, controller.body]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,65 @@
1
+ unless defined? RAILS_ROOT
2
+ raise "Rails' environment has to be loaded before using Rack::Adapter::Rails"
3
+ end
4
+
5
+ require "dispatcher"
6
+
7
+ module Rack
8
+ module Adapter
9
+ # TODO: Extract this
10
+ class Rails < Cascade
11
+ def initialize
12
+ file = Rack::File.new(::File.join(RAILS_ROOT, "public"))
13
+ dispatcher = RailsDispatcher.new
14
+
15
+ super([file, dispatcher])
16
+ end
17
+ end
18
+
19
+ class RailsDispatcher
20
+ def call(env)
21
+ response = dispatch(CGIStub.new(env))
22
+ headers = response.headers
23
+ status = headers.delete("Status")
24
+
25
+ [ status, headers, response.body ]
26
+ end
27
+
28
+ protected
29
+
30
+ def dispatch(cgi)
31
+ session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
32
+
33
+ request = ActionController::CgiRequest.new(cgi, session_options)
34
+ response = ActionController::CgiResponse.new(cgi)
35
+
36
+ Dispatcher.send(:prepare_application)
37
+
38
+ controller = ActionController::Routing::Routes.recognize(request)
39
+ controller.process(request, response)
40
+
41
+ return response
42
+ end
43
+
44
+ class RailsDispatcher::CGIStub
45
+ def initialize(env)
46
+ @request = Request.new(env)
47
+ end
48
+
49
+ def env_table() @request.env end
50
+ def params() @request.params end
51
+ def cookies() @request.cookies end
52
+ def query_string() @request.query_string end
53
+
54
+ def [](key)
55
+ # FIXME: This is probably just wrong
56
+ @request.env[key] || @request.cookies[key]
57
+ end
58
+
59
+ def key?(key)
60
+ self[key] ? true : false
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,52 @@
1
+ module Rack
2
+ # Rack::Builder implements a small DSL to iteratively construct Rack
3
+ # applications.
4
+ #
5
+ # Example:
6
+ #
7
+ # app = Rack::Builder.new {
8
+ # use Rack::CommonLogger
9
+ # use Rack::ShowExceptions
10
+ # map "/lobster" do
11
+ # use Rack::Lint
12
+ # run Rack::Lobster.new
13
+ # end
14
+ # }
15
+ #
16
+ # +use+ adds a middleware to the stack, +run+ dispatches to an application.
17
+ # You can use +map+ to construct a Rack::URLMap in a convenient way.
18
+
19
+ class Builder
20
+ def initialize(&block)
21
+ @ins = []
22
+ instance_eval(&block)
23
+ end
24
+
25
+ def use(middleware, *args)
26
+ @ins << lambda { |app| middleware.new(app, *args) }
27
+ end
28
+
29
+ def run(app)
30
+ @ins << app #lambda { |nothing| app }
31
+ end
32
+
33
+ def map(path, &block)
34
+ if @ins.last.kind_of? Hash
35
+ @ins.last[path] = Rack::Builder.new(&block).to_app
36
+ else
37
+ @ins << {}
38
+ map(path, &block)
39
+ end
40
+ end
41
+
42
+ def to_app
43
+ @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
44
+ inner_app = @ins.pop
45
+ @ins.reverse.inject(inner_app) { |a, e| e.call(a) }
46
+ end
47
+
48
+ def call(env)
49
+ to_app.call(env)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ module Rack
2
+ # Rack::Cascade tries an request on several apps, and returns the
3
+ # first response that is not 404 (or in a list of configurable
4
+ # status codes).
5
+
6
+ class Cascade
7
+ attr_reader :apps
8
+
9
+ def initialize(apps, catch=404)
10
+ @apps = apps
11
+ @catch = [*catch]
12
+ end
13
+
14
+ def call(env)
15
+ status = headers = body = nil
16
+ raise ArgumentError, "empty cascade" if @apps.empty?
17
+ @apps.each { |app|
18
+ begin
19
+ status, headers, body = app.call(env)
20
+ break unless @catch.include?(status.to_i)
21
+ end
22
+ }
23
+ [status, headers, body]
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,56 @@
1
+ module Rack
2
+ # Rack::CommonLogger forwards every request to an +app+ given, and
3
+ # logs a line in the Apache common log format to the +logger+, or
4
+ # rack.errors by default.
5
+
6
+ class CommonLogger
7
+ def initialize(app, logger=nil)
8
+ @app = app
9
+ @logger = logger
10
+ end
11
+
12
+ def call(env)
13
+ dup._call(env)
14
+ end
15
+
16
+ def _call(env)
17
+ @env = env
18
+ @logger ||= self
19
+ @time = Time.now
20
+ @status, @header, @body = @app.call(env)
21
+ [@status, @header, self]
22
+ end
23
+
24
+ # By default, log to rack.errors.
25
+ def <<(str)
26
+ @env["rack.errors"].write(str)
27
+ @env["rack.errors"].flush
28
+ end
29
+
30
+ def each
31
+ length = 0
32
+ @body.each { |part|
33
+ length += part.size
34
+ yield part
35
+ }
36
+
37
+ @now = Time.now
38
+
39
+ # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
40
+ # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
41
+ # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
42
+ @logger << %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} %
43
+ [@env["REMOTE_ADDR"] || "-",
44
+ @env["REMOTE_USER"] || "-",
45
+ @now.strftime("%d/%b/%Y %H:%M:%S"),
46
+ @env["REQUEST_METHOD"],
47
+ @env["PATH_INFO"],
48
+ @env["QUERY_STRING"].empty? ? "" : "?"+@env["QUERY_STRING"],
49
+ @env["HTTP_VERSION"],
50
+ @status.to_s[0..3],
51
+ (length.zero? ? "-" : length.to_s),
52
+ @now - @time
53
+ ]
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,108 @@
1
+ module Rack
2
+ # Rack::File serves files below the +root+ given, according to the
3
+ # path info of the Rack request.
4
+ #
5
+ # Handlers can detect if bodies are a Rack::File, and use mechanisms
6
+ # like sendfile on the +path+.
7
+
8
+ class File
9
+ attr_accessor :root
10
+ attr_accessor :path
11
+
12
+ def initialize(root)
13
+ @root = root
14
+ end
15
+
16
+ def call(env)
17
+ dup._call(env)
18
+ end
19
+
20
+ F = ::File
21
+
22
+ def _call(env)
23
+ if env["PATH_INFO"].include? ".."
24
+ return [403, {"Content-Type" => "text/plain"}, ["Forbidden\n"]]
25
+ end
26
+
27
+ @path = F.join(@root, env["PATH_INFO"])
28
+ ext = F.extname(@path)[1..-1]
29
+
30
+ if F.file?(@path) && F.readable?(@path)
31
+ [200, {
32
+ "Content-Type" => MIME_TYPES[ext] || "text/plain",
33
+ "Content-Length" => F.size(@path).to_s
34
+ }, self]
35
+ else
36
+ return [404, {"Content-Type" => "text/plain"},
37
+ ["File not found: #{env["PATH_INFO"]}\n"]]
38
+ end
39
+ end
40
+
41
+ def each
42
+ F.open(@path, "rb") { |file|
43
+ while part = file.read(8192)
44
+ yield part
45
+ end
46
+ }
47
+ end
48
+
49
+ # :stopdoc:
50
+ # From WEBrick.
51
+ MIME_TYPES = {
52
+ "ai" => "application/postscript",
53
+ "asc" => "text/plain",
54
+ "avi" => "video/x-msvideo",
55
+ "bin" => "application/octet-stream",
56
+ "bmp" => "image/bmp",
57
+ "class" => "application/octet-stream",
58
+ "cer" => "application/pkix-cert",
59
+ "crl" => "application/pkix-crl",
60
+ "crt" => "application/x-x509-ca-cert",
61
+ #"crl" => "application/x-pkcs7-crl",
62
+ "css" => "text/css",
63
+ "dms" => "application/octet-stream",
64
+ "doc" => "application/msword",
65
+ "dvi" => "application/x-dvi",
66
+ "eps" => "application/postscript",
67
+ "etx" => "text/x-setext",
68
+ "exe" => "application/octet-stream",
69
+ "gif" => "image/gif",
70
+ "htm" => "text/html",
71
+ "html" => "text/html",
72
+ "jpe" => "image/jpeg",
73
+ "jpeg" => "image/jpeg",
74
+ "jpg" => "image/jpeg",
75
+ "lha" => "application/octet-stream",
76
+ "lzh" => "application/octet-stream",
77
+ "mov" => "video/quicktime",
78
+ "mpe" => "video/mpeg",
79
+ "mpeg" => "video/mpeg",
80
+ "mpg" => "video/mpeg",
81
+ "pbm" => "image/x-portable-bitmap",
82
+ "pdf" => "application/pdf",
83
+ "pgm" => "image/x-portable-graymap",
84
+ "png" => "image/png",
85
+ "pnm" => "image/x-portable-anymap",
86
+ "ppm" => "image/x-portable-pixmap",
87
+ "ppt" => "application/vnd.ms-powerpoint",
88
+ "ps" => "application/postscript",
89
+ "qt" => "video/quicktime",
90
+ "ras" => "image/x-cmu-raster",
91
+ "rb" => "text/plain",
92
+ "rd" => "text/plain",
93
+ "rtf" => "application/rtf",
94
+ "sgm" => "text/sgml",
95
+ "sgml" => "text/sgml",
96
+ "tif" => "image/tiff",
97
+ "tiff" => "image/tiff",
98
+ "txt" => "text/plain",
99
+ "xbm" => "image/x-xbitmap",
100
+ "xls" => "application/vnd.ms-excel",
101
+ "xml" => "text/xml",
102
+ "xpm" => "image/x-xpixmap",
103
+ "xwd" => "image/x-xwindowdump",
104
+ "zip" => "application/zip",
105
+ }
106
+ # :startdoc:
107
+ end
108
+ end
@@ -0,0 +1,57 @@
1
+ module Rack
2
+ module Handler
3
+ class CGI
4
+ def self.run(app, options=nil)
5
+ serve app
6
+ end
7
+
8
+ def self.serve(app)
9
+ env = ENV.to_hash
10
+ env.delete "HTTP_CONTENT_LENGTH"
11
+
12
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
13
+
14
+ env.update({"rack.version" => [0,1],
15
+ "rack.input" => STDIN,
16
+ "rack.errors" => STDERR,
17
+
18
+ "rack.multithread" => false,
19
+ "rack.multiprocess" => true,
20
+ "rack.run_once" => true,
21
+
22
+ "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
23
+ })
24
+
25
+ env["QUERY_STRING"] ||= ""
26
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
27
+ env["REQUEST_PATH"] ||= "/"
28
+
29
+ status, headers, body = app.call(env)
30
+ begin
31
+ send_headers status, headers
32
+ send_body body
33
+ ensure
34
+ body.close if body.respond_to? :close
35
+ end
36
+ end
37
+
38
+ def self.send_headers(status, headers)
39
+ STDOUT.print "Status: #{status}\r\n"
40
+ headers.each { |k, vs|
41
+ vs.each { |v|
42
+ STDOUT.print "#{k}: #{v}\r\n"
43
+ }
44
+ }
45
+ STDOUT.print "\r\n"
46
+ STDOUT.flush
47
+ end
48
+
49
+ def self.send_body(body)
50
+ body.each { |part|
51
+ STDOUT.print part
52
+ STDOUT.flush
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,81 @@
1
+ require 'fcgi'
2
+
3
+ module Rack
4
+ module Handler
5
+ class FastCGI
6
+ def self.run(app, options=nil)
7
+ FCGI.each { |request|
8
+ serve request, app
9
+ }
10
+ end
11
+
12
+ module ProperStream
13
+ def each # This is missing by default.
14
+ while line = gets
15
+ yield line
16
+ end
17
+ end
18
+
19
+ def read(*args)
20
+ if args.empty?
21
+ super || "" # Empty string on EOF.
22
+ else
23
+ super
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.serve(request, app)
29
+ env = request.env
30
+ env.delete "HTTP_CONTENT_LENGTH"
31
+
32
+ request.in.extend ProperStream
33
+
34
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
35
+
36
+ env.update({"rack.version" => [0,1],
37
+ "rack.input" => request.in,
38
+ "rack.errors" => request.err,
39
+
40
+ "rack.multithread" => false,
41
+ "rack.multiprocess" => true,
42
+ "rack.run_once" => false,
43
+
44
+ "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
45
+ })
46
+
47
+ env["QUERY_STRING"] ||= ""
48
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
49
+ env["REQUEST_PATH"] ||= "/"
50
+ env.delete "PATH_INFO" if env["PATH_INFO"] == ""
51
+
52
+ status, headers, body = app.call(env)
53
+ begin
54
+ send_headers request.out, status, headers
55
+ send_body request.out, body
56
+ ensure
57
+ body.close if body.respond_to? :close
58
+ request.finish
59
+ end
60
+ end
61
+
62
+ def self.send_headers(out, status, headers)
63
+ out.print "Status: #{status}\r\n"
64
+ headers.each { |k, vs|
65
+ vs.each { |v|
66
+ out.print "#{k}: #{v}\r\n"
67
+ }
68
+ }
69
+ out.print "\r\n"
70
+ out.flush
71
+ end
72
+
73
+ def self.send_body(out, body)
74
+ body.each { |part|
75
+ out.print part
76
+ out.flush
77
+ }
78
+ end
79
+ end
80
+ end
81
+ end