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.
- data/AUTHORS +3 -0
- data/COPYING +18 -0
- data/KNOWN-ISSUES +18 -0
- data/RDOX +144 -0
- data/README +154 -0
- data/Rakefile +174 -0
- data/SPEC +132 -0
- data/bin/rackup +148 -0
- data/contrib/rack_logo.svg +111 -0
- data/example/lobster.ru +4 -0
- data/lib/rack.rb +67 -0
- data/lib/rack/adapter/camping.rb +16 -0
- data/lib/rack/adapter/rails.rb +65 -0
- data/lib/rack/builder.rb +52 -0
- data/lib/rack/cascade.rb +26 -0
- data/lib/rack/commonlogger.rb +56 -0
- data/lib/rack/file.rb +108 -0
- data/lib/rack/handler/cgi.rb +57 -0
- data/lib/rack/handler/fastcgi.rb +81 -0
- data/lib/rack/handler/mongrel.rb +57 -0
- data/lib/rack/handler/webrick.rb +56 -0
- data/lib/rack/lint.rb +394 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/mock.rb +183 -0
- data/lib/rack/recursive.rb +57 -0
- data/lib/rack/reloader.rb +64 -0
- data/lib/rack/request.rb +112 -0
- data/lib/rack/response.rb +114 -0
- data/lib/rack/showexceptions.rb +344 -0
- data/lib/rack/urlmap.rb +50 -0
- data/lib/rack/utils.rb +176 -0
- data/test/cgi/lighttpd.conf +20 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test.fcgi +9 -0
- data/test/cgi/test.ru +7 -0
- data/test/spec_rack_camping.rb +44 -0
- data/test/spec_rack_cascade.rb +35 -0
- data/test/spec_rack_cgi.rb +82 -0
- data/test/spec_rack_commonlogger.rb +32 -0
- data/test/spec_rack_fastcgi.rb +82 -0
- data/test/spec_rack_file.rb +32 -0
- data/test/spec_rack_lint.rb +317 -0
- data/test/spec_rack_lobster.rb +45 -0
- data/test/spec_rack_mock.rb +150 -0
- data/test/spec_rack_mongrel.rb +87 -0
- data/test/spec_rack_recursive.rb +77 -0
- data/test/spec_rack_request.rb +219 -0
- data/test/spec_rack_response.rb +110 -0
- data/test/spec_rack_showexceptions.rb +21 -0
- data/test/spec_rack_urlmap.rb +140 -0
- data/test/spec_rack_utils.rb +57 -0
- data/test/spec_rack_webrick.rb +89 -0
- data/test/testrequest.rb +43 -0
- 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
|
data/lib/rack/builder.rb
ADDED
@@ -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
|
data/lib/rack/cascade.rb
ADDED
@@ -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
|
data/lib/rack/file.rb
ADDED
@@ -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
|