Skip to content
Browse files

Merge remote branch 'origin/master'

Conflicts:
	lib/rack/rewindable_input.rb
  • Loading branch information...
2 parents f9acadc + 01532da commit aa0345c1af2f0b2112f068f694cd919b328cf76c @jeremy jeremy committed Feb 15, 2010
Showing with 2,942 additions and 1,132 deletions.
  1. +3 −0 .gitignore
  2. +1 −1 COPYING
  3. +3 −0 KNOWN-ISSUES
  4. +72 −26 README
  5. +11 −78 Rakefile
  6. +2 −174 bin/rackup
  7. +10 −8 lib/rack.rb
  8. +1 −1 lib/rack/auth/digest/params.rb
  9. +0 −487 lib/rack/auth/openid.rb
  10. +17 −0 lib/rack/builder.rb
  11. +2 −2 lib/rack/chunked.rb
  12. +3 −6 lib/rack/commonlogger.rb
  13. +15 −0 lib/rack/config.rb
  14. +1 −1 lib/rack/content_type.rb
  15. +1 −1 lib/rack/deflater.rb
  16. +6 −2 lib/rack/directory.rb
  17. +23 −0 lib/rack/etag.rb
  18. +4 −2 lib/rack/file.rb
  19. +19 −0 lib/rack/handler.rb
  20. +1 −1 lib/rack/handler/cgi.rb
  21. +10 −9 lib/rack/handler/fastcgi.rb
  22. +16 −8 lib/rack/handler/lsws.rb
  23. +12 −6 lib/rack/handler/mongrel.rb
  24. +9 −6 lib/rack/handler/scgi.rb
  25. +7 −5 lib/rack/handler/webrick.rb
  26. +56 −18 lib/rack/lint.rb
  27. +20 −0 lib/rack/logger.rb
  28. +3 −1 lib/rack/mime.rb
  29. +9 −4 lib/rack/mock.rb
  30. +18 −0 lib/rack/nulllogger.rb
  31. +6 −3 lib/rack/reloader.rb
  32. +37 −14 lib/rack/request.rb
  33. +5 −39 lib/rack/response.rb
  34. +3 −5 lib/rack/rewindable_input.rb
  35. +27 −0 lib/rack/runtime.rb
  36. +142 −0 lib/rack/sendfile.rb
  37. +212 −0 lib/rack/server.rb
  38. +3 −5 lib/rack/session/abstract/id.rb
  39. +3 −4 lib/rack/session/cookie.rb
  40. +53 −43 lib/rack/session/memcache.rb
  41. +1 −1 lib/rack/session/pool.rb
  42. +9 −8 lib/rack/urlmap.rb
  43. +134 −13 lib/rack/utils.rb
  44. +38 −0 rack.gemspec
  45. +6 −1 test/cgi/lighttpd.conf
  46. +6 −0 test/cgi/rackup_stub.rb
  47. +5 −0 test/cgi/sample_rackup.ru
  48. +1 −2 test/cgi/test.ru
  49. +259 −0 test/multipart/bad_robots
  50. +814 −0 test/multipart/fail_16384_nofile
  51. +7 −0 test/multipart/filename_and_modification_param
  52. +6 −0 test/multipart/filename_with_escaped_quotes
  53. +7 −0 test/multipart/filename_with_escaped_quotes_and_modification_param
  54. +6 −0 test/multipart/filename_with_percent_escaped_quotes
  55. +6 −0 test/multipart/filename_with_unescaped_quotes
  56. +6 −0 test/multipart/semicolon
  57. +1 −0 test/rackup/.gitignore
  58. +31 −0 test/rackup/config.ru
  59. +0 −84 test/spec_rack_auth_openid.rb
  60. +3 −3 test/spec_rack_cgi.rb
  61. +12 −0 test/spec_rack_commonlogger.rb
  62. +24 −0 test/spec_rack_config.rb
  63. +17 −0 test/spec_rack_etag.rb
  64. +8 −3 test/spec_rack_fastcgi.rb
  65. +46 −17 test/spec_rack_lint.rb
  66. +21 −0 test/spec_rack_logger.rb
  67. +1 −1 test/spec_rack_mock.rb
  68. +4 −4 test/spec_rack_mongrel.rb
  69. +13 −0 test/spec_rack_nulllogger.rb
  70. +47 −6 test/spec_rack_request.rb
  71. +3 −0 test/spec_rack_response.rb
  72. +41 −0 test/spec_rack_runtime.rb
  73. +86 −0 test/spec_rack_sendfile.rb
  74. +1 −10 test/spec_rack_session_cookie.rb
  75. +36 −13 test/spec_rack_session_memcache.rb
  76. +30 −0 test/spec_rack_urlmap.rb
  77. +186 −1 test/spec_rack_utils.rb
  78. +4 −4 test/spec_rack_webrick.rb
  79. +154 −0 test/spec_rackup.rb
  80. +16 −1 test/testrequest.rb
