edgar-rack 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +18 -0
- data/KNOWN-ISSUES +21 -0
- data/README +401 -0
- data/Rakefile +101 -0
- data/SPEC +171 -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 +81 -0
- data/lib/rack/auth/abstract/handler.rb +37 -0
- data/lib/rack/auth/abstract/request.rb +43 -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 +53 -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 +52 -0
- data/lib/rack/commonlogger.rb +49 -0
- data/lib/rack/conditionalget.rb +63 -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 +59 -0
- data/lib/rack/file.rb +118 -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 +90 -0
- data/lib/rack/handler/lsws.rb +61 -0
- data/lib/rack/handler/mongrel.rb +90 -0
- data/lib/rack/handler/scgi.rb +59 -0
- data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/rack/handler/thin.rb +17 -0
- data/lib/rack/handler/webrick.rb +73 -0
- data/lib/rack/head.rb +19 -0
- data/lib/rack/lint.rb +567 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/lock.rb +44 -0
- data/lib/rack/logger.rb +18 -0
- data/lib/rack/methodoverride.rb +27 -0
- data/lib/rack/mime.rb +210 -0
- data/lib/rack/mock.rb +185 -0
- data/lib/rack/nulllogger.rb +18 -0
- data/lib/rack/recursive.rb +61 -0
- data/lib/rack/reloader.rb +109 -0
- data/lib/rack/request.rb +307 -0
- data/lib/rack/response.rb +151 -0
- data/lib/rack/rewindable_input.rb +104 -0
- data/lib/rack/runtime.rb +27 -0
- data/lib/rack/sendfile.rb +139 -0
- data/lib/rack/server.rb +289 -0
- data/lib/rack/session/abstract/id.rb +348 -0
- data/lib/rack/session/cookie.rb +152 -0
- data/lib/rack/session/memcache.rb +93 -0
- data/lib/rack/session/pool.rb +79 -0
- data/lib/rack/showexceptions.rb +378 -0
- data/lib/rack/showstatus.rb +113 -0
- data/lib/rack/static.rb +53 -0
- data/lib/rack/urlmap.rb +55 -0
- data/lib/rack/utils.rb +698 -0
- data/rack.gemspec +39 -0
- data/test/cgi/lighttpd.conf +25 -0
- data/test/cgi/rackup_stub.rb +6 -0
- data/test/cgi/sample_rackup.ru +5 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test.fcgi +8 -0
- data/test/cgi/test.ru +5 -0
- data/test/gemloader.rb +6 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/fail_16384_nofile +814 -0
- data/test/multipart/file1.txt +1 -0
- data/test/multipart/filename_and_modification_param +7 -0
- data/test/multipart/filename_with_escaped_quotes +6 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
- data/test/multipart/filename_with_percent_escaped_quotes +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/semicolon +6 -0
- data/test/multipart/text +15 -0
- data/test/rackup/config.ru +31 -0
- data/test/spec_auth_basic.rb +70 -0
- data/test/spec_auth_digest.rb +241 -0
- data/test/spec_builder.rb +123 -0
- data/test/spec_cascade.rb +45 -0
- data/test/spec_cgi.rb +102 -0
- data/test/spec_chunked.rb +60 -0
- data/test/spec_commonlogger.rb +56 -0
- data/test/spec_conditionalget.rb +86 -0
- data/test/spec_config.rb +23 -0
- data/test/spec_content_length.rb +36 -0
- data/test/spec_content_type.rb +29 -0
- data/test/spec_deflater.rb +125 -0
- data/test/spec_directory.rb +57 -0
- data/test/spec_etag.rb +75 -0
- data/test/spec_fastcgi.rb +107 -0
- data/test/spec_file.rb +92 -0
- data/test/spec_handler.rb +49 -0
- data/test/spec_head.rb +30 -0
- data/test/spec_lint.rb +515 -0
- data/test/spec_lobster.rb +43 -0
- data/test/spec_lock.rb +142 -0
- data/test/spec_logger.rb +28 -0
- data/test/spec_methodoverride.rb +58 -0
- data/test/spec_mock.rb +241 -0
- data/test/spec_mongrel.rb +182 -0
- data/test/spec_nulllogger.rb +12 -0
- data/test/spec_recursive.rb +69 -0
- data/test/spec_request.rb +774 -0
- data/test/spec_response.rb +245 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_runtime.rb +39 -0
- data/test/spec_sendfile.rb +83 -0
- data/test/spec_server.rb +8 -0
- data/test/spec_session_abstract_id.rb +43 -0
- data/test/spec_session_cookie.rb +171 -0
- data/test/spec_session_memcache.rb +289 -0
- data/test/spec_session_pool.rb +200 -0
- data/test/spec_showexceptions.rb +87 -0
- data/test/spec_showstatus.rb +79 -0
- data/test/spec_static.rb +48 -0
- data/test/spec_thin.rb +86 -0
- data/test/spec_urlmap.rb +213 -0
- data/test/spec_utils.rb +678 -0
- data/test/spec_webrick.rb +141 -0
- data/test/testrequest.rb +78 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +329 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
# Rack::Response provides a convenient interface to create a Rack
|
7
|
+
# response.
|
8
|
+
#
|
9
|
+
# It allows setting of headers and cookies, and provides useful
|
10
|
+
# defaults (a OK response containing HTML).
|
11
|
+
#
|
12
|
+
# You can use Response#write to iteratively generate your response,
|
13
|
+
# but note that this is buffered by Rack::Response until you call
|
14
|
+
# +finish+. +finish+ however can take a block inside which calls to
|
15
|
+
# +write+ are syncronous with the Rack response.
|
16
|
+
#
|
17
|
+
# Your application's +call+ should end returning Response#finish.
|
18
|
+
|
19
|
+
class Response
|
20
|
+
attr_accessor :length
|
21
|
+
|
22
|
+
def initialize(body=[], status=200, header={}, &block)
|
23
|
+
@status = status.to_i
|
24
|
+
@header = Utils::HeaderHash.new("Content-Type" => "text/html").
|
25
|
+
merge(header)
|
26
|
+
|
27
|
+
@chunked = "chunked" == @header['Transfer-Encoding']
|
28
|
+
@writer = lambda { |x| @body << x }
|
29
|
+
@block = nil
|
30
|
+
@length = 0
|
31
|
+
|
32
|
+
@body = []
|
33
|
+
|
34
|
+
if body.respond_to? :to_str
|
35
|
+
write body.to_str
|
36
|
+
elsif body.respond_to?(:each)
|
37
|
+
body.each { |part|
|
38
|
+
write part.to_s
|
39
|
+
}
|
40
|
+
else
|
41
|
+
raise TypeError, "stringable or iterable required"
|
42
|
+
end
|
43
|
+
|
44
|
+
yield self if block_given?
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :header
|
48
|
+
attr_accessor :status, :body
|
49
|
+
|
50
|
+
def [](key)
|
51
|
+
header[key]
|
52
|
+
end
|
53
|
+
|
54
|
+
def []=(key, value)
|
55
|
+
header[key] = value
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_cookie(key, value)
|
59
|
+
Utils.set_cookie_header!(header, key, value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def delete_cookie(key, value={})
|
63
|
+
Utils.delete_cookie_header!(header, key, value)
|
64
|
+
end
|
65
|
+
|
66
|
+
def redirect(target, status=302)
|
67
|
+
self.status = status
|
68
|
+
self["Location"] = target
|
69
|
+
end
|
70
|
+
|
71
|
+
def finish(&block)
|
72
|
+
@block = block
|
73
|
+
|
74
|
+
if [204, 304].include?(status.to_i)
|
75
|
+
header.delete "Content-Type"
|
76
|
+
[status.to_i, header, []]
|
77
|
+
else
|
78
|
+
[status.to_i, header, self]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
alias to_a finish # For *response
|
82
|
+
alias to_ary finish # For implicit-splat on Ruby 1.9.2
|
83
|
+
|
84
|
+
def each(&callback)
|
85
|
+
@body.each(&callback)
|
86
|
+
@writer = callback
|
87
|
+
@block.call(self) if @block
|
88
|
+
end
|
89
|
+
|
90
|
+
# Append to body and update Content-Length.
|
91
|
+
#
|
92
|
+
# NOTE: Do not mix #write and direct #body access!
|
93
|
+
#
|
94
|
+
def write(str)
|
95
|
+
s = str.to_s
|
96
|
+
@length += Rack::Utils.bytesize(s) unless @chunked
|
97
|
+
@writer.call s
|
98
|
+
|
99
|
+
header["Content-Length"] = @length.to_s unless @chunked
|
100
|
+
str
|
101
|
+
end
|
102
|
+
|
103
|
+
def close
|
104
|
+
body.close if body.respond_to?(:close)
|
105
|
+
end
|
106
|
+
|
107
|
+
def empty?
|
108
|
+
@block == nil && @body.empty?
|
109
|
+
end
|
110
|
+
|
111
|
+
alias headers header
|
112
|
+
|
113
|
+
module Helpers
|
114
|
+
def invalid?; @status < 100 || @status >= 600; end
|
115
|
+
|
116
|
+
def informational?; @status >= 100 && @status < 200; end
|
117
|
+
def successful?; @status >= 200 && @status < 300; end
|
118
|
+
def redirection?; @status >= 300 && @status < 400; end
|
119
|
+
def client_error?; @status >= 400 && @status < 500; end
|
120
|
+
def server_error?; @status >= 500 && @status < 600; end
|
121
|
+
|
122
|
+
def ok?; @status == 200; end
|
123
|
+
def forbidden?; @status == 403; end
|
124
|
+
def not_found?; @status == 404; end
|
125
|
+
|
126
|
+
def redirect?; [301, 302, 303, 307].include? @status; end
|
127
|
+
|
128
|
+
# Headers
|
129
|
+
attr_reader :headers, :original_headers
|
130
|
+
|
131
|
+
def include?(header)
|
132
|
+
!!headers[header]
|
133
|
+
end
|
134
|
+
|
135
|
+
def content_type
|
136
|
+
headers["Content-Type"]
|
137
|
+
end
|
138
|
+
|
139
|
+
def content_length
|
140
|
+
cl = headers["Content-Length"]
|
141
|
+
cl ? cl.to_i : cl
|
142
|
+
end
|
143
|
+
|
144
|
+
def location
|
145
|
+
headers["Location"]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
include Helpers
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
require 'tempfile'
|
3
|
+
require 'rack/utils'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
# Class which can make any IO object rewindable, including non-rewindable ones. It does
|
7
|
+
# this by buffering the data into a tempfile, which is rewindable.
|
8
|
+
#
|
9
|
+
# rack.input is required to be rewindable, so if your input stream IO is non-rewindable
|
10
|
+
# by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class
|
11
|
+
# to easily make it rewindable.
|
12
|
+
#
|
13
|
+
# Don't forget to call #close when you're done. This frees up temporary resources that
|
14
|
+
# RewindableInput uses, though it does *not* close the original IO object.
|
15
|
+
class RewindableInput
|
16
|
+
def initialize(io)
|
17
|
+
@io = io
|
18
|
+
@rewindable_io = nil
|
19
|
+
@unlinked = false
|
20
|
+
end
|
21
|
+
|
22
|
+
def gets
|
23
|
+
make_rewindable unless @rewindable_io
|
24
|
+
@rewindable_io.gets
|
25
|
+
end
|
26
|
+
|
27
|
+
def read(*args)
|
28
|
+
make_rewindable unless @rewindable_io
|
29
|
+
@rewindable_io.read(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def each(&block)
|
33
|
+
make_rewindable unless @rewindable_io
|
34
|
+
@rewindable_io.each(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def rewind
|
38
|
+
make_rewindable unless @rewindable_io
|
39
|
+
@rewindable_io.rewind
|
40
|
+
end
|
41
|
+
|
42
|
+
# Closes this RewindableInput object without closing the originally
|
43
|
+
# wrapped IO oject. Cleans up any temporary resources that this RewindableInput
|
44
|
+
# has created.
|
45
|
+
#
|
46
|
+
# This method may be called multiple times. It does nothing on subsequent calls.
|
47
|
+
def close
|
48
|
+
if @rewindable_io
|
49
|
+
if @unlinked
|
50
|
+
@rewindable_io.close
|
51
|
+
else
|
52
|
+
@rewindable_io.close!
|
53
|
+
end
|
54
|
+
@rewindable_io = nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Ruby's Tempfile class has a bug. Subclass it and fix it.
|
61
|
+
class Tempfile < ::Tempfile
|
62
|
+
def _close
|
63
|
+
@tmpfile.close if @tmpfile
|
64
|
+
@data[1] = nil if @data
|
65
|
+
@tmpfile = nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def make_rewindable
|
70
|
+
# Buffer all data into a tempfile. Since this tempfile is private to this
|
71
|
+
# RewindableInput object, we chmod it so that nobody else can read or write
|
72
|
+
# it. On POSIX filesystems we also unlink the file so that it doesn't
|
73
|
+
# even have a file entry on the filesystem anymore, though we can still
|
74
|
+
# access it because we have the file handle open.
|
75
|
+
@rewindable_io = Tempfile.new('RackRewindableInput')
|
76
|
+
@rewindable_io.chmod(0000)
|
77
|
+
@rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
|
78
|
+
@rewindable_io.binmode
|
79
|
+
if filesystem_has_posix_semantics?
|
80
|
+
# Use ::File.unlink as 1.9.1 Tempfile has a bug where unlink closes the file!
|
81
|
+
::File.unlink @rewindable_io.path
|
82
|
+
raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
|
83
|
+
@unlinked = true
|
84
|
+
end
|
85
|
+
|
86
|
+
buffer = ""
|
87
|
+
while @io.read(1024 * 4, buffer)
|
88
|
+
entire_buffer_written_out = false
|
89
|
+
while !entire_buffer_written_out
|
90
|
+
written = @rewindable_io.write(buffer)
|
91
|
+
entire_buffer_written_out = written == Rack::Utils.bytesize(buffer)
|
92
|
+
if !entire_buffer_written_out
|
93
|
+
buffer.slice!(0 .. written - 1)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
@rewindable_io.rewind
|
98
|
+
end
|
99
|
+
|
100
|
+
def filesystem_has_posix_semantics?
|
101
|
+
RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
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,139 @@
|
|
1
|
+
require 'rack/file'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
|
5
|
+
# = Sendfile
|
6
|
+
#
|
7
|
+
# The Sendfile middleware intercepts responses whose body is being
|
8
|
+
# served from a file and replaces it with a server specific X-Sendfile
|
9
|
+
# header. The web server is then responsible for writing the file contents
|
10
|
+
# to the client. This can dramatically reduce the amount of work required
|
11
|
+
# by the Ruby backend and takes advantage of the web server's optimized file
|
12
|
+
# delivery code.
|
13
|
+
#
|
14
|
+
# In order to take advantage of this middleware, the response body must
|
15
|
+
# respond to +to_path+ and the request must include an X-Sendfile-Type
|
16
|
+
# header. Rack::File and other components implement +to_path+ so there's
|
17
|
+
# rarely anything you need to do in your application. The X-Sendfile-Type
|
18
|
+
# header is typically set in your web servers configuration. The following
|
19
|
+
# sections attempt to document
|
20
|
+
#
|
21
|
+
# === Nginx
|
22
|
+
#
|
23
|
+
# Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
|
24
|
+
# but requires parts of the filesystem to be mapped into a private URL
|
25
|
+
# hierarachy.
|
26
|
+
#
|
27
|
+
# The following example shows the Nginx configuration required to create
|
28
|
+
# a private "/files/" area, enable X-Accel-Redirect, and pass the special
|
29
|
+
# X-Sendfile-Type and X-Accel-Mapping headers to the backend:
|
30
|
+
#
|
31
|
+
# location ~ /files/(.*) {
|
32
|
+
# internal;
|
33
|
+
# alias /var/www/$1;
|
34
|
+
# }
|
35
|
+
#
|
36
|
+
# location / {
|
37
|
+
# proxy_redirect off;
|
38
|
+
#
|
39
|
+
# proxy_set_header Host $host;
|
40
|
+
# proxy_set_header X-Real-IP $remote_addr;
|
41
|
+
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
42
|
+
#
|
43
|
+
# proxy_set_header X-Sendfile-Type X-Accel-Redirect;
|
44
|
+
# proxy_set_header X-Accel-Mapping /var/www/=/files/;
|
45
|
+
#
|
46
|
+
# proxy_pass http://127.0.0.1:8080/;
|
47
|
+
# }
|
48
|
+
#
|
49
|
+
# Note that the X-Sendfile-Type header must be set exactly as shown above. The
|
50
|
+
# X-Accel-Mapping header should specify the by the location on the file system,
|
51
|
+
# followed by an equals sign (=), followed name of the private URL pattern
|
52
|
+
# that it maps to. The middleware performs a simple substitution on the
|
53
|
+
# resulting path.
|
54
|
+
#
|
55
|
+
# See Also: http://wiki.codemongers.com/NginxXSendfile
|
56
|
+
#
|
57
|
+
# === lighttpd
|
58
|
+
#
|
59
|
+
# Lighttpd has supported some variation of the X-Sendfile header for some
|
60
|
+
# time, although only recent version support X-Sendfile in a reverse proxy
|
61
|
+
# configuration.
|
62
|
+
#
|
63
|
+
# $HTTP["host"] == "example.com" {
|
64
|
+
# proxy-core.protocol = "http"
|
65
|
+
# proxy-core.balancer = "round-robin"
|
66
|
+
# proxy-core.backends = (
|
67
|
+
# "127.0.0.1:8000",
|
68
|
+
# "127.0.0.1:8001",
|
69
|
+
# ...
|
70
|
+
# )
|
71
|
+
#
|
72
|
+
# proxy-core.allow-x-sendfile = "enable"
|
73
|
+
# proxy-core.rewrite-request = (
|
74
|
+
# "X-Sendfile-Type" => (".*" => "X-Sendfile")
|
75
|
+
# )
|
76
|
+
# }
|
77
|
+
#
|
78
|
+
# See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore
|
79
|
+
#
|
80
|
+
# === Apache
|
81
|
+
#
|
82
|
+
# X-Sendfile is supported under Apache 2.x using a separate module:
|
83
|
+
#
|
84
|
+
# http://tn123.ath.cx/mod_xsendfile/
|
85
|
+
#
|
86
|
+
# Once the module is compiled and installed, you can enable it using
|
87
|
+
# XSendFile config directive:
|
88
|
+
#
|
89
|
+
# RequestHeader Set X-Sendfile-Type X-Sendfile
|
90
|
+
# ProxyPassReverse / http://localhost:8001/
|
91
|
+
# XSendFile on
|
92
|
+
|
93
|
+
class Sendfile
|
94
|
+
F = ::File
|
95
|
+
|
96
|
+
def initialize(app, variation=nil)
|
97
|
+
@app = app
|
98
|
+
@variation = variation
|
99
|
+
end
|
100
|
+
|
101
|
+
def call(env)
|
102
|
+
status, headers, body = @app.call(env)
|
103
|
+
if body.respond_to?(:to_path)
|
104
|
+
case type = variation(env)
|
105
|
+
when 'X-Accel-Redirect'
|
106
|
+
path = F.expand_path(body.to_path)
|
107
|
+
if url = map_accel_path(env, path)
|
108
|
+
headers[type] = url
|
109
|
+
body = []
|
110
|
+
else
|
111
|
+
env['rack.errors'] << "X-Accel-Mapping header missing"
|
112
|
+
end
|
113
|
+
when 'X-Sendfile', 'X-Lighttpd-Send-File'
|
114
|
+
path = F.expand_path(body.to_path)
|
115
|
+
headers[type] = path
|
116
|
+
body = []
|
117
|
+
when '', nil
|
118
|
+
else
|
119
|
+
env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
[status, headers, body]
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
def variation(env)
|
127
|
+
@variation ||
|
128
|
+
env['sendfile.type'] ||
|
129
|
+
env['HTTP_X_SENDFILE_TYPE']
|
130
|
+
end
|
131
|
+
|
132
|
+
def map_accel_path(env, file)
|
133
|
+
if mapping = env['HTTP_X_ACCEL_MAPPING']
|
134
|
+
internal, external = mapping.split('=', 2).map{ |p| p.strip }
|
135
|
+
file.sub(/^#{internal}/i, external)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/lib/rack/server.rb
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class Server
|
5
|
+
class Options
|
6
|
+
def parse!(args)
|
7
|
+
options = {}
|
8
|
+
opt_parser = OptionParser.new("", 24, ' ') do |opts|
|
9
|
+
opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
|
10
|
+
|
11
|
+
opts.separator ""
|
12
|
+
opts.separator "Ruby options:"
|
13
|
+
|
14
|
+
lineno = 1
|
15
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
|
16
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
17
|
+
lineno += 1
|
18
|
+
}
|
19
|
+
|
20
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
|
21
|
+
options[:debug] = true
|
22
|
+
}
|
23
|
+
opts.on("-w", "--warn", "turn warnings on for your script") {
|
24
|
+
options[:warn] = true
|
25
|
+
}
|
26
|
+
|
27
|
+
opts.on("-I", "--include PATH",
|
28
|
+
"specify $LOAD_PATH (may be used more than once)") { |path|
|
29
|
+
options[:include] = path.split(":")
|
30
|
+
}
|
31
|
+
|
32
|
+
opts.on("-r", "--require LIBRARY",
|
33
|
+
"require the library, before executing your script") { |library|
|
34
|
+
options[:require] = library
|
35
|
+
}
|
36
|
+
|
37
|
+
opts.separator ""
|
38
|
+
opts.separator "Rack options:"
|
39
|
+
opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
|
40
|
+
options[:server] = s
|
41
|
+
}
|
42
|
+
|
43
|
+
opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
|
44
|
+
options[:Host] = host
|
45
|
+
}
|
46
|
+
|
47
|
+
opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
|
48
|
+
options[:Port] = port
|
49
|
+
}
|
50
|
+
|
51
|
+
opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
|
52
|
+
options[:environment] = e
|
53
|
+
}
|
54
|
+
|
55
|
+
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
|
56
|
+
options[:daemonize] = d ? true : false
|
57
|
+
}
|
58
|
+
|
59
|
+
opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
|
60
|
+
options[:pid] = f
|
61
|
+
}
|
62
|
+
|
63
|
+
opts.separator ""
|
64
|
+
opts.separator "Common options:"
|
65
|
+
|
66
|
+
opts.on_tail("-h", "-?", "--help", "Show this message") do
|
67
|
+
puts opts
|
68
|
+
exit
|
69
|
+
end
|
70
|
+
|
71
|
+
opts.on_tail("--version", "Show version") do
|
72
|
+
puts "Rack #{Rack.version} (Release: #{Rack.release})"
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
begin
|
78
|
+
opt_parser.parse! args
|
79
|
+
rescue OptionParser::InvalidOption => e
|
80
|
+
warn e.message
|
81
|
+
abort opt_parser.to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
options[:config] = args.last if args.last
|
85
|
+
options
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Start a new rack server (like running rackup). This will parse ARGV and
|
90
|
+
# provide standard ARGV rackup options, defaulting to load 'config.ru'.
|
91
|
+
#
|
92
|
+
# Providing an options hash will prevent ARGV parsing and will not include
|
93
|
+
# any default options.
|
94
|
+
#
|
95
|
+
# This method can be used to very easily launch a CGI application, for
|
96
|
+
# example:
|
97
|
+
#
|
98
|
+
# Rack::Server.start(
|
99
|
+
# :app => lambda do |e|
|
100
|
+
# [200, {'Content-Type' => 'text/html'}, ['hello world']]
|
101
|
+
# end,
|
102
|
+
# :server => 'cgi'
|
103
|
+
# )
|
104
|
+
#
|
105
|
+
# Further options available here are documented on Rack::Server#initialize
|
106
|
+
def self.start(options = nil)
|
107
|
+
new(options).start
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_writer :options
|
111
|
+
|
112
|
+
# Options may include:
|
113
|
+
# * :app
|
114
|
+
# a rack application to run (overrides :config)
|
115
|
+
# * :config
|
116
|
+
# a rackup configuration file path to load (.ru)
|
117
|
+
# * :environment
|
118
|
+
# this selects the middleware that will be wrapped around
|
119
|
+
# your application. Default options available are:
|
120
|
+
# - development: CommonLogger, ShowExceptions, and Lint
|
121
|
+
# - deployment: CommonLogger
|
122
|
+
# - none: no extra middleware
|
123
|
+
# note: when the server is a cgi server, CommonLogger is not included.
|
124
|
+
# * :server
|
125
|
+
# choose a specific Rack::Handler, e.g. cgi, fcgi, webrick
|
126
|
+
# * :daemonize
|
127
|
+
# if true, the server will daemonize itself (fork, detach, etc)
|
128
|
+
# * :pid
|
129
|
+
# path to write a pid file after daemonize
|
130
|
+
# * :Host
|
131
|
+
# the host address to bind to (used by supporting Rack::Handler)
|
132
|
+
# * :Port
|
133
|
+
# the port to bind to (used by supporting Rack::Handler)
|
134
|
+
# * :AccessLog
|
135
|
+
# webrick acess log options (or supporting Rack::Handler)
|
136
|
+
# * :debug
|
137
|
+
# turn on debug output ($DEBUG = true)
|
138
|
+
# * :warn
|
139
|
+
# turn on warnings ($-w = true)
|
140
|
+
# * :include
|
141
|
+
# add given paths to $LOAD_PATH
|
142
|
+
# * :require
|
143
|
+
# require the given libraries
|
144
|
+
def initialize(options = nil)
|
145
|
+
@options = options
|
146
|
+
@app = options[:app] if options && options[:app]
|
147
|
+
end
|
148
|
+
|
149
|
+
def options
|
150
|
+
@options ||= parse_options(ARGV)
|
151
|
+
end
|
152
|
+
|
153
|
+
def default_options
|
154
|
+
{
|
155
|
+
:environment => ENV['RACK_ENV'] || "development",
|
156
|
+
:pid => nil,
|
157
|
+
:Port => 9292,
|
158
|
+
:Host => "0.0.0.0",
|
159
|
+
:AccessLog => [],
|
160
|
+
:config => "config.ru"
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
def app
|
165
|
+
@app ||= begin
|
166
|
+
if !::File.exist? options[:config]
|
167
|
+
abort "configuration #{options[:config]} not found"
|
168
|
+
end
|
169
|
+
|
170
|
+
app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
|
171
|
+
self.options.merge! options
|
172
|
+
app
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.middleware
|
177
|
+
@middleware ||= begin
|
178
|
+
m = Hash.new {|h,k| h[k] = []}
|
179
|
+
m["deployment"].concat [
|
180
|
+
[Rack::ContentLength],
|
181
|
+
[Rack::Chunked],
|
182
|
+
lambda { |server|
|
183
|
+
server.server.name =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr]
|
184
|
+
}
|
185
|
+
]
|
186
|
+
m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
|
187
|
+
m
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def middleware
|
192
|
+
self.class.middleware
|
193
|
+
end
|
194
|
+
|
195
|
+
def start
|
196
|
+
if options[:warn]
|
197
|
+
$-w = true
|
198
|
+
end
|
199
|
+
|
200
|
+
if includes = options[:include]
|
201
|
+
$LOAD_PATH.unshift(*includes)
|
202
|
+
end
|
203
|
+
|
204
|
+
if library = options[:require]
|
205
|
+
require library
|
206
|
+
end
|
207
|
+
|
208
|
+
if options[:debug]
|
209
|
+
$DEBUG = true
|
210
|
+
require 'pp'
|
211
|
+
p options[:server]
|
212
|
+
pp wrapped_app
|
213
|
+
pp app
|
214
|
+
end
|
215
|
+
|
216
|
+
# Touch the wrapped app, so that the config.ru is loaded before
|
217
|
+
# daemonization (i.e. before chdir, etc).
|
218
|
+
wrapped_app
|
219
|
+
|
220
|
+
daemonize_app if options[:daemonize]
|
221
|
+
write_pid if options[:pid]
|
222
|
+
|
223
|
+
trap(:INT) do
|
224
|
+
if server.respond_to?(:shutdown)
|
225
|
+
server.shutdown
|
226
|
+
else
|
227
|
+
exit
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
server.run wrapped_app, options
|
232
|
+
end
|
233
|
+
|
234
|
+
def server
|
235
|
+
@_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
def parse_options(args)
|
240
|
+
options = default_options
|
241
|
+
|
242
|
+
# Don't evaluate CGI ISINDEX parameters.
|
243
|
+
# http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
|
244
|
+
args.clear if ENV.include?("REQUEST_METHOD")
|
245
|
+
|
246
|
+
options.merge! opt_parser.parse! args
|
247
|
+
options[:config] = ::File.expand_path(options[:config])
|
248
|
+
ENV["RACK_ENV"] = options[:environment]
|
249
|
+
options
|
250
|
+
end
|
251
|
+
|
252
|
+
def opt_parser
|
253
|
+
Options.new
|
254
|
+
end
|
255
|
+
|
256
|
+
def build_app(app)
|
257
|
+
middleware[options[:environment]].reverse_each do |middleware|
|
258
|
+
middleware = middleware.call(self) if middleware.respond_to?(:call)
|
259
|
+
next unless middleware
|
260
|
+
klass = middleware.shift
|
261
|
+
app = klass.new(app, *middleware)
|
262
|
+
end
|
263
|
+
app
|
264
|
+
end
|
265
|
+
|
266
|
+
def wrapped_app
|
267
|
+
@wrapped_app ||= build_app app
|
268
|
+
end
|
269
|
+
|
270
|
+
def daemonize_app
|
271
|
+
if RUBY_VERSION < "1.9"
|
272
|
+
exit if fork
|
273
|
+
Process.setsid
|
274
|
+
exit if fork
|
275
|
+
Dir.chdir "/"
|
276
|
+
STDIN.reopen "/dev/null"
|
277
|
+
STDOUT.reopen "/dev/null", "a"
|
278
|
+
STDERR.reopen "/dev/null", "a"
|
279
|
+
else
|
280
|
+
Process.daemon
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def write_pid
|
285
|
+
::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
|
286
|
+
at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|