corntrace-rack-contrib 1.0.2
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/AUTHORS +26 -0
- data/COPYING +18 -0
- data/README.rdoc +87 -0
- data/Rakefile +89 -0
- data/lib/rack/contrib/accept_format.rb +46 -0
- data/lib/rack/contrib/access.rb +85 -0
- data/lib/rack/contrib/backstage.rb +20 -0
- data/lib/rack/contrib/bounce_favicon.rb +16 -0
- data/lib/rack/contrib/callbacks.rb +37 -0
- data/lib/rack/contrib/config.rb +16 -0
- data/lib/rack/contrib/cookies.rb +50 -0
- data/lib/rack/contrib/csshttprequest.rb +39 -0
- data/lib/rack/contrib/deflect.rb +137 -0
- data/lib/rack/contrib/evil.rb +12 -0
- data/lib/rack/contrib/expectation_cascade.rb +32 -0
- data/lib/rack/contrib/garbagecollector.rb +14 -0
- data/lib/rack/contrib/host_meta.rb +47 -0
- data/lib/rack/contrib/jsonp.rb +78 -0
- data/lib/rack/contrib/lighttpd_script_name_fix.rb +16 -0
- data/lib/rack/contrib/locale.rb +31 -0
- data/lib/rack/contrib/mailexceptions.rb +120 -0
- data/lib/rack/contrib/nested_params.rb +143 -0
- data/lib/rack/contrib/not_found.rb +18 -0
- data/lib/rack/contrib/post_body_content_type_parser.rb +40 -0
- data/lib/rack/contrib/proctitle.rb +30 -0
- data/lib/rack/contrib/profiler.rb +108 -0
- data/lib/rack/contrib/relative_redirect.rb +44 -0
- data/lib/rack/contrib/response_cache.rb +59 -0
- data/lib/rack/contrib/response_headers.rb +24 -0
- data/lib/rack/contrib/route_exceptions.rb +49 -0
- data/lib/rack/contrib/runtime.rb +31 -0
- data/lib/rack/contrib/sendfile.rb +142 -0
- data/lib/rack/contrib/signals.rb +63 -0
- data/lib/rack/contrib/simple_endpoint.rb +81 -0
- data/lib/rack/contrib/static_cache.rb +93 -0
- data/lib/rack/contrib/time_zone.rb +25 -0
- data/lib/rack/contrib.rb +39 -0
- data/rack-contrib.gemspec +104 -0
- data/test/404.html +1 -0
- data/test/Maintenance.html +1 -0
- data/test/documents/test +1 -0
- data/test/mail_settings.rb +12 -0
- data/test/spec_rack_accept_format.rb +72 -0
- data/test/spec_rack_access.rb +154 -0
- data/test/spec_rack_backstage.rb +26 -0
- data/test/spec_rack_callbacks.rb +65 -0
- data/test/spec_rack_config.rb +22 -0
- data/test/spec_rack_contrib.rb +8 -0
- data/test/spec_rack_cookies.rb +56 -0
- data/test/spec_rack_csshttprequest.rb +66 -0
- data/test/spec_rack_deflect.rb +107 -0
- data/test/spec_rack_evil.rb +19 -0
- data/test/spec_rack_expectation_cascade.rb +72 -0
- data/test/spec_rack_garbagecollector.rb +13 -0
- data/test/spec_rack_host_meta.rb +50 -0
- data/test/spec_rack_jsonp.rb +83 -0
- data/test/spec_rack_lighttpd_script_name_fix.rb +16 -0
- data/test/spec_rack_mailexceptions.rb +97 -0
- data/test/spec_rack_nested_params.rb +46 -0
- data/test/spec_rack_not_found.rb +17 -0
- data/test/spec_rack_post_body_content_type_parser.rb +32 -0
- data/test/spec_rack_proctitle.rb +26 -0
- data/test/spec_rack_profiler.rb +37 -0
- data/test/spec_rack_relative_redirect.rb +78 -0
- data/test/spec_rack_response_cache.rb +137 -0
- data/test/spec_rack_response_headers.rb +35 -0
- data/test/spec_rack_runtime.rb +35 -0
- data/test/spec_rack_sendfile.rb +86 -0
- data/test/spec_rack_simple_endpoint.rb +95 -0
- data/test/spec_rack_static_cache.rb +91 -0
- data/test/statics/test +1 -0
- metadata +234 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
# Rack::RelativeRedirect is a simple middleware that converts relative paths in
|
4
|
+
# redirects in absolute urls, so they conform to RFC2616. It allows the user to
|
5
|
+
# specify the absolute path to use (with a sensible default), and handles
|
6
|
+
# relative paths (those that don't start with a slash) as well.
|
7
|
+
class Rack::RelativeRedirect
|
8
|
+
SCHEME_MAP = {'http'=>'80', 'https'=>'443'}
|
9
|
+
# The default proc used if a block is not provided to .new
|
10
|
+
# Just uses the url scheme of the request and the server name.
|
11
|
+
DEFAULT_ABSOLUTE_PROC = proc do |env, res|
|
12
|
+
port = env['SERVER_PORT']
|
13
|
+
scheme = env['rack.url_scheme']
|
14
|
+
"#{scheme}://#{env['SERVER_NAME']}#{":#{port}" unless SCHEME_MAP[scheme] == port}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Initialize a new RelativeRedirect object with the given arguments. Arguments:
|
18
|
+
# * app : The next middleware in the chain. This is always called.
|
19
|
+
# * &block : If provided, it is called with the environment and the response
|
20
|
+
# from the next middleware. It should return a string representing the scheme
|
21
|
+
# and server name (such as 'http://example.org').
|
22
|
+
def initialize(app, &block)
|
23
|
+
@app = app
|
24
|
+
@absolute_proc = block || DEFAULT_ABSOLUTE_PROC
|
25
|
+
end
|
26
|
+
|
27
|
+
# Call the next middleware with the environment. If the request was a
|
28
|
+
# redirect (response status 301, 302, or 303), and the location header does
|
29
|
+
# not start with an http or https url scheme, call the block provided by new
|
30
|
+
# and use that to make the Location header an absolute url. If the Location
|
31
|
+
# does not start with a slash, make location relative to the path requested.
|
32
|
+
def call(env)
|
33
|
+
res = @app.call(env)
|
34
|
+
if [301,302,303].include?(res[0]) and loc = res[1]['Location'] and !%r{\Ahttps?://}o.match(loc)
|
35
|
+
absolute = @absolute_proc.call(env, res)
|
36
|
+
res[1]['Location'] = if %r{\A/}.match(loc)
|
37
|
+
"#{absolute}#{loc}"
|
38
|
+
else
|
39
|
+
"#{absolute}#{File.dirname(Rack::Utils.unescape(env['PATH_INFO']))}/#{loc}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
res
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
# Rack::ResponseCache is a Rack middleware that caches responses for successful
|
5
|
+
# GET requests with no query string to disk or any ruby object that has an
|
6
|
+
# []= method (so it works with memcached). When caching to disk, it works similar to
|
7
|
+
# Rails' page caching, allowing you to cache dynamic pages to static files that can
|
8
|
+
# be served directly by a front end webserver.
|
9
|
+
class Rack::ResponseCache
|
10
|
+
# The default proc used if a block is not provided to .new
|
11
|
+
# It unescapes the PATH_INFO of the environment, and makes sure that it doesn't
|
12
|
+
# include '..'. If the Content-Type of the response is text/(html|css|xml),
|
13
|
+
# return a path with the appropriate extension (.html, .css, or .xml).
|
14
|
+
# If the path ends with a / and the Content-Type is text/html, change the basename
|
15
|
+
# of the path to index.html.
|
16
|
+
DEFAULT_PATH_PROC = proc do |env, res|
|
17
|
+
path = Rack::Utils.unescape(env['PATH_INFO'])
|
18
|
+
if !path.include?('..') and match = /text\/((?:x|ht)ml|css)/o.match(res[1]['Content-Type'])
|
19
|
+
type = match[1]
|
20
|
+
path = "#{path}.#{type}" unless /\.#{type}\z/.match(path)
|
21
|
+
path = File.join(File.dirname(path), 'index.html') if type == 'html' and File.basename(path) == '.html'
|
22
|
+
path
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Initialize a new ReponseCache object with the given arguments. Arguments:
|
27
|
+
# * app : The next middleware in the chain. This is always called.
|
28
|
+
# * cache : The place to cache responses. If a string is provided, a disk
|
29
|
+
# cache is used, and all cached files will use this directory as the root directory.
|
30
|
+
# If anything other than a string is provided, it should respond to []=, which will
|
31
|
+
# be called with a path string and a body value (the 3rd element of the response).
|
32
|
+
# * &block : If provided, it is called with the environment and the response from the next middleware.
|
33
|
+
# It should return nil or false if the path should not be cached, and should return
|
34
|
+
# the pathname to use as a string if the result should be cached.
|
35
|
+
# If not provided, the DEFAULT_PATH_PROC is used.
|
36
|
+
def initialize(app, cache, &block)
|
37
|
+
@app = app
|
38
|
+
@cache = cache
|
39
|
+
@path_proc = block || DEFAULT_PATH_PROC
|
40
|
+
end
|
41
|
+
|
42
|
+
# Call the next middleware with the environment. If the request was successful (response status 200),
|
43
|
+
# was a GET request, and had an empty query string, call the block set up in initialize to get the path.
|
44
|
+
# If the cache is a string, create any necessary middle directories, and cache the file in the appropriate
|
45
|
+
# subdirectory of cache. Otherwise, cache the body of the reponse as the value with the path as the key.
|
46
|
+
def call(env)
|
47
|
+
res = @app.call(env)
|
48
|
+
if env['REQUEST_METHOD'] == 'GET' and env['QUERY_STRING'] == '' and res[0] == 200 and path = @path_proc.call(env, res)
|
49
|
+
if @cache.is_a?(String)
|
50
|
+
path = File.join(@cache, path) if @cache
|
51
|
+
FileUtils.mkdir_p(File.dirname(path))
|
52
|
+
File.open(path, 'wb'){|f| res[2].each{|c| f.write(c)}}
|
53
|
+
else
|
54
|
+
@cache[path] = res[2]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
res
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rack
|
2
|
+
# Allows you to tap into the response headers. Yields a Rack::Utils::HeaderHash
|
3
|
+
# of current response headers to the block. Example:
|
4
|
+
#
|
5
|
+
# use Rack::ResponseHeaders do |headers|
|
6
|
+
# headers['X-Foo'] = 'bar'
|
7
|
+
# headers.delete('X-Baz')
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
class ResponseHeaders
|
11
|
+
def initialize(app, &block)
|
12
|
+
@app = app
|
13
|
+
@block = block
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
response = @app.call(env)
|
18
|
+
headers = Utils::HeaderHash.new(response[1])
|
19
|
+
@block.call(headers)
|
20
|
+
response[1] = headers
|
21
|
+
response
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Rack
|
2
|
+
class RouteExceptions
|
3
|
+
ROUTES = [
|
4
|
+
[Exception, '/error/internal']
|
5
|
+
]
|
6
|
+
|
7
|
+
PATH_INFO = 'rack.route_exceptions.path_info'.freeze
|
8
|
+
EXCEPTION = 'rack.route_exceptions.exception'.freeze
|
9
|
+
RETURNED = 'rack.route_exceptions.returned'.freeze
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def route(exception, to)
|
13
|
+
ROUTES.delete_if{|k,v| k == exception }
|
14
|
+
ROUTES << [exception, to]
|
15
|
+
end
|
16
|
+
|
17
|
+
alias []= route
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(app)
|
21
|
+
@app = app
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env, try_again = true)
|
25
|
+
returned = @app.call(env)
|
26
|
+
rescue Exception => exception
|
27
|
+
raise(exception) unless try_again
|
28
|
+
|
29
|
+
ROUTES.each do |klass, to|
|
30
|
+
next unless klass === exception
|
31
|
+
return route(to, env, returned, exception)
|
32
|
+
end
|
33
|
+
|
34
|
+
raise(exception)
|
35
|
+
end
|
36
|
+
|
37
|
+
def route(to, env, returned, exception)
|
38
|
+
env.merge!(
|
39
|
+
PATH_INFO => env['PATH_INFO'],
|
40
|
+
EXCEPTION => exception,
|
41
|
+
RETURNED => returned
|
42
|
+
)
|
43
|
+
|
44
|
+
env['PATH_INFO'] = to
|
45
|
+
|
46
|
+
call(env, try_again = false)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module Rack
|
3
|
+
# Sets an "X-Runtime" response header, indicating the response
|
4
|
+
# time of the request, in seconds
|
5
|
+
#
|
6
|
+
# You can put it right before the application to see the processing
|
7
|
+
# time, or before all the other middlewares to include time for them,
|
8
|
+
# too.
|
9
|
+
class Runtime
|
10
|
+
def initialize(app, name = nil)
|
11
|
+
@app = app
|
12
|
+
@header_name = "X-Runtime"
|
13
|
+
@header_name << "-#{name}" if name
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
start_time = Time.now
|
18
|
+
status, headers, body = @app.call(env)
|
19
|
+
request_time = Time.now - start_time
|
20
|
+
|
21
|
+
if !headers.has_key?(@header_name)
|
22
|
+
headers[@header_name] = "%0.6f" % request_time
|
23
|
+
end
|
24
|
+
|
25
|
+
[status, headers, body]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
@@ -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
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Rack
|
2
|
+
# Installs signal handlers that are safely processed after a request
|
3
|
+
#
|
4
|
+
# NOTE: This middleware should not be used in a threaded environment
|
5
|
+
#
|
6
|
+
# use Rack::Signals.new do
|
7
|
+
# trap 'INT', lambda {
|
8
|
+
# puts "Exiting now"
|
9
|
+
# exit
|
10
|
+
# }
|
11
|
+
#
|
12
|
+
# trap_when_ready 'USR1', lambda {
|
13
|
+
# puts "Exiting when ready"
|
14
|
+
# exit
|
15
|
+
# }
|
16
|
+
# end
|
17
|
+
class Signals
|
18
|
+
class BodyWithCallback
|
19
|
+
def initialize(body, callback)
|
20
|
+
@body, @callback = body, callback
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&block)
|
24
|
+
@body.each(&block)
|
25
|
+
@callback.call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(app, &block)
|
30
|
+
@app = app
|
31
|
+
@processing = false
|
32
|
+
@when_ready = nil
|
33
|
+
instance_eval(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(env)
|
37
|
+
begin
|
38
|
+
@processing, @when_ready = true, nil
|
39
|
+
status, headers, body = @app.call(env)
|
40
|
+
|
41
|
+
if handler = @when_ready
|
42
|
+
body = BodyWithCallback.new(body, handler)
|
43
|
+
@when_ready = nil
|
44
|
+
end
|
45
|
+
ensure
|
46
|
+
@processing = false
|
47
|
+
end
|
48
|
+
|
49
|
+
[status, headers, body]
|
50
|
+
end
|
51
|
+
|
52
|
+
def trap_when_ready(signal, handler)
|
53
|
+
when_ready_handler = lambda { |signal|
|
54
|
+
if @processing
|
55
|
+
@when_ready = lambda { handler.call(signal) }
|
56
|
+
else
|
57
|
+
handler.call(signal)
|
58
|
+
end
|
59
|
+
}
|
60
|
+
trap(signal, when_ready_handler)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Rack
|
2
|
+
# Create simple endpoints with routing rules, similar to Sinatra actions.
|
3
|
+
#
|
4
|
+
# Simplest example:
|
5
|
+
#
|
6
|
+
# use Rack::SimpleEndpoint, '/ping_monitor' do
|
7
|
+
# 'pong'
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# The value returned from the block will be written to the response body, so
|
11
|
+
# the above example will return "pong" when the request path is /ping_monitor.
|
12
|
+
#
|
13
|
+
# HTTP verb requirements can optionally be specified:
|
14
|
+
#
|
15
|
+
# use Rack::SimpleEndpoint, '/foo' => :get do
|
16
|
+
# 'only GET requests will match'
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# use Rack::SimpleEndpoint, '/bar' => [:get, :post] do
|
20
|
+
# 'only GET and POST requests will match'
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Rack::Request and Rack::Response objects are yielded to block:
|
24
|
+
#
|
25
|
+
# use Rack::SimpleEndpoint, '/json' do |req, res|
|
26
|
+
# res['Content-Type'] = 'application/json'
|
27
|
+
# %({"foo": "#{req[:foo]}"})
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# When path is a Regexp, match data object is yielded as third argument to block
|
31
|
+
#
|
32
|
+
# use Rack::SimpleEndpoint, %r{^/(john|paul|george|ringo)} do |req, res, match|
|
33
|
+
# "Hello, #{match[1]}"
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# A :pass symbol returned from block will not return a response; control will continue down the
|
37
|
+
# Rack stack:
|
38
|
+
#
|
39
|
+
# use Rack::SimpleEndpoint, '/api_key' do |req, res|
|
40
|
+
# req.env['myapp.user'].authorized? ? '12345' : :pass
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# # Unauthorized access to /api_key will be handled by PublicApp
|
44
|
+
# run PublicApp
|
45
|
+
class SimpleEndpoint
|
46
|
+
def initialize(app, arg, &block)
|
47
|
+
@app = app
|
48
|
+
@path = extract_path(arg)
|
49
|
+
@verbs = extract_verbs(arg)
|
50
|
+
@block = block
|
51
|
+
end
|
52
|
+
|
53
|
+
def call(env)
|
54
|
+
match = match_path(env['PATH_INFO'])
|
55
|
+
if match && valid_method?(env['REQUEST_METHOD'])
|
56
|
+
req, res = Request.new(env), Response.new
|
57
|
+
body = @block.call(req, res, (match unless match == true))
|
58
|
+
body == :pass ? @app.call(env) : (res.write(body); res.finish)
|
59
|
+
else
|
60
|
+
@app.call(env)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def extract_path(arg)
|
66
|
+
arg.is_a?(Hash) ? arg.keys.first : arg
|
67
|
+
end
|
68
|
+
|
69
|
+
def extract_verbs(arg)
|
70
|
+
arg.is_a?(Hash) ? [arg.values.first].flatten.map {|verb| verb.to_s.upcase} : []
|
71
|
+
end
|
72
|
+
|
73
|
+
def match_path(path)
|
74
|
+
@path.is_a?(Regexp) ? @path.match(path.to_s) : @path == path.to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
def valid_method?(method)
|
78
|
+
@verbs.empty? || @verbs.include?(method)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Rack
|
2
|
+
|
3
|
+
#
|
4
|
+
# The Rack::StaticCache middleware automatically adds, removes and modifies
|
5
|
+
# stuffs in response headers to facilitiate client and proxy caching for static files
|
6
|
+
# that minimizes http requests and improves overall load times for second time visitors.
|
7
|
+
#
|
8
|
+
# Once a static content is stored in a client/proxy the only way to enforce the browser
|
9
|
+
# to fetch the latest content and ignore the cache is to rename the static file.
|
10
|
+
#
|
11
|
+
# Alternatively, we can add a version number into the URL to the content to bypass
|
12
|
+
# the caches. Rack::StaticCache by default handles version numbers in the filename.
|
13
|
+
# As an example,
|
14
|
+
# http://yoursite.com/images/test-1.0.0.png and http://yoursite.com/images/test-2.0.0.png
|
15
|
+
# both reffers to the same image file http://yoursite.com/images/test.png
|
16
|
+
#
|
17
|
+
# Another way to bypass the cache is adding the version number in a field-value pair in the
|
18
|
+
# URL query string. As an example, http://yoursite.com/images/test.png?v=1.0.0
|
19
|
+
# In that case, set the option :versioning to false to avoid unneccessary regexp calculations.
|
20
|
+
#
|
21
|
+
# It's better to keep the current version number in some config file and use it in every static
|
22
|
+
# content's URL. So each time we modify our static contents, we just have to change the version
|
23
|
+
# number to enforce the browser to fetch the latest content.
|
24
|
+
#
|
25
|
+
# You can use Rack::Deflater along with Rack::StaticCache for further improvements in page loading time.
|
26
|
+
#
|
27
|
+
# Examples:
|
28
|
+
# use Rack::StaticCache, :urls => ["/images", "/css", "/js", "/documents*"], :root => "statics"
|
29
|
+
# will serve all requests beginning with /images, /csss or /js from the
|
30
|
+
# directory "statics/images", "statics/css", "statics/js".
|
31
|
+
# All the files from these directories will have modified headers to enable client/proxy caching,
|
32
|
+
# except the files from the directory "documents". Append a * (star) at the end of the pattern
|
33
|
+
# if you want to disable caching for any pattern . In that case, plain static contents will be served with
|
34
|
+
# default headers.
|
35
|
+
#
|
36
|
+
# use Rack::StaticCache, :urls => ["/images"], :duration => 2, :versioning => false
|
37
|
+
# will serve all requests begining with /images under the current directory (default for the option :root
|
38
|
+
# is current directory). All the contents served will have cache expiration duration set to 2 years in headers
|
39
|
+
# (default for :duration is 1 year), and StaticCache will not compute any versioning logics (default for
|
40
|
+
# :versioning is true)
|
41
|
+
#
|
42
|
+
|
43
|
+
|
44
|
+
class StaticCache
|
45
|
+
|
46
|
+
def initialize(app, options={})
|
47
|
+
@app = app
|
48
|
+
@urls = options[:urls]
|
49
|
+
@no_cache = {}
|
50
|
+
@urls.collect! do |url|
|
51
|
+
if url =~ /\*$/
|
52
|
+
url.sub!(/\*$/, '')
|
53
|
+
@no_cache[url] = 1
|
54
|
+
end
|
55
|
+
url
|
56
|
+
end
|
57
|
+
root = options[:root] || Dir.pwd
|
58
|
+
@file_server = Rack::File.new(root)
|
59
|
+
@cache_duration = options[:duration] || 1
|
60
|
+
@versioning_enabled = true
|
61
|
+
@versioning_enabled = options[:versioning] unless options[:versioning].nil?
|
62
|
+
@duration_in_seconds = self.duration_in_seconds
|
63
|
+
@duration_in_words = self.duration_in_words
|
64
|
+
end
|
65
|
+
|
66
|
+
def call(env)
|
67
|
+
path = env["PATH_INFO"]
|
68
|
+
url = @urls.detect{ |u| path.index(u) == 0 }
|
69
|
+
unless url.nil?
|
70
|
+
path.sub!(/-[\d.]+([.][a-zA-Z][\w]+)?$/, '\1') if @versioning_enabled
|
71
|
+
status, headers, body = @file_server.call(env)
|
72
|
+
if @no_cache[url].nil?
|
73
|
+
headers['Cache-Control'] ="max-age=#{@duration_in_seconds}, public"
|
74
|
+
headers['Expires'] = @duration_in_words
|
75
|
+
headers.delete 'Etag'
|
76
|
+
headers.delete 'Pragma'
|
77
|
+
headers.delete 'Last-Modified'
|
78
|
+
end
|
79
|
+
[status, headers, body]
|
80
|
+
else
|
81
|
+
@app.call(env)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def duration_in_words
|
86
|
+
(Time.now + self.duration_in_seconds).strftime '%a, %d %b %Y %H:%M:%S GMT'
|
87
|
+
end
|
88
|
+
|
89
|
+
def duration_in_seconds
|
90
|
+
60 * 60 * 24 * 365 * @cache_duration
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rack
|
2
|
+
class TimeZone
|
3
|
+
Javascript = <<-EOJ
|
4
|
+
function setTimezoneCookie() {
|
5
|
+
var offset = (new Date()).getTimezoneOffset()
|
6
|
+
var date = new Date();
|
7
|
+
date.setTime(date.getTime()+3600000);
|
8
|
+
document.cookie = "utc_offset="+offset+"; expires="+date.toGMTString();+"; path=/";
|
9
|
+
}
|
10
|
+
EOJ
|
11
|
+
|
12
|
+
def initialize(app)
|
13
|
+
@app = app
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
request = Rack::Request.new(env)
|
18
|
+
if utc_offset = request.cookies["utc_offset"]
|
19
|
+
env["rack.timezone.utc_offset"] = -(utc_offset.to_i * 60)
|
20
|
+
end
|
21
|
+
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rack/contrib.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Contrib
|
5
|
+
def self.release
|
6
|
+
"1.0.2"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
autoload :AcceptFormat, "rack/contrib/accept_format"
|
11
|
+
autoload :Access, "rack/contrib/access"
|
12
|
+
autoload :BounceFavicon, "rack/contrib/bounce_favicon"
|
13
|
+
autoload :Cookies, "rack/contrib/cookies"
|
14
|
+
autoload :CSSHTTPRequest, "rack/contrib/csshttprequest"
|
15
|
+
autoload :Deflect, "rack/contrib/deflect"
|
16
|
+
autoload :ExpectationCascade, "rack/contrib/expectation_cascade"
|
17
|
+
autoload :GarbageCollector, "rack/contrib/garbagecollector"
|
18
|
+
autoload :JSONP, "rack/contrib/jsonp"
|
19
|
+
autoload :LighttpdScriptNameFix, "rack/contrib/lighttpd_script_name_fix"
|
20
|
+
autoload :Locale, "rack/contrib/locale"
|
21
|
+
autoload :MailExceptions, "rack/contrib/mailexceptions"
|
22
|
+
autoload :PostBodyContentTypeParser, "rack/contrib/post_body_content_type_parser"
|
23
|
+
autoload :ProcTitle, "rack/contrib/proctitle"
|
24
|
+
autoload :Profiler, "rack/contrib/profiler"
|
25
|
+
autoload :ResponseHeaders, "rack/contrib/response_headers"
|
26
|
+
autoload :Runtime, "rack/contrib/runtime"
|
27
|
+
autoload :Sendfile, "rack/contrib/sendfile"
|
28
|
+
autoload :Signals, "rack/contrib/signals"
|
29
|
+
autoload :SimpleEndpoint, "rack/contrib/simple_endpoint"
|
30
|
+
autoload :TimeZone, "rack/contrib/time_zone"
|
31
|
+
autoload :Evil, "rack/contrib/evil"
|
32
|
+
autoload :Callbacks, "rack/contrib/callbacks"
|
33
|
+
autoload :NestedParams, "rack/contrib/nested_params"
|
34
|
+
autoload :Config, "rack/contrib/config"
|
35
|
+
autoload :NotFound, "rack/contrib/not_found"
|
36
|
+
autoload :ResponseCache, "rack/contrib/response_cache"
|
37
|
+
autoload :RelativeRedirect, "rack/contrib/relative_redirect"
|
38
|
+
autoload :StaticCache, "rack/contrib/static_cache"
|
39
|
+
end
|