View
3 .gitignore
@@ -0,0 +1,3 @@
+RDOX
+SPEC
+*.gem
View
2 COPYING
@@ -1,4 +1,4 @@
-Copyright (c) 2007, 2008, 2009 Christian Neukirchen <purl.org/net/chneukirchen>
+Copyright (c) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
View
3 KNOWN-ISSUES
@@ -16,3 +16,6 @@
end
Of course, use this only when your app runs at "/".
+
+ Since lighttpd 1.4.23, you also can use the "fix-root-scriptname" flag
+ in fastcgi.server.
View
98 README
@@ -11,21 +11,13 @@ which all Rack applications should conform to.
== Specification changes in this release
-With Rack 1.0, the Rack specification (found in SPEC) changed in the
-following backward-incompatible ways. This was done to properly
-support Ruby 1.9 and to deprecate some problematic techniques:
-
-* Rack::VERSION has been pushed to [1,0].
-* Header values must be Strings now, split on "\n".
-* rack.input must be rewindable and support reading into a buffer,
- wrap with Rack::RewindableInput if it isn't.
-* Content-Length can be missing, in this case chunked transfer
- encoding is used.
-* Bodies can now additionally respond to #to_path with a filename to
- be served.
-* String bodies are deprecated and will not work with Ruby 1.9, use an
- Array with a single String instead.
-* rack.session is now specified.
+With Rack 1.1, the Rack specification (found in SPEC) changed in the
+following backward-incompatible ways.
+
+* Rack::VERSION has been pushed to [1,1].
+* rack.logger is now specified.
+* The SPEC now allows subclasses of the required types.
+* rack.input has to be opened in binary mode.
== Supported web servers
@@ -43,8 +35,11 @@ The included *handlers* connect all kinds of web servers to Rack:
These web servers include Rack handlers in their distributions:
* Ebb
* Fuzed
+* Glassfish v3
* Phusion Passenger (which is mod_rack for Apache and for nginx)
+* Rainbows!
* Unicorn
+* Zbatery
Any valid Rack app will run the same on all these handlers, without
changing anything.
@@ -70,6 +65,7 @@ These frameworks include Rack adapters in their distributions:
* Vintage
* Waves
* Wee
+* ... and many others.
Current links to these projects can be found at
http://wiki.ramaze.net/Home#other-frameworks
@@ -130,13 +126,13 @@ Either with the embedded WEBrick starter:
Or with rackup:
- bin/rackup -Ilib example/lobster.ru
+ bin/rackup -Ilib example/lobster.ru
By default, the lobster is found at http://localhost:9292.
== Installing with RubyGems
-A Gem of Rack is available. You can install it with:
+A Gem of Rack is available at gemcutter.org. You can install it with:
gem install rack
@@ -165,7 +161,6 @@ To run the test suite completely, you need:
* fcgi
* memcache-client
* mongrel
- * ruby-openid
* thin
The full set of tests test FCGI access with lighttpd (on port
@@ -272,16 +267,60 @@ run on port 11211) and memcache-client installed.
* The Rakefile has been rewritten.
* Many bugfixes and small improvements.
+* October 18th, 2009: Eighth public release 1.0.1.
+ * Bump remainder of rack.versions.
+ * Support the pure Ruby FCGI implementation.
+ * Fix for form names containing "=": split first then unescape components
+ * Fixes the handling of the filename parameter with semicolons in names.
+ * Add anchor to nested params parsing regexp to prevent stack overflows
+ * Use more compatible gzip write api instead of "<<".
+ * Make sure that Reloader doesn't break when executed via ruby -e
+ * Make sure WEBrick respects the :Host option
+ * Many Ruby 1.9 fixes.
+
+* January 3rd, 2010: Ninth public release 1.1.0.
+ * Moved Auth::OpenID to rack-contrib.
+ * SPEC change that relaxes Lint slightly to allow subclasses of the
+ required types
+ * SPEC change to document rack.input binary mode in greator detail
+ * SPEC define optional rack.logger specification
+ * File servers support X-Cascade header
+ * Imported Config middleware
+ * Imported ETag middleware
+ * Imported Runtime middleware
+ * Imported Sendfile middleware
+ * New Logger and NullLogger middlewares
+ * Added mime type for .ogv and .manifest.
+ * Don't squeeze PATH_INFO slashes
+ * Use Content-Type to determine POST params parsing
+ * Update Rack::Utils::HTTP_STATUS_CODES hash
+ * Add status code lookup utility
+ * Response should call #to_i on the status
+ * Add Request#user_agent
+ * Request#host knows about forwared host
+ * Return an empty string for Request#host if HTTP_HOST and
+ SERVER_NAME are both missing
+ * Allow MockRequest to accept hash params
+ * Optimizations to HeaderHash
+ * Refactored rackup into Rack::Server
+ * Added Utils.build_nested_query to complement Utils.parse_nested_query
+ * Added Utils::Multipart.build_multipart to complement
+ Utils::Multipart.parse_multipart
+ * Extracted set and delete cookie helpers into Utils so they can be
+ used outside Response
+ * Extract parse_query and parse_multipart in Request so subclasses
+ can change their behavior
+ * Enforce binary encoding in RewindableInput
+ * Set correct external_encoding for handlers that don't use RewindableInput
+
== Contact
-Please mail bugs, suggestions and patches to
-<mailto:rack-devel@googlegroups.com>.
+Please post bugs, suggestions and patches to
+the bug tracker at <http://rack.lighthouseapp.com/>.
Mailing list archives are available at
<http://groups.google.com/group/rack-devel>.
-There is a bug tracker at <http://rack.lighthouseapp.com/>.
-
Git repository (send Git patches to the mailing list):
* http://github.com/rack/rack
* http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack.git
@@ -307,8 +346,14 @@ would like to thank:
* Luc Heinrich for the Cookie sessions, the static file handler and bugfixes.
* Armin Ronacher, for the logo and racktools.
* Aredridel, Ben Alpert, Dan Kubb, Daniel Roethlisberger, Matt Todd,
- Tom Robinson, Phil Hagelberg, and S. Brent Faulkner for bug fixing
- and other improvements.
+ Tom Robinson, Phil Hagelberg, S. Brent Faulkner, Bosko Milekic,
+ Daniel Rodríguez Troitiño, Genki Takiuchi, Geoffrey Grosenbach,
+ Julien Sanchez, Kamal Fariz Mahyuddin, Masayoshi Takahashi, Patrick
+ Aljordm, Mig, and Kazuhiro Nishiyama for bug fixing and other
+ improvements.
+* Eric Wong, Hongli Lai, Jeremy Kemper for their continuous support
+ and API improvements.
+* Yehuda Katz and Carl Lerche for refactoring rackup.
* Brian Candler, for Rack::ContentType.
* Graham Batty, for improved handler loading.
* Stephen Bannasch, for bug reports and documentation.
@@ -323,7 +368,7 @@ would like to thank:
== Copyright
-Copyright (C) 2007, 2008, 2009 Christian Neukirchen <http://purl.org/net/chneukirchen>
+Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <http://purl.org/net/chneukirchen>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
@@ -338,7 +383,7 @@ all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -347,6 +392,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Rack:: <http://rack.rubyforge.org/>
Rack's Rubyforge project:: <http://rubyforge.org/projects/rack>
Official Rack repositories:: <http://github.com/rack>
+Rack Lighthouse Bug Tracking:: <http://rack.lighthouseapp.com/>
rack-devel mailing list:: <http://groups.google.com/group/rack-devel>
Christian Neukirchen:: <http://chneukirchen.org/>
View
89 Rakefile
@@ -6,12 +6,10 @@ require 'rake/testtask'
desc "Run all the tests"
task :default => [:test]
-
desc "Make an archive as .tar.gz"
-task :dist => [:chmod, :changelog, :rdoc, "SPEC", "rack.gemspec"] do
- FileUtils.touch("RDOX")
+task :dist => [:chmod, :changelog, :rdoc, "SPEC"] do
sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar"
- sh "pax -waf #{release}.tar -s ':^:#{release}/:' RDOX SPEC ChangeLog doc rack.gemspec"
+ sh "pax -waf #{release}.tar -s ':^:#{release}/:' SPEC ChangeLog doc rack.gemspec"
sh "gzip -f -9 #{release}.tar"
end
@@ -24,25 +22,15 @@ task :officialrelease do
sh "mv stage/#{release}.tar.gz stage/#{release}.gem ."
end
-task :officialrelease_really => [:fulltest, "RDOX", "SPEC", :dist, :gem] do
+task :officialrelease_really => ["SPEC", :dist, :gem] do
sh "sha1sum #{release}.tar.gz #{release}.gem"
end
-
-def version
- abort "You need to pass VERSION=... to build packages." unless ENV["VERSION"]
- ENV["VERSION"]
-end
-
def release
- "rack-#{version}"
+ require File.dirname(__FILE__) + "/lib/rack"
+ "rack-#{Rack.release}.0"
end
-def manifest
- `git ls-files`.split("\n")
-end
-
-
desc "Make binaries executable"
task :chmod do
Dir["bin/*"].each { |binary| File.chmod(0775, binary) }
@@ -68,11 +56,6 @@ task :changelog do
end
-desc "Generate RDox"
-task "RDOX" do
- sh "specrb -Ilib:test -a --rdox >RDOX"
-end
-
desc "Generate Rack Specification"
task "SPEC" do
File.open("SPEC", "wb") { |file|
@@ -86,73 +69,23 @@ end
desc "Run all the fast tests"
task :test do
- sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS'] || '-t "^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache|Rack::Auth::OpenID)"'}"
+ sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS'] || '-t "^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache|rackup)"'}"
end
desc "Run all the tests"
task :fulltest => [:chmod] do
sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}"
end
-begin
- require 'rubygems'
-rescue LoadError
- # Too bad.
-else
- task "rack.gemspec" do
- spec = Gem::Specification.new do |s|
- s.name = "rack"
- s.version = version
- s.platform = Gem::Platform::RUBY
- s.summary = "a modular Ruby webserver interface"
-
- s.description = <<-EOF
-Rack provides minimal, modular and adaptable interface for developing
-web applications in Ruby. By wrapping HTTP requests and responses in
-the simplest way possible, it unifies and distills the API for web
-servers, web frameworks, and software in between (the so-called
-middleware) into a single method call.
-
-Also see http://rack.rubyforge.org.
- EOF
-
- s.files = manifest + %w(SPEC RDOX rack.gemspec)
- s.bindir = 'bin'
- s.executables << 'rackup'
- s.require_path = 'lib'
- s.has_rdoc = true
- s.extra_rdoc_files = ['README', 'SPEC', 'RDOX', 'KNOWN-ISSUES']
- s.test_files = Dir['test/{test,spec}_*.rb']
-
- s.author = 'Christian Neukirchen'
- s.email = 'chneukirchen@gmail.com'
- s.homepage = 'http://rack.rubyforge.org'
- s.rubyforge_project = 'rack'
-
- s.add_development_dependency 'test-spec'
-
- s.add_development_dependency 'camping'
- s.add_development_dependency 'fcgi'
- s.add_development_dependency 'memcache-client'
- s.add_development_dependency 'mongrel'
- s.add_development_dependency 'ruby-openid', '~> 2.0.0'
- s.add_development_dependency 'thin'
- end
-
- File.open("rack.gemspec", "w") { |f| f << spec.to_ruby }
- end
-
- task :gem => ["rack.gemspec", "SPEC"] do
- FileUtils.touch("RDOX")
- sh "gem build rack.gemspec"
- end
+task :gem => ["SPEC"] do
+ sh "gem build rack.gemspec"
end
desc "Generate RDoc documentation"
-task :rdoc do
- sh(*%w{rdoc --line-numbers --main README
+task :rdoc => ["SPEC"] do
+ sh(*%w{rdoc --line-numbers --main README
--title 'Rack\ Documentation' --charset utf-8 -U -o doc} +
- %w{README KNOWN-ISSUES SPEC RDOX} +
+ %w{README KNOWN-ISSUES SPEC} +
Dir["lib/**/*.rb"])
end
View
176 bin/rackup
@@ -1,176 +1,4 @@
#!/usr/bin/env ruby
-# -*- ruby -*-
-$LOAD_PATH.unshift File.expand_path("#{__FILE__}/../../lib")
-autoload :Rack, 'rack'
-
-require 'optparse'
-
-automatic = false
-server = nil
-env = "development"
-daemonize = false
-pid = nil
-options = {:Port => 9292, :Host => "0.0.0.0", :AccessLog => []}
-
-# Don't evaluate CGI ISINDEX parameters.
-# http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
-ARGV.clear if ENV.include?("REQUEST_METHOD")
-
-opts = OptionParser.new("", 24, ' ') { |opts|
- opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
-
- opts.separator ""
- opts.separator "Ruby options:"
-
- lineno = 1
- opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
- eval line, TOPLEVEL_BINDING, "-e", lineno
- lineno += 1
- }
-
- opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
- $DEBUG = true
- }
- opts.on("-w", "--warn", "turn warnings on for your script") {
- $-w = true
- }
-
- opts.on("-I", "--include PATH",
- "specify $LOAD_PATH (may be used more than once)") { |path|
- $LOAD_PATH.unshift(*path.split(":"))
- }
-
- opts.on("-r", "--require LIBRARY",
- "require the library, before executing your script") { |library|
- require library
- }
-
- opts.separator ""
- opts.separator "Rack options:"
- opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s|
- server = s
- }
-
- opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host|
- options[:Host] = host
- }
-
- opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
- options[:Port] = port
- }
-
- opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
- env = e
- }
-
- opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
- daemonize = d ? true : false
- }
-
- opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
- pid = File.expand_path(f)
- }
-
- opts.separator ""
- opts.separator "Common options:"
-
- opts.on_tail("-h", "--help", "Show this message") do
- puts opts
- exit
- end
-
- opts.on_tail("--version", "Show version") do
- puts "Rack #{Rack.version}"
- exit
- end
-
- opts.parse! ARGV
-}
-
-require 'pp' if $DEBUG
-
-config = ARGV[0] || "config.ru"
-if !File.exist? config
- abort "configuration #{config} not found"
-end
-
-if config =~ /\.ru$/
- cfgfile = File.read(config)
- if cfgfile[/^#\\(.*)/]
- opts.parse! $1.split(/\s+/)
- end
- inner_app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
- nil, config
-else
- require config
- inner_app = Object.const_get(File.basename(config, '.rb').capitalize)
-end
-
-unless server = Rack::Handler.get(server)
- # Guess.
- if ENV.include?("PHP_FCGI_CHILDREN")
- server = Rack::Handler::FastCGI
-
- # We already speak FastCGI
- options.delete :File
- options.delete :Port
- elsif ENV.include?("REQUEST_METHOD")
- server = Rack::Handler::CGI
- else
- begin
- server = Rack::Handler::Mongrel
- rescue LoadError => e
- server = Rack::Handler::WEBrick
- end
- end
-end
-
-p server if $DEBUG
-
-case env
-when "development"
- app = Rack::Builder.new {
- use Rack::CommonLogger, $stderr unless server.name =~ /CGI/
- use Rack::ShowExceptions
- use Rack::Lint
- run inner_app
- }.to_app
-
-when "deployment"
- app = Rack::Builder.new {
- use Rack::CommonLogger, $stderr unless server.name =~ /CGI/
- run inner_app
- }.to_app
-
-when "none"
- app = inner_app
-
-end
-
-if $DEBUG
- pp app
- pp inner_app
-end
-
-if daemonize
- if RUBY_VERSION < "1.9"
- exit if fork
- Process.setsid
- exit if fork
- Dir.chdir "/"
- File.umask 0000
- STDIN.reopen "/dev/null"
- STDOUT.reopen "/dev/null", "a"
- STDERR.reopen "/dev/null", "a"
- else
- Process.daemon
- end
-
- if pid
- File.open(pid, 'w'){ |f| f.write("#{Process.pid}") }
- at_exit { File.delete(pid) if File.exist?(pid) }
- end
-end
-
-server.run app, options
+require "rack"
+Rack::Server.start
View
18 lib/rack.rb
@@ -1,12 +1,8 @@
-# Copyright (C) 2007, 2008, 2009 Christian Neukirchen <purl.org/net/chneukirchen>
+# Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
#
# Rack is freely distributable under the terms of an MIT-style license.
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
-path = File.expand_path(File.dirname(__FILE__))
-$:.unshift(path) unless $:.include?(path)
-
-
# The Rack main module, serving as a namespace for all core Rack
# modules and classes.
#
@@ -15,7 +11,7 @@
module Rack
# The Rack protocol version number implemented.
- VERSION = [1,0]
+ VERSION = [1,1]
# Return the Rack protocol version as a dotted string.
def self.version
@@ -24,16 +20,18 @@ def self.version
# Return the Rack release as a dotted string.
def self.release
- "1.0"
+ "1.1"
end
autoload :Builder, "rack/builder"
autoload :Cascade, "rack/cascade"
autoload :Chunked, "rack/chunked"
autoload :CommonLogger, "rack/commonlogger"
autoload :ConditionalGet, "rack/conditionalget"
+ autoload :Config, "rack/config"
autoload :ContentLength, "rack/content_length"
autoload :ContentType, "rack/content_type"
+ autoload :ETag, "rack/etag"
autoload :File, "rack/file"
autoload :Deflater, "rack/deflater"
autoload :Directory, "rack/directory"
@@ -42,10 +40,15 @@ def self.release
autoload :Head, "rack/head"
autoload :Lint, "rack/lint"
autoload :Lock, "rack/lock"
+ autoload :Logger, "rack/logger"
autoload :MethodOverride, "rack/methodoverride"
autoload :Mime, "rack/mime"
+ autoload :NullLogger, "rack/nulllogger"
autoload :Recursive, "rack/recursive"
autoload :Reloader, "rack/reloader"
+ autoload :Runtime, "rack/runtime"
+ autoload :Sendfile, "rack/sendfile"
+ autoload :Server, "rack/server"
autoload :ShowExceptions, "rack/showexceptions"
autoload :ShowStatus, "rack/showstatus"
autoload :Static, "rack/static"
@@ -62,7 +65,6 @@ module Auth
autoload :Basic, "rack/auth/basic"
autoload :AbstractRequest, "rack/auth/abstract/request"
autoload :AbstractHandler, "rack/auth/abstract/handler"
- autoload :OpenID, "rack/auth/openid"
module Digest
autoload :MD5, "rack/auth/digest/md5"
autoload :Nonce, "rack/auth/digest/nonce"
View
2 lib/rack/auth/digest/params.rb
@@ -35,7 +35,7 @@ def []=(k, v)
super k.to_s, v.to_s
end
- UNQUOTED = ['qop', 'nc', 'stale']
+ UNQUOTED = ['nc', 'stale']
def to_s
inject([]) do |parts, (k, v)|
View
487 lib/rack/auth/openid.rb
@@ -1,487 +0,0 @@
-# AUTHOR: Scytrin dai Kinthra <scytrin@gmail.com>; blink#ruby-lang@irc.freenode.net
-
-gem 'ruby-openid', '~> 2' if defined? Gem
-require 'rack/request'
-require 'rack/utils'
-require 'rack/auth/abstract/handler'
-
-require 'uri'
-require 'openid'
-require 'openid/extension'
-require 'openid/store/memory'
-
-module Rack
- class Request
- def openid_request
- @env['rack.auth.openid.request']
- end
-
- def openid_response
- @env['rack.auth.openid.response']
- end
- end
-
- module Auth
-
- # Rack::Auth::OpenID provides a simple method for setting up an OpenID
- # Consumer. It requires the ruby-openid library from janrain to operate,
- # as well as a rack method of session management.
- #
- # The ruby-openid home page is at http://openidenabled.com/ruby-openid/.
- #
- # The OpenID specifications can be found at
- # http://openid.net/specs/openid-authentication-1_1.html
- # and
- # http://openid.net/specs/openid-authentication-2_0.html. Documentation
- # for published OpenID extensions and related topics can be found at
- # http://openid.net/developers/specs/.
- #
- # It is recommended to read through the OpenID spec, as well as
- # ruby-openid's documentation, to understand what exactly goes on. However
- # a setup as simple as the presented examples is enough to provide
- # Consumer functionality.
- #
- # This library strongly intends to utilize the OpenID 2.0 features of the
- # ruby-openid library, which provides OpenID 1.0 compatiblity.
- #
- # NOTE: Due to the amount of data that this library stores in the
- # session, Rack::Session::Cookie may fault.
- #
- # == Examples
- #
- # simple_oid = OpenID.new('http://mysite.com/')
- #
- # return_oid = OpenID.new('http://mysite.com/', {
- # :return_to => 'http://mysite.com/openid'
- # })
- #
- # complex_oid = OpenID.new('http://mysite.com/',
- # :immediate => true,
- # :extensions => {
- # ::OpenID::SReg => [['email'],['nickname']]
- # }
- # )
- #
- # = Advanced
- #
- # Most of the functionality of this library is encapsulated such that
- # expansion and overriding functions isn't difficult nor tricky.
- # Alternately, to avoid opening up singleton objects or subclassing, a
- # wrapper rack middleware can be composed to act upon Auth::OpenID's
- # responses. See #check and #finish for locations of pertinent data.
- #
- # == Responses
- #
- # To change the responses that Auth::OpenID returns, override the methods
- # #redirect, #bad_request, #unauthorized, #access_denied, and
- # #foreign_server_failure.
- #
- # Additionally #confirm_post_params is used when the URI would exceed
- # length limits on a GET request when doing the initial verification
- # request.
- #
- # == Processing
- #
- # To change methods of processing completed transactions, override the
- # methods #success, #setup_needed, #cancel, and #failure. Please ensure
- # the returned object is a rack compatible response.
- #
- # The first argument is an OpenID::Response, the second is a
- # Rack::Request of the current request, the last is the hash used in
- # ruby-openid handling, which can be found manually at
- # env['rack.session'][:openid].
- #
- # This is useful if you wanted to expand the processing done, such as
- # setting up user accounts.
- #
- # oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to
- # def oid_app.success oid, request, session
- # user = Models::User[oid.identity_url]
- # user ||= Models::User.create_from_openid oid
- # request['rack.session'][:user] = user.id
- # redirect MyApp.site_home
- # end
- #
- # site_map['/openid'] = oid_app
- # map = Rack::URLMap.new site_map
- # ...
-
- class OpenID
- # Raised if an incompatible session is being used.
- class NoSession < RuntimeError; end
- # Raised if an extension not matching specifications is provided.
- class BadExtension < RuntimeError; end
- # Possible statuses returned from consumer responses. See definitions
- # in the ruby-openid library.
- ValidStatus = [
- ::OpenID::Consumer::SUCCESS,
- ::OpenID::Consumer::FAILURE,
- ::OpenID::Consumer::CANCEL,
- ::OpenID::Consumer::SETUP_NEEDED
- ]
-
- # The first argument is the realm, identifying the site they are trusting
- # with their identity. This is required, also treated as the trust_root
- # in OpenID 1.x exchanges.
- #
- # The lits of acceptable options include :return_to, :session_key,
- # :openid_param, :store, :immediate, :extensions.
- #
- # <tt>:return_to</tt> defines the url to return to after the client
- # authenticates with the openid service provider. This url should point
- # to where Rack::Auth::OpenID is mounted. If unprovided, the url of
- # the current request is used.
- #
- # <tt>:session_key</tt> defines the key to the session hash in the env.
- # The default is 'rack.session'.
- #
- # <tt>:openid_param</tt> defines at what key in the request parameters to
- # find the identifier to resolve. As per the 2.0 spec, the default is
- # 'openid_identifier'.
- #
- # <tt>:store</tt> defined what OpenID Store to use for persistant
- # information. By default a Store::Memory is used.
- #
- # <tt>:immediate</tt> as true will make initial requests to be of an
- # immediate type. This is false by default. See OpenID specification
- # documentation.
- #
- # <tt>:extensions</tt> should be a hash of openid extension
- # implementations. The key should be the extension module, the value
- # should be an array of arguments for extension::Request.new().
- # The hash is iterated over and passed to #add_extension for processing.
- # Please see #add_extension for further documentation.
-
- def initialize(realm, options={})
- realm = URI(realm)
- raise ArgumentError, "Invalid realm: #{realm}" \
- unless realm.absolute? \
- and realm.fragment.nil? \
- and realm.scheme =~ /^https?$/ \
- and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/
- realm.path = '/' if realm.path.empty?
- @realm = realm.to_s
-
- if ruri = options[:return_to]
- ruri = URI(ruri)
- raise ArgumentError, "Invalid return_to: #{ruri}" \
- unless ruri.absolute? \
- and ruri.scheme =~ /^https?$/ \
- and ruri.fragment.nil?
- raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \
- unless self.within_realm?(ruri)
- @return_to = ruri.to_s
- end
-
- @session_key = options[:session_key] || 'rack.session'
- @openid_param = options[:openid_param] || 'openid_identifier'
- @store = options[:store] || ::OpenID::Store::Memory.new
- @immediate = !!options[:immediate]
-
- @extensions = {}
- if extensions = options[:extensions]
- extensions.each do |ext, args|
- add_extension(ext, *args)
- end
- end
-
- # Undocumented, semi-experimental
- @anonymous = !!options[:anonymous]
- end
-
- attr_reader :realm, :return_to, :session_key, :openid_param, :store,
- :immediate, :extensions
-
- # Sets up and uses session data at <tt>:openid</tt> within the session.
- # Errors in this setup will raise a NoSession exception.
- #
- # If the parameter 'openid.mode' is set, which implies a followup from
- # the openid server, processing is passed to #finish and the result is
- # returned. However, if there is no appropriate openid information in the
- # session, a 400 error is returned.
- #
- # If the parameter specified by <tt>options[:openid_param]</tt> is
- # present, processing is passed to #check and the result is returned.
- #
- # If neither of these conditions are met, #bad_request is called.
-
- def call(env)
- env['rack.auth.openid'] = self
- env_session = env[@session_key]
- unless env_session and env_session.is_a?(Hash)
- raise NoSession, 'No compatible session.'
- end
- # let us work in our own namespace...
- session = (env_session[:openid] ||= {})
- unless session and session.is_a?(Hash)
- raise NoSession, 'Incompatible openid session.'
- end
-
- request = Rack::Request.new(env)
- consumer = ::OpenID::Consumer.new(session, @store)
-
- if mode = request.GET['openid.mode']
- finish(consumer, session, request)
- elsif request.GET[@openid_param]
- check(consumer, session, request)
- else
- bad_request
- end
- end
-
- # As the first part of OpenID consumer action, #check retrieves the data
- # required for completion.
- #
- # If all parameters fit within the max length of a URI, a 303 redirect
- # will be returned. Otherwise #confirm_post_params will be called.
- #
- # Any messages from OpenID's request are logged to env['rack.errors']
- #
- # <tt>env['rack.auth.openid.request']</tt> is the openid checkid request
- # instance.
- #
- # <tt>session[:openid_param]</tt> is set to the openid identifier
- # provided by the user.
- #
- # <tt>session[:return_to]</tt> is set to the return_to uri given to the
- # identity provider.
-
- def check(consumer, session, req)
- oid = consumer.begin(req.GET[@openid_param], @anonymous)
- req.env['rack.auth.openid.request'] = oid
- req.env['rack.errors'].puts(oid.message)
- p oid if $DEBUG
-
- ## Extension support
- extensions.each do |ext,args|
- oid.add_extension(ext::Request.new(*args))
- end
-
- session[:openid_param] = req.GET[openid_param]
- return_to_uri = return_to ? return_to : req.url
- session[:return_to] = return_to_uri
- immediate = session.key?(:setup_needed) ? false : immediate
-
- if oid.send_redirect?(realm, return_to_uri, immediate)
- redirect(oid.redirect_url(realm, return_to_uri, immediate))
- else
- confirm_post_params(oid, realm, return_to_uri, immediate)
- end
- rescue ::OpenID::DiscoveryFailure => e
- # thrown from inside OpenID::Consumer#begin by yadis stuff
- req.env['rack.errors'].puts( [e.message, *e.backtrace]*"\n" )
- return foreign_server_failure
- end
-
- # This is the final portion of authentication.
- # If successful, a redirect to the realm is be returned.
- # Data gathered from extensions are stored in session[:openid] with the
- # extension's namespace uri as the key.
- #
- # Any messages from OpenID's response are logged to env['rack.errors']
- #
- # <tt>env['rack.auth.openid.response']</tt> will contain the openid
- # response.
-
- def finish(consumer, session, req)
- oid = consumer.complete(req.GET, req.url)
- req.env['rack.auth.openid.response'] = oid
- req.env['rack.errors'].puts(oid.message)
- p oid if $DEBUG
-
- if ValidStatus.include?(oid.status)
- __send__(oid.status, oid, req, session)
- else
- invalid_status(oid, req, session)
- end
- end
-
- # The first argument should be the main extension module.
- # The extension module should contain the constants:
- # * class Request, should have OpenID::Extension as an ancestor
- # * class Response, should have OpenID::Extension as an ancestor
- # * string NS_URI, which defining the namespace of the extension
- #
- # All trailing arguments will be passed to extension::Request.new in
- # #check.
- # The openid response will be passed to
- # extension::Response#from_success_response, oid#get_extension_args will
- # be called on the result to attain the gathered data.
- #
- # This method returns the key at which the response data will be found in
- # the session, which is the namespace uri by default.
-
- def add_extension(ext, *args)
- raise BadExtension unless valid_extension?(ext)
- extensions[ext] = args
- return ext::NS_URI
- end
-
- # Checks the validitity, in the context of usage, of a submitted
- # extension.
-
- def valid_extension?(ext)
- if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) }
- raise ArgumentError, 'Extension is missing constants.'
- elsif not ext::Response.respond_to?(:from_success_response)
- raise ArgumentError, 'Response is missing required method.'
- end
- return true
- rescue
- return false
- end
-
- # Checks the provided uri to ensure it'd be considered within the realm.
- # is currently not compatible with wildcard realms.
-
- def within_realm? uri
- uri = URI.parse(uri.to_s)
- realm = URI.parse(self.realm)
- return false unless uri.absolute?
- return false unless uri.path[0, realm.path.size] == realm.path
- return false unless uri.host == realm.host or realm.host[/^\*\./]
- # for wildcard support, is awkward with URI limitations
- realm_match = Regexp.escape(realm.host).
- sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$'
- return false unless uri.host.match(realm_match)
- return true
- end
-
- alias_method :include?, :within_realm?
-
- protected
-
- # Returns an html form page for posting to an Identity Provider if the
- # GET request would exceed the upper URI length limit.
-
- def confirm_post_params(oid, realm, return_to, immediate)
- response = Rack::Response.new '<html>'+
- '<head><title>Confirm...</title></head>'+
- '<body>'+oid.form_markup(realm, return_to, immediate)+'</body>'+
- '</html>'
- response.finish
- end
-
- # Returns a 303 redirect with the destination of that provided by the
- # argument.
-
- def redirect(uri)
- [ 303, {'Content-Type'=>'text/plain', 'Content-Length'=>'0',
- 'Location' => uri},
- [] ]
- end
-
- # Returns an empty 400 response.
-
- def bad_request
- [ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'},
- [''] ]
- end
-
- # Returns a basic unauthorized 401 response.
-
- def unauthorized
- [ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'},
- ['Unauthorized.'] ]
- end
-
- # Returns a basic access denied 403 response.
-
- def access_denied
- [ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'},
- ['Access denied.'] ]
- end
-
- # Returns a 503 response to be used if communication with the remote
- # OpenID server fails.
-
- def foreign_server_failure
- [ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'},
- ['Foreign server failure.'] ]
- end
-
- private
-
- # Called to complete processing on a successful transaction.
- # Within the openid session, :openid_identity and :openid_identifier are
- # set to the user friendly and the standard representation of the
- # validated identity. All other data in the openid session is cleared.
-
- def success(oid, request, session)
- session.clear
- session[:openid_identity] = oid.display_identifier
- session[:openid_identifier] = oid.identity_url
- extensions.keys.each do |ext|
- label = ext.name[/[^:]+$/].downcase
- response = ext::Response.from_success_response(oid)
- session[label] = response.data
- end
- redirect(realm)
- end
-
- # Called if the Identity Provider indicates further setup by the user is
- # required.
- # The identifier is retrived from the openid session at :openid_param.
- # And :setup_needed is set to true to prevent looping.
-
- def setup_needed(oid, request, session)
- identifier = session[:openid_param]
- session[:setup_needed] = true
- redirect(req.script_name + '?' + openid_param + '=' + identifier)
- end
-
- # Called if the user indicates they wish to cancel identification.
- # Data within openid session is cleared.
-
- def cancel(oid, request, session)
- session.clear
- access_denied
- end
-
- # Called if the Identity Provider indicates the user is unable to confirm
- # their identity. Data within the openid session is left alone, in case
- # of swarm auth attacks.
-
- def failure(oid, request, session)
- unauthorized
- end
-
- # To be called if there is no method for handling the OpenID response
- # status.
-
- def invalid_status(oid, request, session)
- msg = 'Invalid status returned by the OpenID authorization reponse.'
- [ 500,
- {'Content-Type'=>'text/plain','Content-Length'=>msg.length.to_s},
- [msg] ]
- end
- end
-
- # A class developed out of the request to use OpenID as an authentication
- # middleware. The request will be sent to the OpenID instance unless the
- # block evaluates to true. For example in rackup, you can use it as such:
- #
- # use Rack::Session::Pool
- # use Rack::Auth::OpenIDAuth, realm, openid_options do |env|
- # env['rack.session'][:authkey] == a_string
- # end
- # run RackApp
- #
- # Or simply:
- #
- # app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth
-
- class OpenIDAuth < Rack::Auth::AbstractHandler
- attr_reader :oid
- def initialize(app, realm, options={}, &auth)
- @oid = OpenID.new(realm, options)
- super(app, &auth)
- end
-
- def call(env)
- to = @authenticator.call(env) ? @app : @oid
- to.call(env)
- end
- end
- end
-end
View
17 lib/rack/builder.rb
@@ -24,6 +24,23 @@ module Rack
# You can use +map+ to construct a Rack::URLMap in a convenient way.
class Builder
+ def self.parse_file(config, opts = Server::Options.new)
+ options = {}
+ if config =~ /\.ru$/
+ cfgfile = ::File.read(config)
+ if cfgfile[/^#\\(.*)/] && opts
+ options = opts.parse! $1.split(/\s+/)
+ end
+ cfgfile.sub!(/^__END__\n.*/, '')
+ app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
+ TOPLEVEL_BINDING, config
+ else
+ require config
+ app = Object.const_get(::File.basename(config, '.rb').capitalize)
+ end
+ return app, options
+ end
+
def initialize(&block)
@ins = []
instance_eval(&block) if block_given?
View
4 lib/rack/chunked.rb
@@ -19,7 +19,7 @@ def call(env)
STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
headers['Content-Length'] ||
headers['Transfer-Encoding']
- [status, headers.to_hash, body]
+ [status, headers, body]
else
dup.chunk(status, headers, body)
end
@@ -29,7 +29,7 @@ def chunk(status, headers, body)
@body = body
headers.delete('Content-Length')
headers['Transfer-Encoding'] = 'chunked'
- [status, headers.to_hash, self]
+ [status, headers, self]
end
def each
View
9 lib/rack/commonlogger.rb
@@ -16,6 +16,7 @@ def initialize(app, logger=nil)
def call(env)
began_at = Time.now
status, header, body = @app.call(env)
+ header = Utils::HeaderHash.new(header)
log(env, status, header, began_at)
[status, header, body]
end
@@ -41,12 +42,8 @@ def log(env, status, header, began_at)
end
def extract_content_length(headers)
- headers.each do |key, value|
- if key.downcase == 'content-length'
- return value.to_s == '0' ? '-' : value
- end
- end
- '-'
+ value = headers['Content-Length'] or return '-'
+ value.to_s == '0' ? '-' : value
end
end
end
View
15 lib/rack/config.rb
@@ -0,0 +1,15 @@
+module Rack
+ # Rack::Config modifies the environment using the block given during
+ # initialization.
+ class Config
+ def initialize(app, &block)
+ @app = app
+ @block = block
+ end
+
+ def call(env)
+ @block.call(env)
+ @app.call(env)
+ end
+ end
+end
View
2 lib/rack/content_type.rb
@@ -17,7 +17,7 @@ def call(env)
status, headers, body = @app.call(env)
headers = Utils::HeaderHash.new(headers)
headers['Content-Type'] ||= @content_type
- [status, headers.to_hash, body]
+ [status, headers, body]
end
end
end
View
2 lib/rack/deflater.rb
@@ -60,7 +60,7 @@ def each(&block)
@writer = block
gzip =::Zlib::GzipWriter.new(self)
gzip.mtime = @mtime
- @body.each { |part| gzip << part }
+ @body.each { |part| gzip.write(part) }
@body.close if @body.respond_to?(:close)
gzip.close
@writer = nil
View
8 lib/rack/directory.rb
@@ -71,7 +71,9 @@ def check_forbidden
body = "Forbidden\n"
size = Rack::Utils.bytesize(body)
- return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]]
+ return [403, {"Content-Type" => "text/plain",
+ "Content-Length" => size.to_s,
+ "X-Cascade" => "pass"}, [body]]
end
def list_directory
@@ -123,7 +125,9 @@ def list_path
def entity_not_found
body = "Entity not found: #{@path_info}\n"
size = Rack::Utils.bytesize(body)
- return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]]
+ return [404, {"Content-Type" => "text/plain",
+ "Content-Length" => size.to_s,
+ "X-Cascade" => "pass"}, [body]]
end
def each
View
23 lib/rack/etag.rb
@@ -0,0 +1,23 @@
+require 'digest/md5'
+
+module Rack
+ # Automatically sets the ETag header on all String bodies
+ class ETag
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+
+ if !headers.has_key?('ETag')
+ parts = []
+ body.each { |part| parts << part.to_s }
+ headers['ETag'] = %("#{Digest::MD5.hexdigest(parts.join(""))}")
+ [status, headers, parts]
+ else
+ [status, headers, body]
+ end
+ end
+ end
+end
View
6 lib/rack/file.rb
@@ -45,7 +45,8 @@ def _call(env)
def forbidden
body = "Forbidden\n"
[403, {"Content-Type" => "text/plain",
- "Content-Length" => body.size.to_s},
+ "Content-Length" => body.size.to_s,
+ "X-Cascade" => "pass"},
[body]]
end
@@ -73,7 +74,8 @@ def serving
def not_found
body = "File not found: #{@path_info}\n"
[404, {"Content-Type" => "text/plain",
- "Content-Length" => body.size.to_s},
+ "Content-Length" => body.size.to_s,
+ "X-Cascade" => "pass"},
[body]]
end
View
19 lib/rack/handler.rb
@@ -22,6 +22,25 @@ def self.get(server)
end
end
+ def self.default(options = {})
+ # Guess.
+ if ENV.include?("PHP_FCGI_CHILDREN")
+ # We already speak FastCGI
+ options.delete :File
+ options.delete :Port
+
+ Rack::Handler::FastCGI
+ elsif ENV.include?("REQUEST_METHOD")
+ Rack::Handler::CGI
+ else
+ begin
+ Rack::Handler::Mongrel
+ rescue LoadError => e
+ Rack::Handler::WEBrick
+ end
+ end
+ end
+
# Transforms server-name constants to their canonical form as filenames,
# then tries to require them but silences the LoadError if not found
#
View
2 lib/rack/handler/cgi.rb
@@ -15,7 +15,7 @@ def self.serve(app)
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
- env.update({"rack.version" => [1,0],
+ env.update({"rack.version" => [1,1],
"rack.input" => $stdin,
"rack.errors" => $stderr,
View
19 lib/rack/handler/fastcgi.rb
@@ -3,13 +3,15 @@
require 'rack/content_length'
require 'rack/rewindable_input'
-class FCGI::Stream
- alias _rack_read_without_buffer read
+if defined? FCGI::Stream
+ class FCGI::Stream
+ alias _rack_read_without_buffer read
- def read(n, buffer=nil)
- buf = _rack_read_without_buffer n
- buffer.replace(buf.to_s) if buffer
- buf
+ def read(n, buffer=nil)
+ buf = _rack_read_without_buffer n
+ buffer.replace(buf.to_s) if buffer
+ buf
+ end
end
end
@@ -31,10 +33,10 @@ def self.serve(request, app)
env.delete "HTTP_CONTENT_LENGTH"
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
-
+
rack_input = RewindableInput.new(request.in)
- env.update({"rack.version" => [1,0],
+ env.update({"rack.version" => [1,1],
"rack.input" => rack_input,
"rack.errors" => request.err,
@@ -48,7 +50,6 @@ def self.serve(request, app)
env["QUERY_STRING"] ||= ""
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["REQUEST_PATH"] ||= "/"
- env.delete "PATH_INFO" if env["PATH_INFO"] == ""
env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
View
24 lib/rack/handler/lsws.rb
@@ -1,5 +1,6 @@
require 'lsapi'
require 'rack/content_length'
+require 'rack/rewindable_input'
module Rack
module Handler
@@ -15,14 +16,19 @@ def self.serve(app)
env = ENV.to_hash
env.delete "HTTP_CONTENT_LENGTH"
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
- env.update({"rack.version" => [1,0],
- "rack.input" => StringIO.new($stdin.read.to_s),
- "rack.errors" => $stderr,
- "rack.multithread" => false,
- "rack.multiprocess" => true,
- "rack.run_once" => false,
- "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
- })
+
+ rack_input = RewindableInput.new($stdin.read.to_s)
+
+ env.update(
+ "rack.version" => [1,1],
+ "rack.input" => rack_input,
+ "rack.errors" => $stderr,
+ "rack.multithread" => false,
+ "rack.multiprocess" => true,
+ "rack.run_once" => false,
+ "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http"
+ )
+
env["QUERY_STRING"] ||= ""
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["REQUEST_PATH"] ||= "/"
@@ -33,6 +39,8 @@ def self.serve(app)
ensure
body.close if body.respond_to? :close
end
+ ensure
+ rack_input.close
end
def self.send_headers(status, headers)
print "Status: #{status}\r\n"
View
18 lib/rack/handler/mongrel.rb
@@ -7,10 +7,14 @@ module Rack
module Handler
class Mongrel < ::Mongrel::HttpHandler
def self.run(app, options={})
- server = ::Mongrel::HttpServer.new(options[:Host] || '0.0.0.0',
- options[:Port] || 8080)
+ server = ::Mongrel::HttpServer.new(
+ options[:Host] || '0.0.0.0',
+ options[:Port] || 8080,
+ options[:num_processors] || 950,
+ options[:throttle] || 0,
+ options[:timeout] || 60)
# Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
- # Use is similar to #run, replacing the app argument with a hash of
+ # Use is similar to #run, replacing the app argument with a hash of
# { path=>app, ... } or an instance of Rack::URLMap.
if options[:map]
if app.is_a? Hash
@@ -45,8 +49,11 @@ def process(request, response)
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
- env.update({"rack.version" => [1,0],
- "rack.input" => request.body || StringIO.new(""),
+ rack_input = request.body || StringIO.new('')
+ rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
+
+ env.update({"rack.version" => [1,1],
+ "rack.input" => rack_input,
"rack.errors" => $stderr,
"rack.multithread" => true,
@@ -56,7 +63,6 @@ def process(request, response)
"rack.url_scheme" => "http",
})
env["QUERY_STRING"] ||= ""
- env.delete "PATH_INFO" if env["PATH_INFO"] == ""
status, headers, body = @app.call(env)
View
15 lib/rack/handler/scgi.rb
@@ -7,22 +7,22 @@ module Rack
module Handler
class SCGI < ::SCGI::Processor
attr_accessor :app
-
+
def self.run(app, options=nil)
new(options.merge(:app=>app,
:host=>options[:Host],
:port=>options[:Port],
:socket=>options[:Socket])).listen
end
-
+
def initialize(settings = {})
@app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app]))
@log = Object.new
def @log.info(*args); end
def @log.error(*args); end
super(settings)
end
-
+
def process_request(request, input_body, socket)
env = {}.replace(request)
env.delete "HTTP_CONTENT_TYPE"
@@ -32,10 +32,13 @@ def process_request(request, input_body, socket)
env["PATH_INFO"] = env["REQUEST_PATH"]
env["QUERY_STRING"] ||= ""
env["SCRIPT_NAME"] = ""
- env.update({"rack.version" => [1,0],
- "rack.input" => StringIO.new(input_body),
- "rack.errors" => $stderr,
+ rack_input = StringIO.new(input_body)
+ rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
+
+ env.update({"rack.version" => [1,1],
+ "rack.input" => rack_input,
+ "rack.errors" => $stderr,
"rack.multithread" => true,
"rack.multiprocess" => true,
"rack.run_once" => false,
View
12 lib/rack/handler/webrick.rb
@@ -6,6 +6,7 @@ module Rack
module Handler
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
def self.run(app, options={})
+ options[:BindAddress] = options.delete(:Host) if options[:Host]
server = ::WEBrick::HTTPServer.new(options)
server.mount "/", Rack::Handler::WEBrick, app
trap(:INT) { server.shutdown }
@@ -22,8 +23,11 @@ def service(req, res)
env = req.meta_vars
env.delete_if { |k, v| v.nil? }
- env.update({"rack.version" => [1,0],
- "rack.input" => StringIO.new(req.body.to_s),
+ rack_input = StringIO.new(req.body.to_s)
+ rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
+
+ env.update({"rack.version" => [1,1],
+ "rack.input" => rack_input,
"rack.errors" => $stderr,
"rack.multithread" => true,
@@ -36,9 +40,7 @@ def service(req, res)
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["QUERY_STRING"] ||= ""
env["REQUEST_PATH"] ||= "/"
- if env["PATH_INFO"] == ""
- env.delete "PATH_INFO"
- else
+ unless env["PATH_INFO"] == ""
path, n = req.request_uri.path, env["SCRIPT_NAME"].length
env["PATH_INFO"] = path[n, path.length-n]
end
View
74 lib/rack/lint.rb
@@ -58,11 +58,11 @@ def _call(env)
## == The Environment
def check_env(env)
- ## The environment must be an true instance of Hash (no
- ## subclassing allowed) that includes CGI-like headers.
- ## The application is free to modify the environment.
+ ## The environment must be an instance of Hash that includes
+ ## CGI-like headers. The application is free to modify the
+ ## environment.
assert("env #{env.inspect} is not a Hash, but #{env.class}") {
- env.instance_of? Hash
+ env.kind_of? Hash
}
##
@@ -112,7 +112,7 @@ def check_env(env)
## In addition to this, the Rack environment must include these
## Rack-specific variables:
- ## <tt>rack.version</tt>:: The Array [1,0], representing this version of Rack.
+ ## <tt>rack.version</tt>:: The Array [1,1], representing this version of Rack.
## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
## <tt>rack.input</tt>:: See below, the input stream.
## <tt>rack.errors</tt>:: See below, the error stream.
@@ -149,6 +149,35 @@ def check_env(env)
}
end
+ ## <tt>rack.logger</tt>:: A common object interface for logging messages.
+ ## The object must implement:
+ if logger = env['rack.logger']
+ ## info(message, &block)
+ assert("logger #{logger.inspect} must respond to info") {
+ logger.respond_to?(:info)
+ }
+
+ ## debug(message, &block)
+ assert("logger #{logger.inspect} must respond to debug") {
+ logger.respond_to?(:debug)
+ }
+
+ ## warn(message, &block)
+ assert("logger #{logger.inspect} must respond to warn") {
+ logger.respond_to?(:warn)
+ }
+
+ ## error(message, &block)
+ assert("logger #{logger.inspect} must respond to error") {
+ logger.respond_to?(:error)
+ }
+
+ ## fatal(message, &block)
+ assert("logger #{logger.inspect} must respond to fatal") {
+ logger.respond_to?(:fatal)
+ }
+ end
+
## The server or the application can store their own data in the
## environment, too. The keys must contain at least one dot,
## and should be prefixed uniquely. The prefix <tt>rack.</tt>
@@ -176,7 +205,7 @@ def check_env(env)
env.each { |key, value|
next if key.include? "." # Skip extensions
assert("env variable #{key} has non-string value #{value.inspect}") {
- value.instance_of? String
+ value.kind_of? String
}
}
@@ -185,7 +214,7 @@ def check_env(env)
## * <tt>rack.version</tt> must be an array of Integers.
assert("rack.version must be an Array, was #{env["rack.version"].class}") {
- env["rack.version"].instance_of? Array
+ env["rack.version"].kind_of? Array
}
## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") {
@@ -234,8 +263,17 @@ def check_env(env)
## === The Input Stream
##
## The input stream is an IO-like object which contains the raw HTTP
- ## POST data. If it is a file then it must be opened in binary mode.
+ ## POST data.
def check_input(input)
+ ## When applicable, its external encoding must be "ASCII-8BIT" and it
+ ## must be opened in binary mode, for Ruby 1.9 compatibility.
+ assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
+ input.external_encoding.name == "ASCII-8BIT"
+ } if input.respond_to?(:external_encoding)
+ assert("rack.input #{input} is not opened in binary mode") {
+ input.binmode?
+ } if input.respond_to?(:binmode?)
+
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
[:gets, :each, :read, :rewind].each { |method|
assert("rack.input #{input} does not respond to ##{method}") {
@@ -261,7 +299,7 @@ def gets(*args)
assert("rack.input#gets called with arguments") { args.size == 0 }
v = @input.gets
assert("rack.input#gets didn't return a String") {
- v.nil? or v.instance_of? String
+ v.nil? or v.kind_of? String
}
v
end
@@ -292,18 +330,18 @@ def read(*args)
args[1].kind_of?(String)
}
end
-
+
v = @input.read(*args)
-
+
assert("rack.input#read didn't return nil or a String") {
- v.nil? or v.instance_of? String
+ v.nil? or v.kind_of? String
}
if args[0].nil?
assert("rack.input#read(nil) returned nil on EOF") {
!v.nil?
}
end
-
+
v
end
@@ -312,12 +350,12 @@ def each(*args)
assert("rack.input#each called with arguments") { args.size == 0 }
@input.each { |line|
assert("rack.input#each didn't yield a String") {
- line.instance_of? String
+ line.kind_of? String
}
yield line
}
end
-
+
## * +rewind+ must be called without arguments. It rewinds the input
## stream back to the beginning. It must not raise Errno::ESPIPE:
## that is, it may not be a pipe or a socket. Therefore, handler
@@ -365,7 +403,7 @@ def puts(str)
## * +write+ must be called with a single argument that is a String.
def write(str)
- assert("rack.errors#write not called with a String") { str.instance_of? String }
+ assert("rack.errors#write not called with a String") { str.kind_of? String }
@error.write str
end
@@ -399,7 +437,7 @@ def check_headers(header)
header.each { |key, value|
## The header keys must be Strings.
assert("header key must be a string, was #{key.class}") {
- key.instance_of? String
+ key.kind_of? String
}
## The header must not contain a +Status+ key,
assert("header must not contain Status") { key.downcase != "status" }
@@ -482,7 +520,7 @@ def each
@body.each { |part|
## and must only yield String values.
assert("Body yielded non-string value #{part.inspect}") {
- part.instance_of? String
+ part.kind_of? String
}
bytes += Rack::Utils.bytesize(part)
yield part
View
20 lib/rack/logger.rb
@@ -0,0 +1,20 @@
+require 'logger'
+
+module Rack
+ # Sets up rack.logger to write to rack.errors stream
+ class Logger
+ def initialize(app, level = ::Logger::INFO)
+ @app, @level = app, level
+ end
+
+ def call(env)
+ logger = ::Logger.new(env['rack.errors'])
+ logger.level = @level
+
+ env['rack.logger'] = logger
+ @app.call(env)
+ ensure
+ logger.close
+ end
+ end
+end
View
4 lib/rack/mime.rb
@@ -14,7 +14,7 @@ module Mime
# Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
def mime_type(ext, fallback='application/octet-stream')
- MIME_TYPES.fetch(ext, fallback)
+ MIME_TYPES.fetch(ext.to_s.downcase, fallback)
end
module_function :mime_type
@@ -105,6 +105,7 @@ def mime_type(ext, fallback='application/octet-stream')
".m3u" => "audio/x-mpegurl",
".m4v" => "video/mp4",
".man" => "text/troff",
+ ".manifest"=> "text/cache-manifest",
".mathml" => "application/mathml+xml",
".mbox" => "application/mbox",
".mdoc" => "text/troff",
@@ -126,6 +127,7 @@ def mime_type(ext, fallback='application/octet-stream')
".ods" => "application/vnd.oasis.opendocument.spreadsheet",
".odt" => "application/vnd.oasis.opendocument.text",
".ogg" => "application/ogg",
+ ".ogv" => "video/ogg",
".p" => "text/x-pascal",
".pas" => "text/x-pascal",
".pbm" => "image/x-portable-bitmap",
View
13 lib/rack/mock.rb
@@ -40,7 +40,7 @@ def string
end
DEFAULT_ENV = {
- "rack.version" => [1,0],
+ "rack.version" => [1,1],
"rack.input" => StringIO.new,
"rack.errors" => StringIO.new,
"rack.multithread" => true,
@@ -114,13 +114,18 @@ def self.env_for(uri="", opts={})
end
end
- opts[:input] ||= ""
+ empty_str = ""
+ empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding
+ opts[:input] ||= empty_str
if String === opts[:input]
- env["rack.input"] = StringIO.new(opts[:input])
+ rack_input = StringIO.new(opts[:input])
else
- env["rack.input"] = opts[:input]
+ rack_input = opts[:input]
end
+ rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
+ env['rack.input'] = rack_input
+
env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s
opts.each { |field, value|
View
18 lib/rack/nulllogger.rb
@@ -0,0 +1,18 @@
+module Rack
+ class NullLogger
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env['rack.logger'] = self
+ @app.call(env)
+ end
+
+ def info(progname = nil, &block); end
+ def debug(progname = nil, &block); end
+ def warn(progname = nil, &block); end
+ def error(progname = nil, &block); end
+ def fatal(progname = nil, &block); end
+ end
+end
View
9 lib/rack/reloader.rb
@@ -1,5 +1,6 @@
# Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
-# All files in this distribution are subject to the terms of the Ruby license.
+# Rack::Reloader is subject to the terms of an MIT-style license.
+# See COPYING or http://www.opensource.org/licenses/mit-license.php.
require 'pathname'
@@ -70,7 +71,7 @@ def rotation
next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
found, stat = figure_path(file, paths)
- next unless found and stat and mtime = stat.mtime
+ next unless found && stat && mtime = stat.mtime
@cache[file] = found
@@ -87,11 +88,13 @@ def figure_path(file, paths)
found, stat = safe_stat(found)
return found, stat if found
- paths.each do |possible_path|
+ paths.find do |possible_path|
path = ::File.join(possible_path, file)
found, stat = safe_stat(path)
return ::File.expand_path(found), stat if found
end
+
+ return false, false
end
def safe_stat(file)
View
51 lib/rack/request.rb
@@ -32,6 +32,7 @@ def content_length; @env['CONTENT_LENGTH'] end
def content_type; @env['CONTENT_TYPE'] end
def session; @env['rack.session'] ||= {} end
def session_options; @env['rack.session.options'] ||= {} end
+ def logger; @env['rack.logger'] end
# The media type (type/subtype) portion of the CONTENT_TYPE header
# without any media type parameters. e.g., when CONTENT_TYPE is
@@ -63,9 +64,17 @@ def content_charset
media_type_params['charset']
end
+ def host_with_port
+ if forwarded = @env["HTTP_X_FORWARDED_HOST"]
+ forwarded.split(/,\s?/).last
+ else
+ @env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
+ end
+ end
+
def host
# Remove port number.
- (@env["HTTP_HOST"] || @env["SERVER_NAME"]).gsub(/:\d+\z/, '')
+ host_with_port.to_s.gsub(/:\d+\z/, '')
end
def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
@@ -81,7 +90,6 @@ def head?; request_method == "HEAD" end
# one of the media types presents in this list will not be eligible
# for form-data / param parsing.
FORM_DATA_MEDIA_TYPES = [
- nil,
'application/x-www-form-urlencoded',
'multipart/form-data'
]
@@ -92,15 +100,20 @@ def head?; request_method == "HEAD" end
PARSEABLE_DATA_MEDIA_TYPES = [
'multipart/related',
'multipart/mixed'
- ]
+ ]
# Determine whether the request body contains form-data by checking
- # the request media_type against registered form-data media-types:
- # "application/x-www-form-urlencoded" and "multipart/form-data". The
+ # the request Content-Type for one of the media-types:
+ # "application/x-www-form-urlencoded" or "multipart/form-data". The
# list of form-data media types can be modified through the
# +FORM_DATA_MEDIA_TYPES+ array.
+ #
+ # A request body is also assumed to contain form-data when no
+ # Content-Type header is provided and the request_method is POST.
def form_data?
- FORM_DATA_MEDIA_TYPES.include?(media_type)
+ type = media_type
+ meth = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']
+ (meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
end
# Determine whether the request body contains data by checking
@@ -115,8 +128,7 @@ def GET
@env["rack.request.query_hash"]
else
@env["rack.request.query_string"] = query_string
- @env["rack.request.query_hash"] =
- Utils.parse_nested_query(query_string)
+ @env["rack.request.query_hash"] = parse_query(query_string)
end
end