rtomayko-rack-contrib 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +18 -0
- data/README.rdoc +54 -0
- data/Rakefile +84 -0
- data/lib/rack/contrib.rb +18 -0
- data/lib/rack/etag.rb +20 -0
- data/lib/rack/jsonp.rb +38 -0
- data/lib/rack/lighttpd_script_name_fix.rb +16 -0
- data/lib/rack/locale.rb +31 -0
- data/lib/rack/mailexceptions.rb +120 -0
- data/lib/rack/post_body_content_type_parser.rb +40 -0
- data/lib/rack/profiler.rb +80 -0
- data/lib/rack/sendfile.rb +142 -0
- data/lib/rack/time_zone.rb +25 -0
- data/rack-contrib.gemspec +54 -0
- data/test/mail_settings.rb +12 -0
- data/test/spec_rack_contrib.rb +7 -0
- data/test/spec_rack_etag.rb +22 -0
- data/test/spec_rack_jsonp.rb +20 -0
- data/test/spec_rack_lighttpd_script_name_fix.rb +15 -0
- data/test/spec_rack_mailexceptions.rb +96 -0
- data/test/spec_rack_post_body_content_type_parser.rb +31 -0
- data/test/spec_rack_sendfile.rb +85 -0
- metadata +112 -0
data/COPYING
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2008 The Committers
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
= Contributed Rack Middleware and Utilities
|
2
|
+
|
3
|
+
This package includes a variety of add-on components for Rack, a Ruby web server
|
4
|
+
interface:
|
5
|
+
|
6
|
+
* Rack::ETag - Automatically sets the ETag header on all String bodies.
|
7
|
+
* Rack::JSONP - Adds JSON-P support by stripping out the callback param
|
8
|
+
and padding the response with the appropriate callback format.
|
9
|
+
* Rack::LighttpdScriptNameFix - Fixes how lighttpd sets the SCRIPT_NAME
|
10
|
+
and PATH_INFO variables in certain configurations.
|
11
|
+
* Rack::Locale - Detects the client locale using the Accept-Language request
|
12
|
+
header and sets a rack.locale variable in the environment.
|
13
|
+
* Rack::MailExceptions - Rescues exceptions raised from the app and
|
14
|
+
sends a useful email with the exception, stacktrace, and contents of the
|
15
|
+
environment.
|
16
|
+
* Rack::PostBodyContentTypeParser - Adds support for JSON request bodies. The
|
17
|
+
Rack parameter hash is populated by deserializing the JSON data provided in
|
18
|
+
the request body when the Content-Type is application/json.
|
19
|
+
* Rack::Profiler - Uses ruby-prof to measure request time.
|
20
|
+
* Rack::Sendfile - Enables X-Sendfile support for bodies that can be served
|
21
|
+
from file.
|
22
|
+
* Rack::TimeZone - Detects the clients timezone using JavaScript and sets
|
23
|
+
a variable in Rack's environment with the offset from UTC.
|
24
|
+
|
25
|
+
=== Use
|
26
|
+
|
27
|
+
Git is the quickest way to the rack-contrib sources:
|
28
|
+
|
29
|
+
git clone git://github.com/rtomayko/rack-contrib.git
|
30
|
+
|
31
|
+
Gems are currently available from GitHub clones:
|
32
|
+
|
33
|
+
gem install rtomayko-rack-contrib --source=http://gems.github.com/
|
34
|
+
|
35
|
+
Requiring 'rack/contrib' will add autoloads to the Rack modules for all of the
|
36
|
+
components included. The following example shows what a simple rackup
|
37
|
+
(+config.ru+) file might look like:
|
38
|
+
|
39
|
+
require 'rack'
|
40
|
+
require 'rack/contrib'
|
41
|
+
|
42
|
+
use Rack::Profiler if ENV['RACK_ENV'] == 'development'
|
43
|
+
|
44
|
+
use Rack::ETag
|
45
|
+
use Rack::MailExceptions
|
46
|
+
|
47
|
+
run theapp
|
48
|
+
|
49
|
+
=== Links
|
50
|
+
|
51
|
+
rack-contrib on GitHub:: <http://github.com/rtomayko/rack-contrib>
|
52
|
+
Rack:: <http://rack.rubyforge.org/>
|
53
|
+
Rack On GitHub:: <http://github.org/chneukirchen/rack>
|
54
|
+
rack-devel mailing list:: <http://groups.google.com/group/rack-devel>
|
data/Rakefile
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Rakefile for Rack::Contrib. -*-ruby-*-
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
desc "Run all the tests"
|
6
|
+
task :default => [:test]
|
7
|
+
|
8
|
+
desc "Generate RDox"
|
9
|
+
task "RDOX" do
|
10
|
+
sh "specrb -Ilib:test -a --rdox >RDOX"
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Run all the fast tests"
|
14
|
+
task :test do
|
15
|
+
sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Run all the tests"
|
19
|
+
task :fulltest do
|
20
|
+
sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Generate RDoc documentation"
|
24
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
25
|
+
rdoc.options << '--line-numbers' << '--inline-source' <<
|
26
|
+
'--main' << 'README' <<
|
27
|
+
'--title' << 'Rack Contrib Documentation' <<
|
28
|
+
'--charset' << 'utf-8'
|
29
|
+
rdoc.rdoc_dir = "doc"
|
30
|
+
rdoc.rdoc_files.include 'README.rdoc'
|
31
|
+
rdoc.rdoc_files.include 'RDOX'
|
32
|
+
rdoc.rdoc_files.include('lib/rack/*.rb')
|
33
|
+
rdoc.rdoc_files.include('lib/rack/*/*.rb')
|
34
|
+
end
|
35
|
+
task :rdoc => ["RDOX"]
|
36
|
+
|
37
|
+
|
38
|
+
# PACKAGING =================================================================
|
39
|
+
|
40
|
+
# load gemspec like github's gem builder to surface any SAFE issues.
|
41
|
+
require 'rubygems/specification'
|
42
|
+
$spec = eval(File.read('rack-contrib.gemspec'))
|
43
|
+
|
44
|
+
def package(ext='')
|
45
|
+
"pkg/rack-contrib-#{$spec.version}" + ext
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'Build packages'
|
49
|
+
task :package => %w[.gem .tar.gz].map {|e| package(e)}
|
50
|
+
|
51
|
+
desc 'Build and install as local gem'
|
52
|
+
task :install => package('.gem') do
|
53
|
+
sh "gem install #{package('.gem')}"
|
54
|
+
end
|
55
|
+
|
56
|
+
directory 'pkg/'
|
57
|
+
|
58
|
+
file package('.gem') => %w[pkg/ rack-contrib.gemspec] + $spec.files do |f|
|
59
|
+
sh "gem build rack-contrib.gemspec"
|
60
|
+
mv File.basename(f.name), f.name
|
61
|
+
end
|
62
|
+
|
63
|
+
file package('.tar.gz') => %w[pkg/] + $spec.files do |f|
|
64
|
+
sh "git archive --format=tar HEAD | gzip > #{f.name}"
|
65
|
+
end
|
66
|
+
|
67
|
+
# GEMSPEC ===================================================================
|
68
|
+
|
69
|
+
file 'rack-contrib.gemspec' => FileList['{lib,test}/**','Rakefile', 'README.rdoc'] do |f|
|
70
|
+
# read spec file and split out manifest section
|
71
|
+
spec = File.read(f.name)
|
72
|
+
parts = spec.split(" # = MANIFEST =\n")
|
73
|
+
fail 'bad spec' if parts.length != 3
|
74
|
+
# determine file list from git ls-files
|
75
|
+
files = `git ls-files`.
|
76
|
+
split("\n").sort.reject{ |file| file =~ /^\./ }.
|
77
|
+
map{ |file| " #{file}" }.join("\n")
|
78
|
+
# piece file back together and write...
|
79
|
+
parts[1] = " s.files = %w[\n#{files}\n ]\n"
|
80
|
+
spec = parts.join(" # = MANIFEST =\n")
|
81
|
+
spec.sub!(/s.date = '.*'/, "s.date = '#{Time.now.strftime("%Y-%m-%d")}'")
|
82
|
+
File.open(f.name, 'w') { |io| io.write(spec) }
|
83
|
+
puts "updated #{f.name}"
|
84
|
+
end
|
data/lib/rack/contrib.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rack'
|
2
|
+
module Rack
|
3
|
+
module Contrib
|
4
|
+
def self.release
|
5
|
+
"0.4"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
autoload :ETag, "rack/etag"
|
10
|
+
autoload :JSONP, "rack/jsonp"
|
11
|
+
autoload :LighttpdScriptNameFix, "rack/lighttpd_script_name_fix"
|
12
|
+
autoload :Locale, "rack/locale"
|
13
|
+
autoload :MailExceptions, "rack/mailexceptions"
|
14
|
+
autoload :PostBodyContentTypeParser, "rack/post_body_content_type_parser"
|
15
|
+
autoload :Profiler, "rack/profiler"
|
16
|
+
autoload :Sendfile, "rack/sendfile"
|
17
|
+
autoload :TimeZone, "rack/time_zone"
|
18
|
+
end
|
data/lib/rack/etag.rb
ADDED
@@ -0,0 +1,20 @@
|
|
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') && body.is_a?(String)
|
14
|
+
headers['ETag'] = %("#{Digest::MD5.hexdigest(body)}")
|
15
|
+
end
|
16
|
+
|
17
|
+
[status, headers, body]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/rack/jsonp.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Rack
|
2
|
+
|
3
|
+
# A Rack middleware for providing JSON-P support.
|
4
|
+
#
|
5
|
+
# Full credit to Flinn Mueller (http://actsasflinn.com/) for this contribution.
|
6
|
+
#
|
7
|
+
class JSONP
|
8
|
+
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
# Proxies the request to the application, stripping out the JSON-P callback
|
14
|
+
# method and padding the response with the appropriate callback format.
|
15
|
+
#
|
16
|
+
# Changes nothing if no <tt>callback</tt> param is specified.
|
17
|
+
#
|
18
|
+
def call(env)
|
19
|
+
status, headers, response = @app.call(env)
|
20
|
+
request = Rack::Request.new(env)
|
21
|
+
response = pad(request.params.delete('callback'), response) if request.params.include?('callback')
|
22
|
+
[status, headers, response]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Pads the response with the appropriate callback format according to the
|
26
|
+
# JSON-P spec/requirements.
|
27
|
+
#
|
28
|
+
# The Rack response spec indicates that it should be enumerable. The method
|
29
|
+
# of combining all of the data into a sinle string makes sense since JSON
|
30
|
+
# is returned as a full string.
|
31
|
+
#
|
32
|
+
def pad(callback, response, body = "")
|
33
|
+
response.each{ |s| body << s }
|
34
|
+
"#{callback}(#{body})"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Rack
|
2
|
+
# Lighttpd sets the wrong SCRIPT_NAME and PATH_INFO if you mount your
|
3
|
+
# FastCGI app at "/". This middleware fixes this issue.
|
4
|
+
|
5
|
+
class LighttpdScriptNameFix
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
env["PATH_INFO"] = env["SCRIPT_NAME"].to_s + env["PATH_INFO"].to_s
|
12
|
+
env["SCRIPT_NAME"] = ""
|
13
|
+
@app.call(env)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/rack/locale.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class Locale
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
old_locale = I18n.locale
|
11
|
+
locale = nil
|
12
|
+
|
13
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
|
14
|
+
if lang = env["HTTP_ACCEPT_LANGUAGE"]
|
15
|
+
lang = lang.split(",").map { |l|
|
16
|
+
l += ';q=1.0' unless l =~ /;q=\d+\.\d+$/
|
17
|
+
l.split(';q=')
|
18
|
+
}.first
|
19
|
+
locale = lang.first.split("-").first
|
20
|
+
else
|
21
|
+
locale = I18n.default_locale
|
22
|
+
end
|
23
|
+
|
24
|
+
locale = env['rack.locale'] = I18n.locale = locale.to_s
|
25
|
+
status, headers, body = @app.call(env)
|
26
|
+
headers['Content-Language'] = locale
|
27
|
+
I18n.locale = old_locale
|
28
|
+
[status, headers, body]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'net/smtp'
|
2
|
+
require 'tmail'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
# Catches all exceptions raised from the app it wraps and
|
7
|
+
# sends a useful email with the exception, stacktrace, and
|
8
|
+
# contents of the environment.
|
9
|
+
|
10
|
+
class MailExceptions
|
11
|
+
attr_reader :config
|
12
|
+
|
13
|
+
def initialize(app)
|
14
|
+
@app = app
|
15
|
+
@config = {
|
16
|
+
:to => nil,
|
17
|
+
:from => ENV['USER'] || 'rack',
|
18
|
+
:subject => '[exception] %s',
|
19
|
+
:smtp => {
|
20
|
+
:server => 'localhost',
|
21
|
+
:domain => 'localhost',
|
22
|
+
:port => 25,
|
23
|
+
:authentication => :login,
|
24
|
+
:user_name => nil,
|
25
|
+
:password => nil
|
26
|
+
}
|
27
|
+
}
|
28
|
+
@template = ERB.new(TEMPLATE)
|
29
|
+
yield self if block_given?
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(env)
|
33
|
+
status, headers, body =
|
34
|
+
begin
|
35
|
+
@app.call(env)
|
36
|
+
rescue => boom
|
37
|
+
# TODO don't allow exceptions from send_notification to
|
38
|
+
# propogate
|
39
|
+
send_notification boom, env
|
40
|
+
raise
|
41
|
+
end
|
42
|
+
send_notification env['mail.exception'], env if env['mail.exception']
|
43
|
+
[status, headers, body]
|
44
|
+
end
|
45
|
+
|
46
|
+
%w[to from subject].each do |meth|
|
47
|
+
define_method(meth) { |value| @config[meth.to_sym] = value }
|
48
|
+
end
|
49
|
+
|
50
|
+
def smtp(settings={})
|
51
|
+
@config[:smtp].merge! settings
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def generate_mail(exception, env)
|
56
|
+
mail = TMail::Mail.new
|
57
|
+
mail.to = Array(config[:to])
|
58
|
+
mail.from = config[:from]
|
59
|
+
mail.subject = config[:subject] % [exception.to_s]
|
60
|
+
mail.date = Time.now
|
61
|
+
mail.set_content_type 'text/plain'
|
62
|
+
mail.charset = 'UTF-8'
|
63
|
+
mail.body = @template.result(binding)
|
64
|
+
mail
|
65
|
+
end
|
66
|
+
|
67
|
+
def send_notification(exception, env)
|
68
|
+
mail = generate_mail(exception, env)
|
69
|
+
smtp = config[:smtp]
|
70
|
+
env['mail.sent'] = true
|
71
|
+
return if smtp[:server] == 'example.com'
|
72
|
+
|
73
|
+
Net::SMTP.start smtp[:address], smtp[:port], smtp[:domain], smtp[:user_name], smtp[:password], smtp[:authentication] do |server|
|
74
|
+
mail.to.each do |recipient|
|
75
|
+
server.send_message mail.to_s, mail.from, recipient
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def extract_body(env)
|
81
|
+
if io = env['rack.input']
|
82
|
+
io.rewind if io.respond_to?(:rewind)
|
83
|
+
io.read
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
TEMPLATE = (<<-'EMAIL').gsub(/^ {4}/, '')
|
88
|
+
A <%= exception.class.to_s %> occured: <%= exception.to_s %>
|
89
|
+
<% if body = extract_body(env) %>
|
90
|
+
|
91
|
+
===================================================================
|
92
|
+
Request Body:
|
93
|
+
===================================================================
|
94
|
+
|
95
|
+
<%= body.gsub(/^/, ' ') %>
|
96
|
+
<% end %>
|
97
|
+
|
98
|
+
===================================================================
|
99
|
+
Rack Environment:
|
100
|
+
===================================================================
|
101
|
+
|
102
|
+
PID: <%= $$ %>
|
103
|
+
PWD: <%= Dir.getwd %>
|
104
|
+
|
105
|
+
<%= env.to_a.
|
106
|
+
sort{|a,b| a.first <=> b.first}.
|
107
|
+
map{ |k,v| "%-25s%p" % [k+':', v] }.
|
108
|
+
join("\n ") %>
|
109
|
+
|
110
|
+
<% if exception.respond_to?(:backtrace) %>
|
111
|
+
===================================================================
|
112
|
+
Backtrace:
|
113
|
+
===================================================================
|
114
|
+
|
115
|
+
<%= exception.backtrace.join("\n ") %>
|
116
|
+
<% end %>
|
117
|
+
EMAIL
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
begin
|
2
|
+
require 'json'
|
3
|
+
rescue LoadError => e
|
4
|
+
require 'json/pure'
|
5
|
+
end
|
6
|
+
|
7
|
+
module Rack
|
8
|
+
|
9
|
+
# A Rack middleware for parsing POST/PUT body data when Content-Type is
|
10
|
+
# not one of the standard supported types, like <tt>application/json</tt>.
|
11
|
+
#
|
12
|
+
# TODO: Find a better name.
|
13
|
+
#
|
14
|
+
class PostBodyContentTypeParser
|
15
|
+
|
16
|
+
# Constants
|
17
|
+
#
|
18
|
+
CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
19
|
+
POST_BODY = 'rack.input'.freeze
|
20
|
+
FORM_INPUT = 'rack.request.form_input'.freeze
|
21
|
+
FORM_HASH = 'rack.request.form_hash'.freeze
|
22
|
+
|
23
|
+
# Supported Content-Types
|
24
|
+
#
|
25
|
+
APPLICATION_JSON = 'application/json'.freeze
|
26
|
+
|
27
|
+
def initialize(app)
|
28
|
+
@app = app
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(env)
|
32
|
+
case env[CONTENT_TYPE]
|
33
|
+
when APPLICATION_JSON
|
34
|
+
env.update(FORM_HASH => JSON.parse(env[POST_BODY].read), FORM_INPUT => env[POST_BODY])
|
35
|
+
end
|
36
|
+
@app.call(env)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
gem 'ruby-prof', '>= 0.7.1'
|
2
|
+
require 'ruby-prof'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
# Set the profile=process_time query parameter to download a
|
7
|
+
# calltree profile of the request.
|
8
|
+
class Profiler
|
9
|
+
MODES = %w(
|
10
|
+
process_time
|
11
|
+
wall_time
|
12
|
+
cpu_time
|
13
|
+
allocations
|
14
|
+
memory
|
15
|
+
gc_runs
|
16
|
+
gc_time
|
17
|
+
).to_set
|
18
|
+
|
19
|
+
PRINTER_CONTENT_TYPE = {
|
20
|
+
RubyProf::FlatPrinter => 'text/plain',
|
21
|
+
RubyProf::GraphPrinter => 'text/plain',
|
22
|
+
RubyProf::GraphHtmlPrinter => 'text/html',
|
23
|
+
RubyProf::CallTreePrinter => 'application/octet-stream'
|
24
|
+
}
|
25
|
+
|
26
|
+
def initialize(app, printer = RubyProf::GraphHtmlPrinter)
|
27
|
+
@app = app
|
28
|
+
@printer = printer
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(env)
|
32
|
+
if mode = profiling?(env)
|
33
|
+
profile(env, mode)
|
34
|
+
else
|
35
|
+
@app.call(env)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def profiling?(env)
|
41
|
+
unless RubyProf.running?
|
42
|
+
request = Rack::Request.new(env)
|
43
|
+
if mode = request.params.delete('profile')
|
44
|
+
if MODES.include?(mode)
|
45
|
+
mode
|
46
|
+
else
|
47
|
+
env['rack.errors'].write "Invalid RubyProf measure_mode: " +
|
48
|
+
"#{mode}. Use one of #{MODES.to_a.join(', ')}"
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def profile(env, mode)
|
56
|
+
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
|
57
|
+
result = RubyProf.profile { @app.call(env) }
|
58
|
+
headers = headers(@printer, env, mode)
|
59
|
+
body = print(@printer, result)
|
60
|
+
[200, headers, body]
|
61
|
+
end
|
62
|
+
|
63
|
+
def print(printer, result)
|
64
|
+
body = StringIO.new
|
65
|
+
printer.new(result).print(body, :min_percent => 0.01)
|
66
|
+
body.rewind
|
67
|
+
body
|
68
|
+
end
|
69
|
+
|
70
|
+
def headers(printer, env, mode)
|
71
|
+
headers = { 'Content-Type' => PRINTER_CONTENT_TYPE[printer] }
|
72
|
+
if printer == RubyProf::CallTreePrinter
|
73
|
+
filename = ::File.basename(env['PATH_INFO'])
|
74
|
+
headers['Content-Disposition'] = "attachment; " +
|
75
|
+
"filename=\"#{filename}.#{mode}.tree\")"
|
76
|
+
end
|
77
|
+
headers
|
78
|
+
end
|
79
|
+
end
|
80
|
+
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
|
@@ -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
|
@@ -0,0 +1,54 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
|
5
|
+
s.name = 'rack-contrib'
|
6
|
+
s.version = '0.4.0'
|
7
|
+
s.date = '2008-12-09'
|
8
|
+
|
9
|
+
s.description = "Contributed Rack Middleware and Utilities"
|
10
|
+
s.summary = "Contributed Rack Middleware and Utilities"
|
11
|
+
|
12
|
+
s.authors = ["rack-devel"]
|
13
|
+
s.email = "rack-devel@googlegroups.com"
|
14
|
+
|
15
|
+
# = MANIFEST =
|
16
|
+
s.files = %w[
|
17
|
+
COPYING
|
18
|
+
README.rdoc
|
19
|
+
Rakefile
|
20
|
+
lib/rack/contrib.rb
|
21
|
+
lib/rack/etag.rb
|
22
|
+
lib/rack/jsonp.rb
|
23
|
+
lib/rack/lighttpd_script_name_fix.rb
|
24
|
+
lib/rack/locale.rb
|
25
|
+
lib/rack/mailexceptions.rb
|
26
|
+
lib/rack/post_body_content_type_parser.rb
|
27
|
+
lib/rack/profiler.rb
|
28
|
+
lib/rack/sendfile.rb
|
29
|
+
lib/rack/time_zone.rb
|
30
|
+
rack-contrib.gemspec
|
31
|
+
test/mail_settings.rb
|
32
|
+
test/spec_rack_contrib.rb
|
33
|
+
test/spec_rack_etag.rb
|
34
|
+
test/spec_rack_jsonp.rb
|
35
|
+
test/spec_rack_lighttpd_script_name_fix.rb
|
36
|
+
test/spec_rack_mailexceptions.rb
|
37
|
+
test/spec_rack_post_body_content_type_parser.rb
|
38
|
+
test/spec_rack_sendfile.rb
|
39
|
+
]
|
40
|
+
# = MANIFEST =
|
41
|
+
|
42
|
+
s.test_files = s.files.select {|path| path =~ /^test\/spec_.*\.rb/}
|
43
|
+
|
44
|
+
s.extra_rdoc_files = %w[README.rdoc COPYING]
|
45
|
+
s.add_dependency 'rack', '~> 0.4'
|
46
|
+
s.add_dependency 'tmail', '>= 1.2'
|
47
|
+
s.add_dependency 'json', '>= 1.1'
|
48
|
+
|
49
|
+
s.has_rdoc = true
|
50
|
+
s.homepage = "http://github.com/rtomayko/rack-contrib/"
|
51
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "rack-contrib", "--main", "README"]
|
52
|
+
s.require_paths = %w[lib]
|
53
|
+
s.rubygems_version = '1.1.1'
|
54
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
TEST_SMTP = nil
|
2
|
+
|
3
|
+
# Enable SMTP tests by providing the following for your SMTP server.
|
4
|
+
#
|
5
|
+
# TEST_SMTP = {
|
6
|
+
# :server => 'localhost',
|
7
|
+
# :domain => 'localhost',
|
8
|
+
# :port => 25,
|
9
|
+
# :authentication => :login,
|
10
|
+
# :user_name => nil,
|
11
|
+
# :password => nil
|
12
|
+
# }
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rack/mock'
|
2
|
+
require 'rack/etag'
|
3
|
+
|
4
|
+
context "Rack::ETag" do
|
5
|
+
specify "sets ETag if none is set" do
|
6
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
|
7
|
+
response = Rack::ETag.new(app).call({})
|
8
|
+
response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
|
9
|
+
end
|
10
|
+
|
11
|
+
specify "does not change ETag if it is already set" do
|
12
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, "Hello, World!"] }
|
13
|
+
response = Rack::ETag.new(app).call({})
|
14
|
+
response[1]['ETag'].should.equal "\"abc\""
|
15
|
+
end
|
16
|
+
|
17
|
+
specify "does not set ETag if steaming body" do
|
18
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello", "World"]] }
|
19
|
+
response = Rack::ETag.new(app).call({})
|
20
|
+
response[1]['ETag'].should.equal nil
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rack/mock'
|
2
|
+
require 'rack/jsonp'
|
3
|
+
|
4
|
+
context "Rack::JSONP" do
|
5
|
+
|
6
|
+
specify "should wrap the response body in the Javascript callback when provided" do
|
7
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, '{"bar":"foo"}'] }
|
8
|
+
request = Rack::MockRequest.env_for("/", :input => "foo=bar&callback=foo")
|
9
|
+
body = Rack::JSONP.new(app).call(request).last
|
10
|
+
body.should == 'foo({"bar":"foo"})'
|
11
|
+
end
|
12
|
+
|
13
|
+
specify "should not change anything if no :callback param is provided" do
|
14
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, '{"bar":"foo"}'] }
|
15
|
+
request = Rack::MockRequest.env_for("/", :input => "foo=bar")
|
16
|
+
body = Rack::JSONP.new(app).call(request).last
|
17
|
+
body.should == '{"bar":"foo"}'
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rack/mock'
|
2
|
+
require 'rack/lighttpd_script_name_fix'
|
3
|
+
|
4
|
+
context "Rack::LighttpdScriptNameFix" do
|
5
|
+
specify "corrects SCRIPT_NAME and PATH_INFO set by lighttpd " do
|
6
|
+
env = {
|
7
|
+
"PATH_INFO" => "/foo/bar/baz",
|
8
|
+
"SCRIPT_NAME" => "/hello"
|
9
|
+
}
|
10
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
|
11
|
+
response = Rack::LighttpdScriptNameFix.new(app).call(env)
|
12
|
+
env['SCRIPT_NAME'].should.be.empty
|
13
|
+
env['PATH_INFO'].should.equal '/hello/foo/bar/baz'
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'rack/mock'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'tmail'
|
5
|
+
require 'rack/mailexceptions'
|
6
|
+
|
7
|
+
require File.dirname(__FILE__) + '/mail_settings.rb'
|
8
|
+
|
9
|
+
class TestError < RuntimeError
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_exception
|
13
|
+
raise TestError, 'Suffering Succotash!'
|
14
|
+
rescue => boom
|
15
|
+
return boom
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'Rack::MailExceptions' do
|
19
|
+
|
20
|
+
setup do
|
21
|
+
@app = lambda { |env| raise TestError, 'Why, I say' }
|
22
|
+
@env = Rack::MockRequest.env_for("/foo",
|
23
|
+
'FOO' => 'BAR',
|
24
|
+
:method => 'GET',
|
25
|
+
:input => 'THE BODY'
|
26
|
+
)
|
27
|
+
@smtp_settings = {
|
28
|
+
:server => 'example.com',
|
29
|
+
:domain => 'example.com',
|
30
|
+
:port => 500,
|
31
|
+
:authentication => :login,
|
32
|
+
:user_name => 'joe',
|
33
|
+
:password => 'secret'
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
specify 'yields a configuration object to the block when created' do
|
38
|
+
called = false
|
39
|
+
mailer =
|
40
|
+
Rack::MailExceptions.new(@app) do |mail|
|
41
|
+
called = true
|
42
|
+
mail.to 'foo@example.org'
|
43
|
+
mail.from 'bar@example.org'
|
44
|
+
mail.subject '[ERROR] %s'
|
45
|
+
mail.smtp @smtp_settings
|
46
|
+
end
|
47
|
+
called.should.be == true
|
48
|
+
end
|
49
|
+
|
50
|
+
specify 'generates a TMail object with configured settings' do
|
51
|
+
mailer =
|
52
|
+
Rack::MailExceptions.new(@app) do |mail|
|
53
|
+
mail.to 'foo@example.org'
|
54
|
+
mail.from 'bar@example.org'
|
55
|
+
mail.subject '[ERROR] %s'
|
56
|
+
mail.smtp @smtp_settings
|
57
|
+
end
|
58
|
+
|
59
|
+
tmail = mailer.send(:generate_mail, test_exception, @env)
|
60
|
+
tmail.to.should.equal ['foo@example.org']
|
61
|
+
tmail.from.should.equal ['bar@example.org']
|
62
|
+
tmail.subject.should.equal '[ERROR] Suffering Succotash!'
|
63
|
+
tmail.body.should.not.be.nil
|
64
|
+
tmail.body.should.be =~ /FOO:\s+"BAR"/
|
65
|
+
tmail.body.should.be =~ /^\s*THE BODY\s*$/
|
66
|
+
end
|
67
|
+
|
68
|
+
specify 'catches exceptions raised from app, sends mail, and re-raises' do
|
69
|
+
mailer =
|
70
|
+
Rack::MailExceptions.new(@app) do |mail|
|
71
|
+
mail.to 'foo@example.org'
|
72
|
+
mail.from 'bar@example.org'
|
73
|
+
mail.subject '[ERROR] %s'
|
74
|
+
mail.smtp @smtp_settings
|
75
|
+
end
|
76
|
+
lambda { mailer.call(@env) }.should.raise(TestError)
|
77
|
+
@env['mail.sent'].should.be == true
|
78
|
+
end
|
79
|
+
|
80
|
+
if TEST_SMTP && ! TEST_SMTP.empty?
|
81
|
+
specify 'sends mail' do
|
82
|
+
mailer =
|
83
|
+
Rack::MailExceptions.new(@app) do |mail|
|
84
|
+
mail.config.merge! TEST_SMTP
|
85
|
+
end
|
86
|
+
lambda { mailer.call(@env) }.should.raise(TestError)
|
87
|
+
@env['mail.sent'].should.be == true
|
88
|
+
end
|
89
|
+
else
|
90
|
+
STDERR.puts 'WARN: Skipping SMTP tests (edit test/mail_settings.rb to enable)'
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
rescue LoadError => boom
|
95
|
+
STDERR.puts "WARN: Skipping Rack::MailExceptions tests (tmail not installed)"
|
96
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rack/mock'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rack/post_body_content_type_parser'
|
5
|
+
|
6
|
+
context "Rack::PostBodyContentTypeParser" do
|
7
|
+
|
8
|
+
specify "should handle requests with POST body Content-Type of application/json" do
|
9
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, Rack::Request.new(env).POST] }
|
10
|
+
env = env_for_post_with_headers('/', {'Content_Type'.upcase => 'application/json'}, {:body => "asdf", :status => "12"}.to_json)
|
11
|
+
body = Rack::PostBodyContentTypeParser.new(app).call(env).last
|
12
|
+
body['body'].should.equal "asdf"
|
13
|
+
body['status'].should.equal "12"
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "should change nothing when the POST body content type isn't application/json" do
|
17
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, Rack::Request.new(env).POST] }
|
18
|
+
body = app.call(Rack::MockRequest.env_for("/", :input => "body=asdf&status=12")).last
|
19
|
+
body['body'].should.equal "asdf"
|
20
|
+
body['status'].should.equal "12"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def env_for_post_with_headers(path, headers, body)
|
26
|
+
Rack::MockRequest.env_for(path, {:method => "POST", :input => body}.merge(headers))
|
27
|
+
end
|
28
|
+
rescue LoadError => e
|
29
|
+
# Missing dependency JSON, skipping tests.
|
30
|
+
STDERR.puts "WARN: Skipping Rack::PostBodyContentTypeParser tests (json not installed)"
|
31
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'rack/mock'
|
2
|
+
require 'rack/sendfile'
|
3
|
+
|
4
|
+
context "Rack::File" do
|
5
|
+
specify "should respond to #to_path" do
|
6
|
+
Rack::File.new(Dir.pwd).should.respond_to :to_path
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context "Rack::Sendfile" do
|
11
|
+
def sendfile_body
|
12
|
+
res = ['Hello World']
|
13
|
+
def res.to_path ; "/tmp/hello.txt" ; end
|
14
|
+
res
|
15
|
+
end
|
16
|
+
|
17
|
+
def simple_app(body=sendfile_body)
|
18
|
+
lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
|
19
|
+
end
|
20
|
+
|
21
|
+
def sendfile_app(body=sendfile_body)
|
22
|
+
Rack::Sendfile.new(simple_app(body))
|
23
|
+
end
|
24
|
+
|
25
|
+
setup do
|
26
|
+
@request = Rack::MockRequest.new(sendfile_app)
|
27
|
+
end
|
28
|
+
|
29
|
+
def request(headers={})
|
30
|
+
yield @request.get('/', headers)
|
31
|
+
end
|
32
|
+
|
33
|
+
specify "does nothing when no X-Sendfile-Type header present" do
|
34
|
+
request do |response|
|
35
|
+
response.should.be.ok
|
36
|
+
response.body.should.equal 'Hello World'
|
37
|
+
response.headers.should.not.include 'X-Sendfile'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
specify "sets X-Sendfile response header and discards body" do
|
42
|
+
request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
|
43
|
+
response.should.be.ok
|
44
|
+
response.body.should.be.empty
|
45
|
+
response.headers['X-Sendfile'].should.equal '/tmp/hello.txt'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
specify "sets X-Lighttpd-Send-File response header and discards body" do
|
50
|
+
request 'HTTP_X_SENDFILE_TYPE' => 'X-Lighttpd-Send-File' do |response|
|
51
|
+
response.should.be.ok
|
52
|
+
response.body.should.be.empty
|
53
|
+
response.headers['X-Lighttpd-Send-File'].should.equal '/tmp/hello.txt'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
specify "sets X-Accel-Redirect response header and discards body" do
|
58
|
+
headers = {
|
59
|
+
'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect',
|
60
|
+
'HTTP_X_ACCEL_MAPPING' => '/tmp/=/foo/bar/'
|
61
|
+
}
|
62
|
+
request headers do |response|
|
63
|
+
response.should.be.ok
|
64
|
+
response.body.should.be.empty
|
65
|
+
response.headers['X-Accel-Redirect'].should.equal '/foo/bar/hello.txt'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
specify 'writes to rack.error when no X-Accel-Mapping is specified' do
|
70
|
+
request 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' do |response|
|
71
|
+
response.should.be.ok
|
72
|
+
response.body.should.equal 'Hello World'
|
73
|
+
response.headers.should.not.include 'X-Accel-Redirect'
|
74
|
+
response.errors.should.include 'X-Accel-Mapping'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
specify 'does nothing when body does not respond to #to_path' do
|
79
|
+
@request = Rack::MockRequest.new(sendfile_app(['Not a file...']))
|
80
|
+
request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
|
81
|
+
response.body.should.equal 'Not a file...'
|
82
|
+
response.headers.should.not.include 'X-Sendfile'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rtomayko-rack-contrib
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- rack-devel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-09 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0.4"
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: tmail
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "1.2"
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: json
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: "1.1"
|
41
|
+
version:
|
42
|
+
description: Contributed Rack Middleware and Utilities
|
43
|
+
email: rack-devel@googlegroups.com
|
44
|
+
executables: []
|
45
|
+
|
46
|
+
extensions: []
|
47
|
+
|
48
|
+
extra_rdoc_files:
|
49
|
+
- README.rdoc
|
50
|
+
- COPYING
|
51
|
+
files:
|
52
|
+
- COPYING
|
53
|
+
- README.rdoc
|
54
|
+
- Rakefile
|
55
|
+
- lib/rack/contrib.rb
|
56
|
+
- lib/rack/etag.rb
|
57
|
+
- lib/rack/jsonp.rb
|
58
|
+
- lib/rack/lighttpd_script_name_fix.rb
|
59
|
+
- lib/rack/locale.rb
|
60
|
+
- lib/rack/mailexceptions.rb
|
61
|
+
- lib/rack/post_body_content_type_parser.rb
|
62
|
+
- lib/rack/profiler.rb
|
63
|
+
- lib/rack/sendfile.rb
|
64
|
+
- lib/rack/time_zone.rb
|
65
|
+
- rack-contrib.gemspec
|
66
|
+
- test/mail_settings.rb
|
67
|
+
- test/spec_rack_contrib.rb
|
68
|
+
- test/spec_rack_etag.rb
|
69
|
+
- test/spec_rack_jsonp.rb
|
70
|
+
- test/spec_rack_lighttpd_script_name_fix.rb
|
71
|
+
- test/spec_rack_mailexceptions.rb
|
72
|
+
- test/spec_rack_post_body_content_type_parser.rb
|
73
|
+
- test/spec_rack_sendfile.rb
|
74
|
+
has_rdoc: true
|
75
|
+
homepage: http://github.com/rtomayko/rack-contrib/
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options:
|
78
|
+
- --line-numbers
|
79
|
+
- --inline-source
|
80
|
+
- --title
|
81
|
+
- rack-contrib
|
82
|
+
- --main
|
83
|
+
- README
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: "0"
|
91
|
+
version:
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: "0"
|
97
|
+
version:
|
98
|
+
requirements: []
|
99
|
+
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.2.0
|
102
|
+
signing_key:
|
103
|
+
specification_version: 2
|
104
|
+
summary: Contributed Rack Middleware and Utilities
|
105
|
+
test_files:
|
106
|
+
- test/spec_rack_contrib.rb
|
107
|
+
- test/spec_rack_etag.rb
|
108
|
+
- test/spec_rack_jsonp.rb
|
109
|
+
- test/spec_rack_lighttpd_script_name_fix.rb
|
110
|
+
- test/spec_rack_mailexceptions.rb
|
111
|
+
- test/spec_rack_post_body_content_type_parser.rb
|
112
|
+
- test/spec_rack_sendfile.rb
|