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.
- data/COPYING +1 -1
- data/KNOWN-ISSUES +3 -0
- data/RDOX +0 -428
- data/README +61 -26
- data/SPEC +8 -1
- data/bin/rackup +2 -174
- data/lib/rack.rb +10 -8
- data/lib/rack/builder.rb +17 -0
- data/lib/rack/cascade.rb +17 -12
- data/lib/rack/chunked.rb +2 -2
- data/lib/rack/commonlogger.rb +31 -43
- data/lib/rack/config.rb +15 -0
- data/lib/rack/content_type.rb +1 -1
- data/lib/rack/directory.rb +6 -2
- data/lib/rack/etag.rb +23 -0
- data/lib/rack/file.rb +4 -2
- data/lib/rack/handler.rb +19 -0
- data/lib/rack/handler/cgi.rb +1 -1
- data/lib/rack/handler/fastcgi.rb +2 -3
- data/lib/rack/handler/lsws.rb +4 -1
- data/lib/rack/handler/mongrel.rb +8 -5
- data/lib/rack/handler/scgi.rb +4 -4
- data/lib/rack/handler/webrick.rb +2 -4
- data/lib/rack/lint.rb +44 -15
- data/lib/rack/logger.rb +20 -0
- data/lib/rack/mime.rb +3 -1
- data/lib/rack/mock.rb +30 -4
- data/lib/rack/nulllogger.rb +18 -0
- data/lib/rack/reloader.rb +4 -1
- data/lib/rack/request.rb +40 -15
- data/lib/rack/response.rb +5 -39
- 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 +3 -5
- data/lib/rack/session/cookie.rb +3 -4
- data/lib/rack/session/memcache.rb +53 -43
- data/lib/rack/session/pool.rb +1 -1
- data/lib/rack/urlmap.rb +9 -8
- data/lib/rack/utils.rb +230 -11
- data/rack.gemspec +33 -49
- data/test/spec_rack_cascade.rb +3 -5
- data/test/spec_rack_cgi.rb +3 -3
- data/test/spec_rack_commonlogger.rb +39 -10
- data/test/spec_rack_config.rb +24 -0
- data/test/spec_rack_directory.rb +1 -1
- data/test/spec_rack_etag.rb +17 -0
- data/test/spec_rack_fastcgi.rb +2 -2
- data/test/spec_rack_file.rb +1 -1
- data/test/spec_rack_lint.rb +26 -19
- data/test/spec_rack_logger.rb +21 -0
- data/test/spec_rack_mock.rb +87 -1
- data/test/spec_rack_mongrel.rb +4 -4
- data/test/spec_rack_nulllogger.rb +13 -0
- data/test/spec_rack_request.rb +47 -6
- data/test/spec_rack_response.rb +3 -0
- data/test/spec_rack_runtime.rb +35 -0
- data/test/spec_rack_sendfile.rb +86 -0
- data/test/spec_rack_session_cookie.rb +1 -10
- data/test/spec_rack_session_memcache.rb +53 -20
- data/test/spec_rack_urlmap.rb +30 -0
- data/test/spec_rack_utils.rb +171 -6
- data/test/spec_rack_webrick.rb +4 -4
- data/test/spec_rackup.rb +154 -0
- metadata +37 -79
- data/Rakefile +0 -164
- data/lib/rack/auth/openid.rb +0 -480
- data/test/cgi/lighttpd.conf +0 -20
- data/test/cgi/test +0 -9
- data/test/cgi/test.fcgi +0 -8
- data/test/cgi/test.ru +0 -7
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +0 -10
- data/test/multipart/ie +0 -6
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -10
- data/test/spec_rack_auth_openid.rb +0 -84
- data/test/testrequest.rb +0 -57
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/mock.rb
CHANGED
@@ -40,7 +40,7 @@ module Rack
|
|
40
40
|
end
|
41
41
|
|
42
42
|
DEFAULT_ENV = {
|
43
|
-
"rack.version" => [1,
|
43
|
+
"rack.version" => [1,1],
|
44
44
|
"rack.input" => StringIO.new,
|
45
45
|
"rack.errors" => StringIO.new,
|
46
46
|
"rack.multithread" => true,
|
@@ -73,14 +73,17 @@ module Rack
|
|
73
73
|
# Return the Rack environment used for a request to +uri+.
|
74
74
|
def self.env_for(uri="", opts={})
|
75
75
|
uri = URI(uri)
|
76
|
+
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
|
77
|
+
|
76
78
|
env = DEFAULT_ENV.dup
|
77
79
|
|
78
|
-
env["REQUEST_METHOD"] = opts[:method]
|
80
|
+
env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET"
|
79
81
|
env["SERVER_NAME"] = uri.host || "example.org"
|
80
82
|
env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
|
81
83
|
env["QUERY_STRING"] = uri.query.to_s
|
82
84
|
env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
|
83
85
|
env["rack.url_scheme"] = uri.scheme || "http"
|
86
|
+
env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
|
84
87
|
|
85
88
|
env["SCRIPT_NAME"] = opts[:script_name] || ""
|
86
89
|
|
@@ -90,7 +93,30 @@ module Rack
|
|
90
93
|
env["rack.errors"] = StringIO.new
|
91
94
|
end
|
92
95
|
|
93
|
-
opts[:
|
96
|
+
if params = opts[:params]
|
97
|
+
if env["REQUEST_METHOD"] == "GET"
|
98
|
+
params = Utils.parse_nested_query(params) if params.is_a?(String)
|
99
|
+
params.update(Utils.parse_nested_query(env["QUERY_STRING"]))
|
100
|
+
env["QUERY_STRING"] = Utils.build_nested_query(params)
|
101
|
+
elsif !opts.has_key?(:input)
|
102
|
+
opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
|
103
|
+
if params.is_a?(Hash)
|
104
|
+
if data = Utils::Multipart.build_multipart(params)
|
105
|
+
opts[:input] = data
|
106
|
+
opts["CONTENT_LENGTH"] ||= data.length.to_s
|
107
|
+
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}"
|
108
|
+
else
|
109
|
+
opts[:input] = Utils.build_nested_query(params)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
opts[:input] = params
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
empty_str = ""
|
118
|
+
empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding
|
119
|
+
opts[:input] ||= empty_str
|
94
120
|
if String === opts[:input]
|
95
121
|
rack_input = StringIO.new(opts[:input])
|
96
122
|
else
|
@@ -128,7 +154,7 @@ module Rack
|
|
128
154
|
@body = ""
|
129
155
|
body.each { |part| @body << part }
|
130
156
|
|
131
|
-
@errors = errors.string
|
157
|
+
@errors = errors.string if errors.respond_to?(:string)
|
132
158
|
end
|
133
159
|
|
134
160
|
# Status
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rack
|
2
|
+
class NullLogger
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
env['rack.logger'] = self
|
9
|
+
@app.call(env)
|
10
|
+
end
|
11
|
+
|
12
|
+
def info(progname = nil, &block); end
|
13
|
+
def debug(progname = nil, &block); end
|
14
|
+
def warn(progname = nil, &block); end
|
15
|
+
def error(progname = nil, &block); end
|
16
|
+
def fatal(progname = nil, &block); end
|
17
|
+
end
|
18
|
+
end
|
data/lib/rack/reloader.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
|
2
|
-
#
|
2
|
+
# Rack::Reloader is subject to the terms of an MIT-style license.
|
3
|
+
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
|
3
4
|
|
4
5
|
require 'pathname'
|
5
6
|
|
@@ -92,6 +93,8 @@ module Rack
|
|
92
93
|
found, stat = safe_stat(path)
|
93
94
|
return ::File.expand_path(found), stat if found
|
94
95
|
end
|
96
|
+
|
97
|
+
return false, false
|
95
98
|
end
|
96
99
|
|
97
100
|
def safe_stat(file)
|
data/lib/rack/request.rb
CHANGED
@@ -32,6 +32,7 @@ module Rack
|
|
32
32
|
def content_type; @env['CONTENT_TYPE'] end
|
33
33
|
def session; @env['rack.session'] ||= {} end
|
34
34
|
def session_options; @env['rack.session.options'] ||= {} end
|
35
|
+
def logger; @env['rack.logger'] end
|
35
36
|
|
36
37
|
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
37
38
|
# without any media type parameters. e.g., when CONTENT_TYPE is
|
@@ -63,9 +64,17 @@ module Rack
|
|
63
64
|
media_type_params['charset']
|
64
65
|
end
|
65
66
|
|
67
|
+
def host_with_port
|
68
|
+
if forwarded = @env["HTTP_X_FORWARDED_HOST"]
|
69
|
+
forwarded.split(/,\s?/).last
|
70
|
+
else
|
71
|
+
@env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
66
75
|
def host
|
67
76
|
# Remove port number.
|
68
|
-
|
77
|
+
host_with_port.to_s.gsub(/:\d+\z/, '')
|
69
78
|
end
|
70
79
|
|
71
80
|
def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
|
@@ -81,7 +90,6 @@ module Rack
|
|
81
90
|
# one of the media types presents in this list will not be eligible
|
82
91
|
# for form-data / param parsing.
|
83
92
|
FORM_DATA_MEDIA_TYPES = [
|
84
|
-
nil,
|
85
93
|
'application/x-www-form-urlencoded',
|
86
94
|
'multipart/form-data'
|
87
95
|
]
|
@@ -92,15 +100,20 @@ module Rack
|
|
92
100
|
PARSEABLE_DATA_MEDIA_TYPES = [
|
93
101
|
'multipart/related',
|
94
102
|
'multipart/mixed'
|
95
|
-
]
|
103
|
+
]
|
96
104
|
|
97
105
|
# Determine whether the request body contains form-data by checking
|
98
|
-
# the request
|
99
|
-
# "application/x-www-form-urlencoded"
|
106
|
+
# the request Content-Type for one of the media-types:
|
107
|
+
# "application/x-www-form-urlencoded" or "multipart/form-data". The
|
100
108
|
# list of form-data media types can be modified through the
|
101
109
|
# +FORM_DATA_MEDIA_TYPES+ array.
|
110
|
+
#
|
111
|
+
# A request body is also assumed to contain form-data when no
|
112
|
+
# Content-Type header is provided and the request_method is POST.
|
102
113
|
def form_data?
|
103
|
-
|
114
|
+
type = media_type
|
115
|
+
meth = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']
|
116
|
+
(meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
104
117
|
end
|
105
118
|
|
106
119
|
# Determine whether the request body contains data by checking
|
@@ -115,8 +128,7 @@ module Rack
|
|
115
128
|
@env["rack.request.query_hash"]
|
116
129
|
else
|
117
130
|
@env["rack.request.query_string"] = query_string
|
118
|
-
@env["rack.request.query_hash"] =
|
119
|
-
Utils.parse_nested_query(query_string)
|
131
|
+
@env["rack.request.query_hash"] = parse_query(query_string)
|
120
132
|
end
|
121
133
|
end
|
122
134
|
|
@@ -125,19 +137,20 @@ module Rack
|
|
125
137
|
# This method support both application/x-www-form-urlencoded and
|
126
138
|
# multipart/form-data.
|
127
139
|
def POST
|
128
|
-
if @env["rack.
|
140
|
+
if @env["rack.input"].nil?
|
141
|
+
raise "Missing rack.input"
|
142
|
+
elsif @env["rack.request.form_input"].eql? @env["rack.input"]
|
129
143
|
@env["rack.request.form_hash"]
|
130
144
|
elsif form_data? || parseable_data?
|
131
145
|
@env["rack.request.form_input"] = @env["rack.input"]
|
132
|
-
unless @env["rack.request.form_hash"] =
|
133
|
-
Utils::Multipart.parse_multipart(env)
|
146
|
+
unless @env["rack.request.form_hash"] = parse_multipart(env)
|
134
147
|
form_vars = @env["rack.input"].read
|
135
148
|
|
136
149
|
# Fix for Safari Ajax postings that always append \0
|
137
150
|
form_vars.sub!(/\0\z/, '')
|
138
151
|
|
139
152
|
@env["rack.request.form_vars"] = form_vars
|
140
|
-
@env["rack.request.form_hash"] =
|
153
|
+
@env["rack.request.form_hash"] = parse_query(form_vars)
|
141
154
|
|
142
155
|
@env["rack.input"].rewind
|
143
156
|
end
|
@@ -149,7 +162,7 @@ module Rack
|
|
149
162
|
|
150
163
|
# The union of GET and POST data.
|
151
164
|
def params
|
152
|
-
self.
|
165
|
+
self.GET.update(self.POST)
|
153
166
|
rescue EOFError => e
|
154
167
|
self.GET
|
155
168
|
end
|
@@ -175,6 +188,9 @@ module Rack
|
|
175
188
|
end
|
176
189
|
alias referrer referer
|
177
190
|
|
191
|
+
def user_agent
|
192
|
+
@env['HTTP_USER_AGENT']
|
193
|
+
end
|
178
194
|
|
179
195
|
def cookies
|
180
196
|
return {} unless @env["HTTP_COOKIE"]
|
@@ -214,11 +230,11 @@ module Rack
|
|
214
230
|
|
215
231
|
url
|
216
232
|
end
|
217
|
-
|
233
|
+
|
218
234
|
def path
|
219
235
|
script_name + path_info
|
220
236
|
end
|
221
|
-
|
237
|
+
|
222
238
|
def fullpath
|
223
239
|
query_string.empty? ? path : "#{path}?#{query_string}"
|
224
240
|
end
|
@@ -242,5 +258,14 @@ module Rack
|
|
242
258
|
@env['REMOTE_ADDR']
|
243
259
|
end
|
244
260
|
end
|
261
|
+
|
262
|
+
protected
|
263
|
+
def parse_query(qs)
|
264
|
+
Utils.parse_nested_query(qs)
|
265
|
+
end
|
266
|
+
|
267
|
+
def parse_multipart(env)
|
268
|
+
Utils::Multipart.parse_multipart(env)
|
269
|
+
end
|
245
270
|
end
|
246
271
|
end
|
data/lib/rack/response.rb
CHANGED
@@ -19,7 +19,7 @@ module Rack
|
|
19
19
|
attr_accessor :length
|
20
20
|
|
21
21
|
def initialize(body=[], status=200, header={}, &block)
|
22
|
-
@status = status
|
22
|
+
@status = status.to_i
|
23
23
|
@header = Utils::HeaderHash.new({"Content-Type" => "text/html"}.
|
24
24
|
merge(header))
|
25
25
|
|
@@ -54,45 +54,11 @@ module Rack
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def set_cookie(key, value)
|
57
|
-
|
58
|
-
when Hash
|
59
|
-
domain = "; domain=" + value[:domain] if value[:domain]
|
60
|
-
path = "; path=" + value[:path] if value[:path]
|
61
|
-
# According to RFC 2109, we need dashes here.
|
62
|
-
# N.B.: cgi.rb uses spaces...
|
63
|
-
expires = "; expires=" + value[:expires].clone.gmtime.
|
64
|
-
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
|
65
|
-
secure = "; secure" if value[:secure]
|
66
|
-
httponly = "; HttpOnly" if value[:httponly]
|
67
|
-
value = value[:value]
|
68
|
-
end
|
69
|
-
value = [value] unless Array === value
|
70
|
-
cookie = Utils.escape(key) + "=" +
|
71
|
-
value.map { |v| Utils.escape v }.join("&") +
|
72
|
-
"#{domain}#{path}#{expires}#{secure}#{httponly}"
|
73
|
-
|
74
|
-
case self["Set-Cookie"]
|
75
|
-
when Array
|
76
|
-
self["Set-Cookie"] << cookie
|
77
|
-
when String
|
78
|
-
self["Set-Cookie"] = [self["Set-Cookie"], cookie]
|
79
|
-
when nil
|
80
|
-
self["Set-Cookie"] = cookie
|
81
|
-
end
|
57
|
+
Utils.set_cookie_header!(header, key, value)
|
82
58
|
end
|
83
59
|
|
84
60
|
def delete_cookie(key, value={})
|
85
|
-
|
86
|
-
self["Set-Cookie"] = [self["Set-Cookie"]].compact
|
87
|
-
end
|
88
|
-
|
89
|
-
self["Set-Cookie"].reject! { |cookie|
|
90
|
-
cookie =~ /\A#{Utils.escape(key)}=/
|
91
|
-
}
|
92
|
-
|
93
|
-
set_cookie(key,
|
94
|
-
{:value => '', :path => nil, :domain => nil,
|
95
|
-
:expires => Time.at(0) }.merge(value))
|
61
|
+
Utils.delete_cookie_header!(header, key, value)
|
96
62
|
end
|
97
63
|
|
98
64
|
def redirect(target, status=302)
|
@@ -105,9 +71,9 @@ module Rack
|
|
105
71
|
|
106
72
|
if [204, 304].include?(status.to_i)
|
107
73
|
header.delete "Content-Type"
|
108
|
-
[status.to_i, header
|
74
|
+
[status.to_i, header, []]
|
109
75
|
else
|
110
|
-
[status.to_i, header
|
76
|
+
[status.to_i, header, self]
|
111
77
|
end
|
112
78
|
end
|
113
79
|
alias to_a finish # For *response
|
data/lib/rack/runtime.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rack
|
2
|
+
# Sets an "X-Runtime" response header, indicating the response
|
3
|
+
# time of the request, in seconds
|
4
|
+
#
|
5
|
+
# You can put it right before the application to see the processing
|
6
|
+
# time, or before all the other middlewares to include time for them,
|
7
|
+
# too.
|
8
|
+
class Runtime
|
9
|
+
def initialize(app, name = nil)
|
10
|
+
@app = app
|
11
|
+
@header_name = "X-Runtime"
|
12
|
+
@header_name << "-#{name}" if name
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
start_time = Time.now
|
17
|
+
status, headers, body = @app.call(env)
|
18
|
+
request_time = Time.now - start_time
|
19
|
+
|
20
|
+
if !headers.has_key?(@header_name)
|
21
|
+
headers[@header_name] = "%0.6f" % request_time
|
22
|
+
end
|
23
|
+
|
24
|
+
[status, headers, body]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'rack/file'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class File #:nodoc:
|
5
|
+
alias :to_path :path
|
6
|
+
end
|
7
|
+
|
8
|
+
# = Sendfile
|
9
|
+
#
|
10
|
+
# The Sendfile middleware intercepts responses whose body is being
|
11
|
+
# served from a file and replaces it with a server specific X-Sendfile
|
12
|
+
# header. The web server is then responsible for writing the file contents
|
13
|
+
# to the client. This can dramatically reduce the amount of work required
|
14
|
+
# by the Ruby backend and takes advantage of the web servers optimized file
|
15
|
+
# delivery code.
|
16
|
+
#
|
17
|
+
# In order to take advantage of this middleware, the response body must
|
18
|
+
# respond to +to_path+ and the request must include an X-Sendfile-Type
|
19
|
+
# header. Rack::File and other components implement +to_path+ so there's
|
20
|
+
# rarely anything you need to do in your application. The X-Sendfile-Type
|
21
|
+
# header is typically set in your web servers configuration. The following
|
22
|
+
# sections attempt to document
|
23
|
+
#
|
24
|
+
# === Nginx
|
25
|
+
#
|
26
|
+
# Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
|
27
|
+
# but requires parts of the filesystem to be mapped into a private URL
|
28
|
+
# hierarachy.
|
29
|
+
#
|
30
|
+
# The following example shows the Nginx configuration required to create
|
31
|
+
# a private "/files/" area, enable X-Accel-Redirect, and pass the special
|
32
|
+
# X-Sendfile-Type and X-Accel-Mapping headers to the backend:
|
33
|
+
#
|
34
|
+
# location /files/ {
|
35
|
+
# internal;
|
36
|
+
# alias /var/www/;
|
37
|
+
# }
|
38
|
+
#
|
39
|
+
# location / {
|
40
|
+
# proxy_redirect false;
|
41
|
+
#
|
42
|
+
# proxy_set_header Host $host;
|
43
|
+
# proxy_set_header X-Real-IP $remote_addr;
|
44
|
+
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
45
|
+
#
|
46
|
+
# proxy_set_header X-Sendfile-Type X-Accel-Redirect
|
47
|
+
# proxy_set_header X-Accel-Mapping /files/=/var/www/;
|
48
|
+
#
|
49
|
+
# proxy_pass http://127.0.0.1:8080/;
|
50
|
+
# }
|
51
|
+
#
|
52
|
+
# Note that the X-Sendfile-Type header must be set exactly as shown above. The
|
53
|
+
# X-Accel-Mapping header should specify the name of the private URL pattern,
|
54
|
+
# followed by an equals sign (=), followed by the location on the file system
|
55
|
+
# that it maps to. The middleware performs a simple substitution on the
|
56
|
+
# resulting path.
|
57
|
+
#
|
58
|
+
# See Also: http://wiki.codemongers.com/NginxXSendfile
|
59
|
+
#
|
60
|
+
# === lighttpd
|
61
|
+
#
|
62
|
+
# Lighttpd has supported some variation of the X-Sendfile header for some
|
63
|
+
# time, although only recent version support X-Sendfile in a reverse proxy
|
64
|
+
# configuration.
|
65
|
+
#
|
66
|
+
# $HTTP["host"] == "example.com" {
|
67
|
+
# proxy-core.protocol = "http"
|
68
|
+
# proxy-core.balancer = "round-robin"
|
69
|
+
# proxy-core.backends = (
|
70
|
+
# "127.0.0.1:8000",
|
71
|
+
# "127.0.0.1:8001",
|
72
|
+
# ...
|
73
|
+
# )
|
74
|
+
#
|
75
|
+
# proxy-core.allow-x-sendfile = "enable"
|
76
|
+
# proxy-core.rewrite-request = (
|
77
|
+
# "X-Sendfile-Type" => (".*" => "X-Sendfile")
|
78
|
+
# )
|
79
|
+
# }
|
80
|
+
#
|
81
|
+
# See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore
|
82
|
+
#
|
83
|
+
# === Apache
|
84
|
+
#
|
85
|
+
# X-Sendfile is supported under Apache 2.x using a separate module:
|
86
|
+
#
|
87
|
+
# http://tn123.ath.cx/mod_xsendfile/
|
88
|
+
#
|
89
|
+
# Once the module is compiled and installed, you can enable it using
|
90
|
+
# XSendFile config directive:
|
91
|
+
#
|
92
|
+
# RequestHeader Set X-Sendfile-Type X-Sendfile
|
93
|
+
# ProxyPassReverse / http://localhost:8001/
|
94
|
+
# XSendFile on
|
95
|
+
|
96
|
+
class Sendfile
|
97
|
+
F = ::File
|
98
|
+
|
99
|
+
def initialize(app, variation=nil)
|
100
|
+
@app = app
|
101
|
+
@variation = variation
|
102
|
+
end
|
103
|
+
|
104
|
+
def call(env)
|
105
|
+
status, headers, body = @app.call(env)
|
106
|
+
if body.respond_to?(:to_path)
|
107
|
+
case type = variation(env)
|
108
|
+
when 'X-Accel-Redirect'
|
109
|
+
path = F.expand_path(body.to_path)
|
110
|
+
if url = map_accel_path(env, path)
|
111
|
+
headers[type] = url
|
112
|
+
body = []
|
113
|
+
else
|
114
|
+
env['rack.errors'] << "X-Accel-Mapping header missing"
|
115
|
+
end
|
116
|
+
when 'X-Sendfile', 'X-Lighttpd-Send-File'
|
117
|
+
path = F.expand_path(body.to_path)
|
118
|
+
headers[type] = path
|
119
|
+
body = []
|
120
|
+
when '', nil
|
121
|
+
else
|
122
|
+
env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
[status, headers, body]
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
def variation(env)
|
130
|
+
@variation ||
|
131
|
+
env['sendfile.type'] ||
|
132
|
+
env['HTTP_X_SENDFILE_TYPE']
|
133
|
+
end
|
134
|
+
|
135
|
+
def map_accel_path(env, file)
|
136
|
+
if mapping = env['HTTP_X_ACCEL_MAPPING']
|
137
|
+
internal, external = mapping.split('=', 2).map{ |p| p.strip }
|
138
|
+
file.sub(/^#{internal}/i, external)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|