technomancy-rack 0.3.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.
- data/KNOWN-ISSUES +18 -0
- data/README +242 -0
- data/bin/rackup +183 -0
- data/lib/rack.rb +92 -0
- data/lib/rack/adapter/camping.rb +22 -0
- data/lib/rack/auth/abstract/handler.rb +28 -0
- data/lib/rack/auth/abstract/request.rb +37 -0
- data/lib/rack/auth/basic.rb +58 -0
- data/lib/rack/auth/digest/md5.rb +124 -0
- data/lib/rack/auth/digest/nonce.rb +51 -0
- data/lib/rack/auth/digest/params.rb +55 -0
- data/lib/rack/auth/digest/request.rb +40 -0
- data/lib/rack/auth/openid.rb +116 -0
- data/lib/rack/builder.rb +56 -0
- data/lib/rack/cascade.rb +36 -0
- data/lib/rack/commonlogger.rb +56 -0
- data/lib/rack/file.rb +112 -0
- data/lib/rack/handler/cgi.rb +57 -0
- data/lib/rack/handler/fastcgi.rb +83 -0
- data/lib/rack/handler/lsws.rb +52 -0
- data/lib/rack/handler/mongrel.rb +97 -0
- data/lib/rack/handler/scgi.rb +57 -0
- data/lib/rack/handler/webrick.rb +57 -0
- data/lib/rack/lint.rb +394 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/mock.rb +172 -0
- data/lib/rack/recursive.rb +57 -0
- data/lib/rack/reloader.rb +64 -0
- data/lib/rack/request.rb +197 -0
- data/lib/rack/response.rb +166 -0
- data/lib/rack/session/abstract/id.rb +126 -0
- data/lib/rack/session/cookie.rb +71 -0
- data/lib/rack/session/memcache.rb +83 -0
- data/lib/rack/session/pool.rb +67 -0
- data/lib/rack/showexceptions.rb +344 -0
- data/lib/rack/showstatus.rb +103 -0
- data/lib/rack/static.rb +38 -0
- data/lib/rack/urlmap.rb +48 -0
- data/lib/rack/utils.rb +256 -0
- data/test/spec_rack_auth_basic.rb +69 -0
- data/test/spec_rack_auth_digest.rb +169 -0
- data/test/spec_rack_builder.rb +50 -0
- data/test/spec_rack_camping.rb +47 -0
- data/test/spec_rack_cascade.rb +50 -0
- data/test/spec_rack_cgi.rb +91 -0
- data/test/spec_rack_commonlogger.rb +32 -0
- data/test/spec_rack_fastcgi.rb +91 -0
- data/test/spec_rack_file.rb +40 -0
- data/test/spec_rack_lint.rb +317 -0
- data/test/spec_rack_lobster.rb +45 -0
- data/test/spec_rack_mock.rb +152 -0
- data/test/spec_rack_mongrel.rb +165 -0
- data/test/spec_rack_recursive.rb +77 -0
- data/test/spec_rack_request.rb +384 -0
- data/test/spec_rack_response.rb +167 -0
- data/test/spec_rack_session_cookie.rb +49 -0
- data/test/spec_rack_session_memcache.rb +100 -0
- data/test/spec_rack_session_pool.rb +84 -0
- data/test/spec_rack_showexceptions.rb +21 -0
- data/test/spec_rack_showstatus.rb +71 -0
- data/test/spec_rack_static.rb +37 -0
- data/test/spec_rack_urlmap.rb +175 -0
- data/test/spec_rack_utils.rb +69 -0
- data/test/spec_rack_webrick.rb +106 -0
- data/test/testrequest.rb +43 -0
- metadata +167 -0
data/lib/rack/builder.rb
ADDED
@@ -0,0 +1,56 @@
|
|
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, &block)
|
26
|
+
@ins << if block_given?
|
27
|
+
lambda { |app| middleware.new(app, *args, &block) }
|
28
|
+
else
|
29
|
+
lambda { |app| middleware.new(app, *args) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def run(app)
|
34
|
+
@ins << app #lambda { |nothing| app }
|
35
|
+
end
|
36
|
+
|
37
|
+
def map(path, &block)
|
38
|
+
if @ins.last.kind_of? Hash
|
39
|
+
@ins.last[path] = Rack::Builder.new(&block).to_app
|
40
|
+
else
|
41
|
+
@ins << {}
|
42
|
+
map(path, &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_app
|
47
|
+
@ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
|
48
|
+
inner_app = @ins.last
|
49
|
+
@ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def call(env)
|
53
|
+
to_app.call(env)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/rack/cascade.rb
ADDED
@@ -0,0 +1,36 @@
|
|
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
|
+
|
26
|
+
def add app
|
27
|
+
@apps << app
|
28
|
+
end
|
29
|
+
|
30
|
+
def include? app
|
31
|
+
@apps.include? app
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :<<, :add
|
35
|
+
end
|
36
|
+
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,112 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
# Rack::File serves files below the +root+ given, according to the
|
5
|
+
# path info of the Rack request.
|
6
|
+
#
|
7
|
+
# Handlers can detect if bodies are a Rack::File, and use mechanisms
|
8
|
+
# like sendfile on the +path+.
|
9
|
+
|
10
|
+
class File
|
11
|
+
attr_accessor :root
|
12
|
+
attr_accessor :path
|
13
|
+
|
14
|
+
def initialize(root)
|
15
|
+
@root = root
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
dup._call(env)
|
20
|
+
end
|
21
|
+
|
22
|
+
F = ::File
|
23
|
+
|
24
|
+
def _call(env)
|
25
|
+
if env["PATH_INFO"].include? ".."
|
26
|
+
return [403, {"Content-Type" => "text/plain"}, ["Forbidden\n"]]
|
27
|
+
end
|
28
|
+
|
29
|
+
@path = F.join(@root, Utils.unescape(env["PATH_INFO"]))
|
30
|
+
ext = F.extname(@path)[1..-1]
|
31
|
+
|
32
|
+
if F.file?(@path) && F.readable?(@path)
|
33
|
+
[200, {
|
34
|
+
"Last-Modified" => F.mtime(@path).rfc822,
|
35
|
+
"Content-Type" => MIME_TYPES[ext] || "text/plain",
|
36
|
+
"Content-Length" => F.size(@path).to_s
|
37
|
+
}, self]
|
38
|
+
else
|
39
|
+
return [404, {"Content-Type" => "text/plain"},
|
40
|
+
["File not found: #{env["PATH_INFO"]}\n"]]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def each
|
45
|
+
F.open(@path, "rb") { |file|
|
46
|
+
while part = file.read(8192)
|
47
|
+
yield part
|
48
|
+
end
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# :stopdoc:
|
53
|
+
# From WEBrick.
|
54
|
+
MIME_TYPES = {
|
55
|
+
"ai" => "application/postscript",
|
56
|
+
"asc" => "text/plain",
|
57
|
+
"avi" => "video/x-msvideo",
|
58
|
+
"bin" => "application/octet-stream",
|
59
|
+
"bmp" => "image/bmp",
|
60
|
+
"class" => "application/octet-stream",
|
61
|
+
"cer" => "application/pkix-cert",
|
62
|
+
"crl" => "application/pkix-crl",
|
63
|
+
"crt" => "application/x-x509-ca-cert",
|
64
|
+
#"crl" => "application/x-pkcs7-crl",
|
65
|
+
"css" => "text/css",
|
66
|
+
"dms" => "application/octet-stream",
|
67
|
+
"doc" => "application/msword",
|
68
|
+
"dvi" => "application/x-dvi",
|
69
|
+
"eps" => "application/postscript",
|
70
|
+
"etx" => "text/x-setext",
|
71
|
+
"exe" => "application/octet-stream",
|
72
|
+
"gif" => "image/gif",
|
73
|
+
"htm" => "text/html",
|
74
|
+
"html" => "text/html",
|
75
|
+
"jpe" => "image/jpeg",
|
76
|
+
"jpeg" => "image/jpeg",
|
77
|
+
"jpg" => "image/jpeg",
|
78
|
+
"js" => "text/javascript",
|
79
|
+
"lha" => "application/octet-stream",
|
80
|
+
"lzh" => "application/octet-stream",
|
81
|
+
"mov" => "video/quicktime",
|
82
|
+
"mpe" => "video/mpeg",
|
83
|
+
"mpeg" => "video/mpeg",
|
84
|
+
"mpg" => "video/mpeg",
|
85
|
+
"pbm" => "image/x-portable-bitmap",
|
86
|
+
"pdf" => "application/pdf",
|
87
|
+
"pgm" => "image/x-portable-graymap",
|
88
|
+
"png" => "image/png",
|
89
|
+
"pnm" => "image/x-portable-anymap",
|
90
|
+
"ppm" => "image/x-portable-pixmap",
|
91
|
+
"ppt" => "application/vnd.ms-powerpoint",
|
92
|
+
"ps" => "application/postscript",
|
93
|
+
"qt" => "video/quicktime",
|
94
|
+
"ras" => "image/x-cmu-raster",
|
95
|
+
"rb" => "text/plain",
|
96
|
+
"rd" => "text/plain",
|
97
|
+
"rtf" => "application/rtf",
|
98
|
+
"sgm" => "text/sgml",
|
99
|
+
"sgml" => "text/sgml",
|
100
|
+
"tif" => "image/tiff",
|
101
|
+
"tiff" => "image/tiff",
|
102
|
+
"txt" => "text/plain",
|
103
|
+
"xbm" => "image/x-xbitmap",
|
104
|
+
"xls" => "application/vnd.ms-excel",
|
105
|
+
"xml" => "text/xml",
|
106
|
+
"xpm" => "image/x-xpixmap",
|
107
|
+
"xwd" => "image/x-xwindowdump",
|
108
|
+
"zip" => "application/zip",
|
109
|
+
}
|
110
|
+
# :startdoc:
|
111
|
+
end
|
112
|
+
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,83 @@
|
|
1
|
+
require 'fcgi'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Handler
|
5
|
+
class FastCGI
|
6
|
+
def self.run(app, options={})
|
7
|
+
file = options[:File] and STDIN.reopen(UNIXServer.new(file))
|
8
|
+
port = options[:Port] and STDIN.reopen(TCPServer.new(port))
|
9
|
+
FCGI.each { |request|
|
10
|
+
serve request, app
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
module ProperStream # :nodoc:
|
15
|
+
def each # This is missing by default.
|
16
|
+
while line = gets
|
17
|
+
yield line
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def read(*args)
|
22
|
+
if args.empty?
|
23
|
+
super || "" # Empty string on EOF.
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.serve(request, app)
|
31
|
+
env = request.env
|
32
|
+
env.delete "HTTP_CONTENT_LENGTH"
|
33
|
+
|
34
|
+
request.in.extend ProperStream
|
35
|
+
|
36
|
+
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
37
|
+
|
38
|
+
env.update({"rack.version" => [0,1],
|
39
|
+
"rack.input" => request.in,
|
40
|
+
"rack.errors" => request.err,
|
41
|
+
|
42
|
+
"rack.multithread" => false,
|
43
|
+
"rack.multiprocess" => true,
|
44
|
+
"rack.run_once" => false,
|
45
|
+
|
46
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
|
47
|
+
})
|
48
|
+
|
49
|
+
env["QUERY_STRING"] ||= ""
|
50
|
+
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
51
|
+
env["REQUEST_PATH"] ||= "/"
|
52
|
+
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
|
53
|
+
|
54
|
+
status, headers, body = app.call(env)
|
55
|
+
begin
|
56
|
+
send_headers request.out, status, headers
|
57
|
+
send_body request.out, body
|
58
|
+
ensure
|
59
|
+
body.close if body.respond_to? :close
|
60
|
+
request.finish
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.send_headers(out, status, headers)
|
65
|
+
out.print "Status: #{status}\r\n"
|
66
|
+
headers.each { |k, vs|
|
67
|
+
vs.each { |v|
|
68
|
+
out.print "#{k}: #{v}\r\n"
|
69
|
+
}
|
70
|
+
}
|
71
|
+
out.print "\r\n"
|
72
|
+
out.flush
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.send_body(out, body)
|
76
|
+
body.each { |part|
|
77
|
+
out.print part
|
78
|
+
out.flush
|
79
|
+
}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'lsapi'
|
2
|
+
#require 'cgi'
|
3
|
+
module Rack
|
4
|
+
module Handler
|
5
|
+
class LSWS
|
6
|
+
def self.run(app, options=nil)
|
7
|
+
while LSAPI.accept != nil
|
8
|
+
serve app
|
9
|
+
end
|
10
|
+
end
|
11
|
+
def self.serve(app)
|
12
|
+
env = ENV.to_hash
|
13
|
+
env.delete "HTTP_CONTENT_LENGTH"
|
14
|
+
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
15
|
+
env.update({"rack.version" => [0,1],
|
16
|
+
"rack.input" => STDIN,
|
17
|
+
"rack.errors" => STDERR,
|
18
|
+
"rack.multithread" => false,
|
19
|
+
"rack.multiprocess" => true,
|
20
|
+
"rack.run_once" => false,
|
21
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
|
22
|
+
})
|
23
|
+
env["QUERY_STRING"] ||= ""
|
24
|
+
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
25
|
+
env["REQUEST_PATH"] ||= "/"
|
26
|
+
status, headers, body = app.call(env)
|
27
|
+
begin
|
28
|
+
send_headers status, headers
|
29
|
+
send_body body
|
30
|
+
ensure
|
31
|
+
body.close if body.respond_to? :close
|
32
|
+
end
|
33
|
+
end
|
34
|
+
def self.send_headers(status, headers)
|
35
|
+
print "Status: #{status}\r\n"
|
36
|
+
headers.each { |k, vs|
|
37
|
+
vs.each { |v|
|
38
|
+
print "#{k}: #{v}\r\n"
|
39
|
+
}
|
40
|
+
}
|
41
|
+
print "\r\n"
|
42
|
+
STDOUT.flush
|
43
|
+
end
|
44
|
+
def self.send_body(body)
|
45
|
+
body.each { |part|
|
46
|
+
print part
|
47
|
+
STDOUT.flush
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|