rtomayko-rack-contrib 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +18 -0
- data/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
|