eac-rack 1.1.1
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/COPYING +18 -0
- data/KNOWN-ISSUES +21 -0
- data/README +399 -0
- data/bin/rackup +4 -0
- data/contrib/rack_logo.svg +111 -0
- data/example/lobster.ru +4 -0
- data/example/protectedlobster.rb +14 -0
- data/example/protectedlobster.ru +8 -0
- data/lib/rack.rb +92 -0
- data/lib/rack/adapter/camping.rb +22 -0
- data/lib/rack/auth/abstract/handler.rb +37 -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/builder.rb +80 -0
- data/lib/rack/cascade.rb +41 -0
- data/lib/rack/chunked.rb +49 -0
- data/lib/rack/commonlogger.rb +49 -0
- data/lib/rack/conditionalget.rb +47 -0
- data/lib/rack/config.rb +15 -0
- data/lib/rack/content_length.rb +29 -0
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +96 -0
- data/lib/rack/directory.rb +157 -0
- data/lib/rack/etag.rb +23 -0
- data/lib/rack/file.rb +90 -0
- data/lib/rack/handler.rb +88 -0
- data/lib/rack/handler/cgi.rb +61 -0
- data/lib/rack/handler/evented_mongrel.rb +8 -0
- data/lib/rack/handler/fastcgi.rb +89 -0
- data/lib/rack/handler/lsws.rb +63 -0
- data/lib/rack/handler/mongrel.rb +90 -0
- data/lib/rack/handler/scgi.rb +62 -0
- data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/rack/handler/thin.rb +18 -0
- data/lib/rack/handler/webrick.rb +69 -0
- data/lib/rack/head.rb +19 -0
- data/lib/rack/lint.rb +575 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/lock.rb +16 -0
- data/lib/rack/logger.rb +20 -0
- data/lib/rack/methodoverride.rb +27 -0
- data/lib/rack/mime.rb +206 -0
- data/lib/rack/mock.rb +189 -0
- data/lib/rack/nulllogger.rb +18 -0
- data/lib/rack/recursive.rb +57 -0
- data/lib/rack/reloader.rb +109 -0
- data/lib/rack/request.rb +271 -0
- data/lib/rack/response.rb +149 -0
- data/lib/rack/rewindable_input.rb +100 -0
- data/lib/rack/runtime.rb +27 -0
- data/lib/rack/sendfile.rb +142 -0
- data/lib/rack/server.rb +212 -0
- data/lib/rack/session/abstract/id.rb +140 -0
- data/lib/rack/session/cookie.rb +90 -0
- data/lib/rack/session/memcache.rb +119 -0
- data/lib/rack/session/pool.rb +100 -0
- data/lib/rack/showexceptions.rb +349 -0
- data/lib/rack/showstatus.rb +106 -0
- data/lib/rack/static.rb +38 -0
- data/lib/rack/urlmap.rb +56 -0
- data/lib/rack/utils.rb +614 -0
- data/rack.gemspec +38 -0
- data/test/spec_rack_auth_basic.rb +73 -0
- data/test/spec_rack_auth_digest.rb +226 -0
- data/test/spec_rack_builder.rb +84 -0
- data/test/spec_rack_camping.rb +51 -0
- data/test/spec_rack_cascade.rb +48 -0
- data/test/spec_rack_cgi.rb +89 -0
- data/test/spec_rack_chunked.rb +62 -0
- data/test/spec_rack_commonlogger.rb +61 -0
- data/test/spec_rack_conditionalget.rb +41 -0
- data/test/spec_rack_config.rb +24 -0
- data/test/spec_rack_content_length.rb +43 -0
- data/test/spec_rack_content_type.rb +30 -0
- data/test/spec_rack_deflater.rb +127 -0
- data/test/spec_rack_directory.rb +61 -0
- data/test/spec_rack_etag.rb +17 -0
- data/test/spec_rack_fastcgi.rb +89 -0
- data/test/spec_rack_file.rb +75 -0
- data/test/spec_rack_handler.rb +43 -0
- data/test/spec_rack_head.rb +30 -0
- data/test/spec_rack_lint.rb +528 -0
- data/test/spec_rack_lobster.rb +45 -0
- data/test/spec_rack_lock.rb +38 -0
- data/test/spec_rack_logger.rb +21 -0
- data/test/spec_rack_methodoverride.rb +60 -0
- data/test/spec_rack_mock.rb +243 -0
- data/test/spec_rack_mongrel.rb +189 -0
- data/test/spec_rack_nulllogger.rb +13 -0
- data/test/spec_rack_recursive.rb +77 -0
- data/test/spec_rack_request.rb +545 -0
- data/test/spec_rack_response.rb +221 -0
- data/test/spec_rack_rewindable_input.rb +118 -0
- data/test/spec_rack_runtime.rb +35 -0
- data/test/spec_rack_sendfile.rb +86 -0
- data/test/spec_rack_session_cookie.rb +73 -0
- data/test/spec_rack_session_memcache.rb +273 -0
- data/test/spec_rack_session_pool.rb +172 -0
- data/test/spec_rack_showexceptions.rb +21 -0
- data/test/spec_rack_showstatus.rb +72 -0
- data/test/spec_rack_static.rb +37 -0
- data/test/spec_rack_thin.rb +91 -0
- data/test/spec_rack_urlmap.rb +215 -0
- data/test/spec_rack_utils.rb +554 -0
- data/test/spec_rack_webrick.rb +130 -0
- data/test/spec_rackup.rb +154 -0
- metadata +311 -0
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'rack/mime'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
# Rack::Directory serves entries below the +root+ given, according to the
|
7
|
+
# path info of the Rack request. If a directory is found, the file's contents
|
8
|
+
# will be presented in an html based index. If a file is found, the env will
|
9
|
+
# be passed to the specified +app+.
|
10
|
+
#
|
11
|
+
# If +app+ is not specified, a Rack::File of the same +root+ will be used.
|
12
|
+
|
13
|
+
class Directory
|
14
|
+
DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
|
15
|
+
DIR_PAGE = <<-PAGE
|
16
|
+
<html><head>
|
17
|
+
<title>%s</title>
|
18
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
19
|
+
<style type='text/css'>
|
20
|
+
table { width:100%%; }
|
21
|
+
.name { text-align:left; }
|
22
|
+
.size, .mtime { text-align:right; }
|
23
|
+
.type { width:11em; }
|
24
|
+
.mtime { width:15em; }
|
25
|
+
</style>
|
26
|
+
</head><body>
|
27
|
+
<h1>%s</h1>
|
28
|
+
<hr />
|
29
|
+
<table>
|
30
|
+
<tr>
|
31
|
+
<th class='name'>Name</th>
|
32
|
+
<th class='size'>Size</th>
|
33
|
+
<th class='type'>Type</th>
|
34
|
+
<th class='mtime'>Last Modified</th>
|
35
|
+
</tr>
|
36
|
+
%s
|
37
|
+
</table>
|
38
|
+
<hr />
|
39
|
+
</body></html>
|
40
|
+
PAGE
|
41
|
+
|
42
|
+
attr_reader :files
|
43
|
+
attr_accessor :root, :path
|
44
|
+
|
45
|
+
def initialize(root, app=nil)
|
46
|
+
@root = F.expand_path(root)
|
47
|
+
@app = app || Rack::File.new(@root)
|
48
|
+
end
|
49
|
+
|
50
|
+
def call(env)
|
51
|
+
dup._call(env)
|
52
|
+
end
|
53
|
+
|
54
|
+
F = ::File
|
55
|
+
|
56
|
+
def _call(env)
|
57
|
+
@env = env
|
58
|
+
@script_name = env['SCRIPT_NAME']
|
59
|
+
@path_info = Utils.unescape(env['PATH_INFO'])
|
60
|
+
|
61
|
+
if forbidden = check_forbidden
|
62
|
+
forbidden
|
63
|
+
else
|
64
|
+
@path = F.join(@root, @path_info)
|
65
|
+
list_path
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_forbidden
|
70
|
+
return unless @path_info.include? ".."
|
71
|
+
|
72
|
+
body = "Forbidden\n"
|
73
|
+
size = Rack::Utils.bytesize(body)
|
74
|
+
return [403, {"Content-Type" => "text/plain",
|
75
|
+
"Content-Length" => size.to_s,
|
76
|
+
"X-Cascade" => "pass"}, [body]]
|
77
|
+
end
|
78
|
+
|
79
|
+
def list_directory
|
80
|
+
@files = [['../','Parent Directory','','','']]
|
81
|
+
glob = F.join(@path, '*')
|
82
|
+
|
83
|
+
Dir[glob].sort.each do |node|
|
84
|
+
stat = stat(node)
|
85
|
+
next unless stat
|
86
|
+
basename = F.basename(node)
|
87
|
+
ext = F.extname(node)
|
88
|
+
|
89
|
+
url = F.join(@script_name, @path_info, basename)
|
90
|
+
size = stat.size
|
91
|
+
type = stat.directory? ? 'directory' : Mime.mime_type(ext)
|
92
|
+
size = stat.directory? ? '-' : filesize_format(size)
|
93
|
+
mtime = stat.mtime.httpdate
|
94
|
+
url << '/' if stat.directory?
|
95
|
+
basename << '/' if stat.directory?
|
96
|
+
|
97
|
+
@files << [ url, basename, size, type, mtime ]
|
98
|
+
end
|
99
|
+
|
100
|
+
return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ]
|
101
|
+
end
|
102
|
+
|
103
|
+
def stat(node, max = 10)
|
104
|
+
F.stat(node)
|
105
|
+
rescue Errno::ENOENT, Errno::ELOOP
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
|
109
|
+
# TODO: add correct response if not readable, not sure if 404 is the best
|
110
|
+
# option
|
111
|
+
def list_path
|
112
|
+
@stat = F.stat(@path)
|
113
|
+
|
114
|
+
if @stat.readable?
|
115
|
+
return @app.call(@env) if @stat.file?
|
116
|
+
return list_directory if @stat.directory?
|
117
|
+
else
|
118
|
+
raise Errno::ENOENT, 'No such file or directory'
|
119
|
+
end
|
120
|
+
|
121
|
+
rescue Errno::ENOENT, Errno::ELOOP
|
122
|
+
return entity_not_found
|
123
|
+
end
|
124
|
+
|
125
|
+
def entity_not_found
|
126
|
+
body = "Entity not found: #{@path_info}\n"
|
127
|
+
size = Rack::Utils.bytesize(body)
|
128
|
+
return [404, {"Content-Type" => "text/plain",
|
129
|
+
"Content-Length" => size.to_s,
|
130
|
+
"X-Cascade" => "pass"}, [body]]
|
131
|
+
end
|
132
|
+
|
133
|
+
def each
|
134
|
+
show_path = @path.sub(/^#{@root}/,'')
|
135
|
+
files = @files.map{|f| DIR_FILE % f }*"\n"
|
136
|
+
page = DIR_PAGE % [ show_path, show_path , files ]
|
137
|
+
page.each_line{|l| yield l }
|
138
|
+
end
|
139
|
+
|
140
|
+
# Stolen from Ramaze
|
141
|
+
|
142
|
+
FILESIZE_FORMAT = [
|
143
|
+
['%.1fT', 1 << 40],
|
144
|
+
['%.1fG', 1 << 30],
|
145
|
+
['%.1fM', 1 << 20],
|
146
|
+
['%.1fK', 1 << 10],
|
147
|
+
]
|
148
|
+
|
149
|
+
def filesize_format(int)
|
150
|
+
FILESIZE_FORMAT.each do |format, size|
|
151
|
+
return format % (int.to_f / size) if int >= size
|
152
|
+
end
|
153
|
+
|
154
|
+
int.to_s + 'B'
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/lib/rack/etag.rb
ADDED
@@ -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
|
data/lib/rack/file.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'rack/mime'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
# Rack::File serves files below the +root+ given, according to the
|
7
|
+
# path info of the Rack request.
|
8
|
+
#
|
9
|
+
# Handlers can detect if bodies are a Rack::File, and use mechanisms
|
10
|
+
# like sendfile on the +path+.
|
11
|
+
|
12
|
+
class File
|
13
|
+
attr_accessor :root
|
14
|
+
attr_accessor :path
|
15
|
+
|
16
|
+
alias :to_path :path
|
17
|
+
|
18
|
+
def initialize(root)
|
19
|
+
@root = root
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
dup._call(env)
|
24
|
+
end
|
25
|
+
|
26
|
+
F = ::File
|
27
|
+
|
28
|
+
def _call(env)
|
29
|
+
@path_info = Utils.unescape(env["PATH_INFO"])
|
30
|
+
return forbidden if @path_info.include? ".."
|
31
|
+
|
32
|
+
@path = F.join(@root, @path_info)
|
33
|
+
|
34
|
+
begin
|
35
|
+
if F.file?(@path) && F.readable?(@path)
|
36
|
+
serving
|
37
|
+
else
|
38
|
+
raise Errno::EPERM
|
39
|
+
end
|
40
|
+
rescue SystemCallError
|
41
|
+
not_found
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def forbidden
|
46
|
+
body = "Forbidden\n"
|
47
|
+
[403, {"Content-Type" => "text/plain",
|
48
|
+
"Content-Length" => body.size.to_s,
|
49
|
+
"X-Cascade" => "pass"},
|
50
|
+
[body]]
|
51
|
+
end
|
52
|
+
|
53
|
+
# NOTE:
|
54
|
+
# We check via File::size? whether this file provides size info
|
55
|
+
# via stat (e.g. /proc files often don't), otherwise we have to
|
56
|
+
# figure it out by reading the whole file into memory. And while
|
57
|
+
# we're at it we also use this as body then.
|
58
|
+
|
59
|
+
def serving
|
60
|
+
if size = F.size?(@path)
|
61
|
+
body = self
|
62
|
+
else
|
63
|
+
body = [F.read(@path)]
|
64
|
+
size = Utils.bytesize(body.first)
|
65
|
+
end
|
66
|
+
|
67
|
+
[200, {
|
68
|
+
"Last-Modified" => F.mtime(@path).httpdate,
|
69
|
+
"Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain'),
|
70
|
+
"Content-Length" => size.to_s
|
71
|
+
}, body]
|
72
|
+
end
|
73
|
+
|
74
|
+
def not_found
|
75
|
+
body = "File not found: #{@path_info}\n"
|
76
|
+
[404, {"Content-Type" => "text/plain",
|
77
|
+
"Content-Length" => body.size.to_s,
|
78
|
+
"X-Cascade" => "pass"},
|
79
|
+
[body]]
|
80
|
+
end
|
81
|
+
|
82
|
+
def each
|
83
|
+
F.open(@path, "rb") { |file|
|
84
|
+
while part = file.read(8192)
|
85
|
+
yield part
|
86
|
+
end
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/rack/handler.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
module Rack
|
2
|
+
# *Handlers* connect web servers with Rack.
|
3
|
+
#
|
4
|
+
# Rack includes Handlers for Mongrel, WEBrick, FastCGI, CGI, SCGI
|
5
|
+
# and LiteSpeed.
|
6
|
+
#
|
7
|
+
# Handlers usually are activated by calling <tt>MyHandler.run(myapp)</tt>.
|
8
|
+
# A second optional hash can be passed to include server-specific
|
9
|
+
# configuration.
|
10
|
+
module Handler
|
11
|
+
def self.get(server)
|
12
|
+
return unless server
|
13
|
+
server = server.to_s
|
14
|
+
|
15
|
+
if klass = @handlers[server]
|
16
|
+
obj = Object
|
17
|
+
klass.split("::").each { |x| obj = obj.const_get(x) }
|
18
|
+
obj
|
19
|
+
else
|
20
|
+
try_require('rack/handler', server)
|
21
|
+
const_get(server)
|
22
|
+
end
|
23
|
+
end
|
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
|
+
|
44
|
+
# Transforms server-name constants to their canonical form as filenames,
|
45
|
+
# then tries to require them but silences the LoadError if not found
|
46
|
+
#
|
47
|
+
# Naming convention:
|
48
|
+
#
|
49
|
+
# Foo # => 'foo'
|
50
|
+
# FooBar # => 'foo_bar.rb'
|
51
|
+
# FooBAR # => 'foobar.rb'
|
52
|
+
# FOObar # => 'foobar.rb'
|
53
|
+
# FOOBAR # => 'foobar.rb'
|
54
|
+
# FooBarBaz # => 'foo_bar_baz.rb'
|
55
|
+
def self.try_require(prefix, const_name)
|
56
|
+
file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }.
|
57
|
+
gsub(/[A-Z]+[^A-Z]/, '_\&').downcase
|
58
|
+
|
59
|
+
require(::File.join(prefix, file))
|
60
|
+
rescue LoadError
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.register(server, klass)
|
64
|
+
@handlers ||= {}
|
65
|
+
@handlers[server] = klass
|
66
|
+
end
|
67
|
+
|
68
|
+
autoload :CGI, "rack/handler/cgi"
|
69
|
+
autoload :FastCGI, "rack/handler/fastcgi"
|
70
|
+
autoload :Mongrel, "rack/handler/mongrel"
|
71
|
+
autoload :EventedMongrel, "rack/handler/evented_mongrel"
|
72
|
+
autoload :SwiftipliedMongrel, "rack/handler/swiftiplied_mongrel"
|
73
|
+
autoload :WEBrick, "rack/handler/webrick"
|
74
|
+
autoload :LSWS, "rack/handler/lsws"
|
75
|
+
autoload :SCGI, "rack/handler/scgi"
|
76
|
+
autoload :Thin, "rack/handler/thin"
|
77
|
+
|
78
|
+
register 'cgi', 'Rack::Handler::CGI'
|
79
|
+
register 'fastcgi', 'Rack::Handler::FastCGI'
|
80
|
+
register 'mongrel', 'Rack::Handler::Mongrel'
|
81
|
+
register 'emongrel', 'Rack::Handler::EventedMongrel'
|
82
|
+
register 'smongrel', 'Rack::Handler::SwiftipliedMongrel'
|
83
|
+
register 'webrick', 'Rack::Handler::WEBrick'
|
84
|
+
register 'lsws', 'Rack::Handler::LSWS'
|
85
|
+
register 'scgi', 'Rack::Handler::SCGI'
|
86
|
+
register 'thin', 'Rack::Handler::Thin'
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rack/content_length'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Handler
|
5
|
+
class CGI
|
6
|
+
def self.run(app, options=nil)
|
7
|
+
serve app
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.serve(app)
|
11
|
+
app = ContentLength.new(app)
|
12
|
+
|
13
|
+
env = ENV.to_hash
|
14
|
+
env.delete "HTTP_CONTENT_LENGTH"
|
15
|
+
|
16
|
+
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
17
|
+
|
18
|
+
env.update({"rack.version" => [1,1],
|
19
|
+
"rack.input" => $stdin,
|
20
|
+
"rack.errors" => $stderr,
|
21
|
+
|
22
|
+
"rack.multithread" => false,
|
23
|
+
"rack.multiprocess" => true,
|
24
|
+
"rack.run_once" => true,
|
25
|
+
|
26
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
|
27
|
+
})
|
28
|
+
|
29
|
+
env["QUERY_STRING"] ||= ""
|
30
|
+
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
31
|
+
env["REQUEST_PATH"] ||= "/"
|
32
|
+
|
33
|
+
status, headers, body = app.call(env)
|
34
|
+
begin
|
35
|
+
send_headers status, headers
|
36
|
+
send_body body
|
37
|
+
ensure
|
38
|
+
body.close if body.respond_to? :close
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.send_headers(status, headers)
|
43
|
+
STDOUT.print "Status: #{status}\r\n"
|
44
|
+
headers.each { |k, vs|
|
45
|
+
vs.split("\n").each { |v|
|
46
|
+
STDOUT.print "#{k}: #{v}\r\n"
|
47
|
+
}
|
48
|
+
}
|
49
|
+
STDOUT.print "\r\n"
|
50
|
+
STDOUT.flush
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.send_body(body)
|
54
|
+
body.each { |part|
|
55
|
+
STDOUT.print part
|
56
|
+
STDOUT.flush
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'fcgi'
|
2
|
+
require 'socket'
|
3
|
+
require 'rack/content_length'
|
4
|
+
require 'rack/rewindable_input'
|
5
|
+
|
6
|
+
if defined? FCGI::Stream
|
7
|
+
class FCGI::Stream
|
8
|
+
alias _rack_read_without_buffer read
|
9
|
+
|
10
|
+
def read(n, buffer=nil)
|
11
|
+
buf = _rack_read_without_buffer n
|
12
|
+
buffer.replace(buf.to_s) if buffer
|
13
|
+
buf
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Rack
|
19
|
+
module Handler
|
20
|
+
class FastCGI
|
21
|
+
def self.run(app, options={})
|
22
|
+
file = options[:File] and STDIN.reopen(UNIXServer.new(file))
|
23
|
+
port = options[:Port] and STDIN.reopen(TCPServer.new(port))
|
24
|
+
FCGI.each { |request|
|
25
|
+
serve request, app
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.serve(request, app)
|
30
|
+
app = Rack::ContentLength.new(app)
|
31
|
+
|
32
|
+
env = request.env
|
33
|
+
env.delete "HTTP_CONTENT_LENGTH"
|
34
|
+
|
35
|
+
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
36
|
+
|
37
|
+
rack_input = RewindableInput.new(request.in)
|
38
|
+
|
39
|
+
env.update({"rack.version" => [1,1],
|
40
|
+
"rack.input" => rack_input,
|
41
|
+
"rack.errors" => request.err,
|
42
|
+
|
43
|
+
"rack.multithread" => false,
|
44
|
+
"rack.multiprocess" => true,
|
45
|
+
"rack.run_once" => false,
|
46
|
+
|
47
|
+
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
|
48
|
+
})
|
49
|
+
|
50
|
+
env["QUERY_STRING"] ||= ""
|
51
|
+
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
52
|
+
env["REQUEST_PATH"] ||= "/"
|
53
|
+
env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
|
54
|
+
env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
|
55
|
+
|
56
|
+
begin
|
57
|
+
status, headers, body = app.call(env)
|
58
|
+
begin
|
59
|
+
send_headers request.out, status, headers
|
60
|
+
send_body request.out, body
|
61
|
+
ensure
|
62
|
+
body.close if body.respond_to? :close
|
63
|
+
end
|
64
|
+
ensure
|
65
|
+
rack_input.close
|
66
|
+
request.finish
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.send_headers(out, status, headers)
|
71
|
+
out.print "Status: #{status}\r\n"
|
72
|
+
headers.each { |k, vs|
|
73
|
+
vs.split("\n").each { |v|
|
74
|
+
out.print "#{k}: #{v}\r\n"
|
75
|
+
}
|
76
|
+
}
|
77
|
+
out.print "\r\n"
|
78
|
+
out.flush
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.send_body(out, body)
|
82
|
+
body.each { |part|
|
83
|
+
out.print part
|
84
|
+
out.flush
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|