Skip to content
Snippets Groups Projects
Commit b4dfa912 authored by Apertis CI's avatar Apertis CI
Browse files

Import Upstream version 2.2.6.4

parent d2f77b21
Branches upstream/buster
Tags upstream/2.1.1
1 merge request!5Update from debian/bookworm for apertis/v2024dev2
Pipeline #878745 skipped
Showing with 1178 additions and 249 deletions
workflows:
version: 2
test:
jobs:
- test-jruby
- test-ruby-2.2
- test-ruby-2.3
- test-ruby-2.4
- test-ruby-2.5
- test-ruby-2.6
- test-ruby-2.7
version: 2
default-steps: &default-steps
- checkout
- run: sudo apt-get install lighttpd libfcgi-dev libmemcached-dev
# Restore bundle cache
- type: cache-restore
key: rack-{{ checksum "rack.gemspec" }}-{{ checksum "Gemfile" }}
# Bundle install dependencies
- run: bundle install --path vendor/bundle
# Store bundle cache
- type: cache-save
key: rack-{{ checksum "rack.gemspec" }}-{{ checksum "Gemfile" }}
paths:
- vendor/bundle
- run: bundle exec rubocop
- run: bundle exec rake ci
jobs:
test-ruby-2.2:
docker:
- image: circleci/ruby:2.2
# Spawn a process owned by root
# This works around an issue explained here:
# https://github.com/circleci/circleci-images/pull/132
command: sudo /bin/sh
- image: memcached:1.4
steps: *default-steps
test-ruby-2.3:
docker:
- image: circleci/ruby:2.3
# Spawn a process owned by root
# This works around an issue explained here:
# https://github.com/circleci/circleci-images/pull/132
command: sudo /bin/sh
- image: memcached:1.4
steps: *default-steps
test-ruby-2.4:
docker:
- image: circleci/ruby:2.4
# Spawn a process owned by root
# This works around an issue explained here:
# https://github.com/circleci/circleci-images/pull/132
command: sudo /bin/sh
- image: memcached:1.4
steps: *default-steps
test-ruby-2.5:
docker:
- image: circleci/ruby:2.5
# Spawn a process owned by root
# This works around an issue explained here:
# https://github.com/circleci/circleci-images/pull/132
command: sudo /bin/sh
- image: memcached:1.4
steps: *default-steps
test-ruby-2.6:
docker:
- image: circleci/ruby:2.6
# Spawn a process owned by root
# This works around an issue explained here:
# https://github.com/circleci/circleci-images/pull/132
command: sudo /bin/sh
- image: memcached:1.4
steps: *default-steps
test-ruby-2.7:
docker:
- image: circleci/ruby:2.7
# Spawn a process owned by root
# This works around an issue explained here:
# https://github.com/circleci/circleci-images/pull/132
command: sudo /bin/sh
- image: memcached:1.4
steps: *default-steps
test-jruby:
docker:
- image: circleci/jruby
# Spawn a process owned by root
# This works around an issue explained here:
# https://github.com/circleci/circleci-images/pull/132
command: sudo /bin/sh
- image: memcached:1.4
steps: *default-steps
name: Development
on: [push, pull_request]
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04]
ruby: [2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2]
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{matrix.ruby}}
- uses: actions/cache@v1
with:
path: vendor/bundle
key: bundle-use-ruby-${{matrix.os}}-${{matrix.ruby}}-${{hashFiles('**/Gemfile')}}
restore-keys: |
bundle-use-ruby-${{matrix.os}}-${{matrix.ruby}}-
- name: Installing packages
run: sudo apt-get install libfcgi-dev libmemcached-dev
- name: Bundle install...
run: |
bundle config path vendor/bundle
bundle install
- run: bundle exec rake
......@@ -10,3 +10,4 @@ Gemfile.lock
doc
/.bundle
/.yardoc
/coverage
AllCops:
TargetRubyVersion: 2.2
TargetRubyVersion: 2.3
DisabledByDefault: true
Exclude:
- '**/vendor/**/*'
......@@ -14,6 +14,9 @@ Style/FrozenStringLiteralComment:
Style/HashSyntax:
Enabled: true
Style/MethodDefParentheses:
Enabled: true
Layout/EmptyLineAfterMagicComment:
Enabled: true
......@@ -46,3 +49,9 @@ Layout/SpaceBeforeFirstArg:
# Use `{ a: 1 }` not `{a:1}`.
Layout/SpaceInsideHashLiteralBraces:
Enabled: true
Layout/Tab:
Enabled: true
Layout/TrailingWhitespace:
Enabled: true
This diff is collapsed.
Contributing to Rack
=====================
Rack is work of [hundreds of contributors](https://github.com/rack/rack/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/rack/rack/pulls), [propose features and discuss issues](https://github.com/rack/rack/issues). When in doubt, post to the [rack-devel](http://groups.google.com/group/rack-devel) mailing list.
#### Fork the Project
Fork the [project on Github](https://github.com/rack/rack) and check out your copy.
```
git clone https://github.com/contributor/rack.git
cd rack
git remote add upstream https://github.com/rack/rack.git
```
#### Create a Topic Branch
Make sure your fork is up-to-date and create a topic branch for your feature or bug fix.
```
git checkout master
git pull upstream master
git checkout -b my-feature-branch
```
#### Bundle Install and Quick Test
Ensure that you can build the project and run quick tests.
```
bundle install --without extra
bundle exec rake test
```
#### Running All Tests
Install all dependencies.
```
bundle install
```
Run all tests.
```
rake test
```
The test suite has no dependencies outside of the core Ruby installation and bacon.
Some tests will be skipped if a dependency is not found.
To run the test suite completely, you need:
* fcgi
* dalli
* thin
To test Memcache sessions, you need memcached (will be run on port 11211) and dalli installed.
#### Write Tests
Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build.
We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix.
#### Write Code
Implement your feature or bug fix.
Make sure that `bundle exec rake fulltest` completes without errors.
#### Write Documentation
Document any external behavior in the [README](README.rdoc).
#### Update Changelog
Add a line to [CHANGELOG](CHANGELOG.md).
#### Commit Changes
Make sure git knows your name and email address:
```
git config --global user.name "Your Name"
git config --global user.email "contributor@example.com"
```
Writing good commit logs is important. A commit log should describe what changed and why.
```
git add ...
git commit
```
#### Push
```
git push origin my-feature-branch
```
#### Make a Pull Request
Go to https://github.com/contributor/rack and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days.
#### Rebase
If you've been working on a change for a while, rebase with upstream/master.
```
git fetch upstream
git rebase upstream/master
git push origin my-feature-branch -f
```
#### Make Required Changes
Amend your previous commit and force push the changes.
```
git commit --amend
git push origin my-feature-branch -f
```
#### Check on Your Pull Request
Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above.
#### Be Patient
It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there!
#### Thank You
Please do know that we really appreciate and value your time and work. We love you, really.
......@@ -7,12 +7,17 @@ gemspec
# What we need to do here is just *exclude* JRuby, but bundler has no way to do
# this, because of some argument that I know I had with Yehuda and Carl years
# ago, but I've since forgotten. Anyway, we actually need it here, and it's not
# avaialable, so prepare yourself for a yak shave when this breaks.
# available, so prepare yourself for a yak shave when this breaks.
c_platforms = Bundler::Dsl::VALID_PLATFORMS.dup.delete_if do |platform|
platform =~ /jruby/
end
gem "rubocop", "0.68.1", require: false
gem "rubocop", require: false
group :test do
gem "webrick" # gemified in Ruby 3.1+
gem "psych"
end
# Alternative solution that might work, but it has bad interactions with
# Gemfile.lock if that gets committed/reused:
......
......@@ -5,6 +5,7 @@
{<img src="https://circleci.com/gh/rack/rack.svg?style=svg" alt="CircleCI" />}[https://circleci.com/gh/rack/rack]
{<img src="https://badge.fury.io/rb/rack.svg" alt="Gem Version" />}[http://badge.fury.io/rb/rack]
{<img src="https://api.dependabot.com/badges/compatibility_score?dependency-name=rack&package-manager=bundler&version-scheme=semver" alt="SemVer Stability" />}[https://dependabot.com/compatibility-score.html?dependency-name=rack&package-manager=bundler&version-scheme=semver]
{<img src="http://inch-ci.org/github/rack/rack.svg?branch=master" alt="Inline docs" />}[http://inch-ci.org/github/rack/rack]
\Rack provides a minimal, modular, and adaptable interface for developing
web applications in Ruby. By wrapping HTTP requests and responses in
......@@ -30,10 +31,11 @@ These web servers include \Rack handlers in their distributions:
* Agoo[https://github.com/ohler55/agoo]
* Falcon[https://github.com/socketry/falcon]
* Iodine[https://github.com/boazsegev/iodine]
* {NGINX Unit}[https://unit.nginx.org/]
* {Phusion Passenger}[https://www.phusionpassenger.com/] (which is mod_rack for Apache and for nginx)
* Puma[https://puma.io/]
* Unicorn[https://bogomips.org/unicorn/]
* Unicorn[https://yhbt.net/unicorn/]
* uWSGI[https://uwsgi-docs.readthedocs.io/en/latest/]
Any valid \Rack app will run the same on all these handlers, without
......@@ -41,13 +43,12 @@ changing anything.
== Supported web frameworks
These frameworks include \Rack adapters in their distributions:
These frameworks and many others support the \Rack API:
* Camping[http://www.ruby-camping.com/]
* Coset[http://leahneukirchen.org/repos/coset/]
* Hanami[https://hanamirb.org/]
* Padrino[http://padrinorb.com/]
* Racktools::SimpleApplication
* Ramaze[http://ramaze.net/]
* Roda[https://github.com/jeremyevans/roda]
* {Ruby on Rails}[https://rubyonrails.org/]
......@@ -55,19 +56,45 @@ These frameworks include \Rack adapters in their distributions:
* Sinatra[http://sinatrarb.com/]
* Utopia[https://github.com/socketry/utopia]
* WABuR[https://github.com/ohler55/wabur]
* ... and many others.
== Available middleware
== Available middleware shipped with \Rack
Between the server and the framework, \Rack can be customized to your
applications needs using middleware, for example:
applications needs using middleware. \Rack itself ships with the following
middleware:
* Rack::URLMap, to route to multiple applications inside the same process.
* Rack::Chunked, for streaming responses using chunked encoding.
* Rack::CommonLogger, for creating Apache-style logfiles.
* Rack::ConditionalGet, for returning not modified responses when the response
has not changed.
* Rack::Config, for modifying the environment before processing the request.
* Rack::ContentLength, for setting Content-Length header based on body size.
* Rack::ContentType, for setting default Content-Type header for responses.
* Rack::Deflater, for compressing responses with gzip.
* Rack::ETag, for setting ETag header on string bodies.
* Rack::Events, for providing easy hooks when a request is received
and when the response is sent.
* Rack::Files, for serving static files.
* Rack::Head, for returning an empty body for HEAD requests.
* Rack::Lint, for checking conformance to the \Rack API.
* Rack::Lock, for serializing requests using a mutex.
* Rack::Logger, for setting a logger to handle logging errors.
* Rack::MethodOverride, for modifying the request method based on a submitted
parameter.
* Rack::Recursive, for including data from other paths in the application,
and for performing internal redirects.
* Rack::Reloader, for reloading files if they have been modified.
* Rack::Runtime, for including a response header with the time taken to
process the request.
* Rack::Sendfile, for working with web servers that can use optimized
file serving for file system paths.
* Rack::ShowException, for catching unhandled exceptions and
presenting them in a nice and helpful way with clickable backtrace.
* Rack::Files, for serving static files.
* ...many others!
* Rack::ShowStatus, for using nice error pages for empty client error
responses.
* Rack::Static, for more configurable serving of static files.
* Rack::TempfileReaper, for removing temporary files creating during a
request.
All these components use the same interface, which is described in
detail in the \Rack specification. These optional components can be
......@@ -86,6 +113,15 @@ over:
cookie handling.
* Rack::MockRequest and Rack::MockResponse for efficient and quick
testing of \Rack application without real HTTP round-trips.
* Rack::Cascade, for trying additional \Rack applications if an
application returns a not found or method not supported response.
* Rack::Directory, for serving files under a given directory, with
directory indexes.
* Rack::MediaType, for parsing Content-Type headers.
* Rack::Mime, for determining Content-Type based on file extension.
* Rack::RewindableInput, for making any IO object rewindable, using
a temporary file buffer.
* Rack::URLMap, to route to multiple applications inside the same process.
== rack-contrib
......@@ -125,62 +161,80 @@ A Gem of \Rack is available at {rubygems.org}[https://rubygems.org/gems/rack]. Y
gem install rack
== Running the tests
== Usage
Testing \Rack requires the bacon testing framework:
You should require the library:
bundle install --without extra # to be able to run the fast tests
require 'rack'
Or:
\Rack uses autoload to automatically load other files \Rack ships with on demand,
so you should not need require paths under +rack+. If you require paths under
+rack+ without requiring +rack+ itself, things may not work correctly.
bundle install # this assumes that you have installed native extensions!
== Configuration
There is a rake-based test task:
Several parameters can be modified on Rack::Utils to configure \Rack behaviour.
rake test # tests all the tests
e.g:
The testsuite has no dependencies outside of the core Ruby
installation and bacon.
Rack::Utils.key_space_limit = 128
To run the test suite completely, you need:
=== key_space_limit
* fcgi
* dalli
* thin
The default number of bytes to allow all parameters keys in a given parameter hash to take up.
Does not affect nested parameter hashes, so doesn't actually prevent an attacker from using
more than this many bytes for parameter keys.
To test Memcache sessions, you need memcached (will be
run on port 11211) and dalli installed.
Defaults to 65536 characters.
== Configuration
=== param_depth_limit
Several parameters can be modified on Rack::Utils to configure \Rack behaviour.
The maximum amount of nesting allowed in parameters.
For example, if set to 3, this query string would be allowed:
e.g:
?a[b][c]=d
Rack::Utils.key_space_limit = 128
but this query string would not be allowed:
=== key_space_limit
?a[b][c][d]=e
The default number of bytes to allow a single parameter key to take up.
This helps prevent a rogue client from flooding a Request.
Limiting the depth prevents a possible stack overflow when parsing parameters.
Default to 65536 characters (4 kiB in worst case).
Defaults to 100.
=== multipart_part_limit
=== multipart_file_limit
The maximum number of parts a request can contain.
The maximum number of parts with a filename a request can contain.
Accepting too many part can lead to the server running out of file handles.
The default is 128, which means that a single request can't upload more than 128 files at once.
Set to 0 for no limit.
Can also be set via the +RACK_MULTIPART_PART_LIMIT+ environment variable.
Can also be set via the +RACK_MULTIPART_FILE_LIMIT+ environment variable.
(This is also aliased as +multipart_part_limit+ and +RACK_MULTIPART_PART_LIMIT+ for compatibility)
=== multipart_total_part_limit
The maximum total number of parts a request can contain of any type, including
both file and non-file form fields.
The default is 4096, which means that a single request can't contain more than
4096 parts.
Set to 0 for no limit.
Can also be set via the +RACK_MULTIPART_TOTAL_PART_LIMIT+ environment variable.
== Changelog
See {CHANGELOG.md}[https://github.com/rack/rack/blob/master/CHANGELOG.md].
== Contributing
See {CONTRIBUTING.md}[https://github.com/rack/rack/blob/master/CONTRIBUTING.md].
== Contact
Please post bugs, suggestions and patches to
......@@ -198,7 +252,6 @@ Mailing list archives are available at
Git repository (send Git patches to the mailing list):
* https://github.com/rack/rack
* http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack-github.git
You are also welcome to join the #rack channel on irc.freenode.net.
......@@ -206,20 +259,25 @@ You are also welcome to join the #rack channel on irc.freenode.net.
The \Rack Core Team, consisting of
* Aaron Patterson (tenderlove[https://github.com/tenderlove])
* Samuel Williams (ioquatix[https://github.com/ioquatix])
* Jeremy Evans (jeremyevans[https://github.com/jeremyevans])
* Eileen Uchitelle (eileencodes[https://github.com/eileencodes])
* Matthew Draper (matthewd[https://github.com/matthewd])
* Rafael França (rafaelfranca[https://github.com/rafaelfranca])
and the \Rack Alumni
* Ryan Tomayko (rtomayko[https://github.com/rtomayko])
* Scytrin dai Kinthra (scytrin[https://github.com/scytrin])
* Leah Neukirchen (leahneukirchen[https://github.com/leahneukirchen])
* James Tucker (raggi[https://github.com/raggi])
* Josh Peek (josh[https://github.com/josh])
* José Valim (josevalim[https://github.com/josevalim])
* Michael Fellinger (manveru[https://github.com/manveru])
* Aaron Patterson (tenderlove[https://github.com/tenderlove])
* Santiago Pastorino (spastorino[https://github.com/spastorino])
* Konstantin Haase (rkh[https://github.com/rkh])
and the \Rack Alumnis
* Ryan Tomayko (rtomayko[https://github.com/rtomayko])
* Scytrin dai Kinthra (scytrin[https://github.com/scytrin])
would like to thank:
* Adrian Madrid, for the LiteSpeed handler.
......
# frozen_string_literal: true
require "bundler/gem_tasks"
require "rake/testtask"
desc "Run all the tests"
......@@ -20,7 +21,7 @@ end
desc "Make an archive as .tar.gz"
task dist: %w[chmod changelog spec rdoc] do
sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar"
sh "pax -waf #{release}.tar -s ':^:#{release}/:' SPEC ChangeLog doc rack.gemspec"
sh "pax -waf #{release}.tar -s ':^:#{release}/:' SPEC.rdoc ChangeLog doc rack.gemspec"
sh "gzip -f -9 #{release}.tar"
end
......@@ -38,7 +39,7 @@ task officialrelease_really: %w[spec dist gem] do
end
def release
"rack-" + File.read('lib/rack.rb')[/RELEASE += +([\"\'])([\d][\w\.]+)\1/, 2]
"rack-" + File.read('lib/rack/version.rb')[/RELEASE += +([\"\'])([\d][\w\.]+)\1/, 2]
end
desc "Make binaries executable"
......@@ -71,13 +72,13 @@ file "ChangeLog" => '.git/index' do
end
desc "Generate Rack Specification"
task spec: "SPEC"
task spec: "SPEC.rdoc"
file 'lib/rack/lint.rb'
file "SPEC" => 'lib/rack/lint.rb' do
File.open("SPEC", "wb") { |file|
file "SPEC.rdoc" => 'lib/rack/lint.rb' do
File.open("SPEC.rdoc", "wb") { |file|
IO.foreach("lib/rack/lint.rb") { |line|
if line =~ /## (.*)/
if line =~ /^\s*## ?(.*)/
file.puts $1
end
}
......@@ -91,6 +92,12 @@ Rake::TestTask.new("test:regular") do |t|
t.verbose = true
end
desc "Run tests with coverage"
task "test_cov" do
ENV['COVERAGE'] = '1'
Rake::Task['test:regular'].invoke
end
desc "Run all the fast + platform agnostic tests"
task test: %w[spec test:regular]
......@@ -107,7 +114,7 @@ desc "Generate RDoc documentation"
task rdoc: %w[changelog spec] do
sh(*%w{rdoc --line-numbers --main README.rdoc
--title 'Rack\ Documentation' --charset utf-8 -U -o doc} +
%w{README.rdoc KNOWN-ISSUES SPEC ChangeLog} +
%w{README.rdoc KNOWN-ISSUES SPEC.rdoc ChangeLog} +
`git ls-files lib/\*\*/\*.rb`.strip.split)
cp "contrib/rdoc.css", "doc/rdoc.css"
end
......
......@@ -10,26 +10,26 @@ New features will only be added to the master branch and will not be made availa
Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from.
* Current release series: 2.0.x
* Current release series: 2.1.x
### Security issues
The current release series and the next most recent one will receive patches and new versions in case of a security issue.
* Current release series: 2.0.x
* Next most recent release series: 1.6.x
* Current release series: 2.1.x
* Next most recent release series: 2.0.x
### Severe security issues
For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team.
* Current release series: 2.0.x
* Next most recent release series: 1.6.x
* Last most recent release series: 1.5.x
* Current release series: 2.1.x
* Next most recent release series: 2.0.x
* Last major release series: 1.6.x
### Unsupported Release Series
When a release series is no longer supported, it’s your own responsibility to deal with bugs and security issues. We may provide back-ports of the fixes and publish them to git, however there will be no new versions released. If you are not comfortable maintaining your own versions, you should upgrade to a supported version.
When a release series is no longer supported, it’s your own responsibility to deal with bugs and security issues. If you are not comfortable maintaining your own versions, you should upgrade to a supported version.
## Reporting a bug
......
This specification aims to formalize the Rack protocol. You
can (and should) use Rack::Lint to enforce it.
When you develop middleware, be sure to add a Lint before and
after to catch all mistakes.
= Rack applications
......@@ -11,9 +12,10 @@ The *status*,
the *headers*,
and the *body*.
== The Environment
The environment must be an instance of Hash that includes
The environment must be an unfrozen instance of Hash that includes
CGI-like headers. The application is free to modify the
environment.
The environment is required to include these variables
(adopted from PEP333), except when they'd be empty, but see
below.
......@@ -104,6 +106,7 @@ be implemented by the server.
fetch(key, default = nil) (aliased as []);
delete(key);
clear;
to_hash (returning unfrozen Hash instance);
<tt>rack.logger</tt>:: A common object interface for logging messages.
The object must implement:
info(message, &block)
......@@ -118,10 +121,13 @@ environment, too. The keys must contain at least one dot,
and should be prefixed uniquely. The prefix <tt>rack.</tt>
is reserved for use with the Rack core distribution and other
accepted specifications and must not be used otherwise.
The environment must not contain the keys
<tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
(use the versions without <tt>HTTP_</tt>).
The CGI keys (named without a period) must have String values.
If the string values for CGI keys contain non-ASCII characters,
they should use ASCII-8BIT encoding.
There are the following restrictions:
* <tt>rack.version</tt> must be an array of Integers.
* <tt>rack.url_scheme</tt> must either be +http+ or +https+.
......@@ -137,6 +143,7 @@ There are the following restrictions:
<tt>SCRIPT_NAME</tt> is empty.
<tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
=== The Input Stream
The input stream is an IO-like object which contains the raw HTTP
POST data.
When applicable, its external encoding must be "ASCII-8BIT" and it
......@@ -146,14 +153,19 @@ The input stream must respond to +gets+, +each+, +read+ and +rewind+.
or +nil+ on EOF.
* +read+ behaves like IO#read.
Its signature is <tt>read([length, [buffer]])</tt>.
If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
and +buffer+ must be a String and may not be nil.
If +length+ is given and not nil, then this method reads at most
+length+ bytes from the input stream.
If +length+ is not given or nil, then this method reads
all data until EOF.
When EOF is reached, this method returns nil if +length+ is given
and not nil, or "" if +length+ is not given or is nil.
If +buffer+ is given, then the read data will be placed
into +buffer+ instead of a newly created String object.
* +each+ must be called without arguments and only yield Strings.
......@@ -175,16 +187,20 @@ The error stream must respond to +puts+, +write+ and +flush+.
If rack.hijack? is true then rack.hijack must respond to #call.
rack.hijack must return the io that will also be assigned (or is
already present, in rack.hijack_io.
rack.hijack_io must respond to:
<tt>read, write, read_nonblock, write_nonblock, flush, close,
close_read, close_write, closed?</tt>
The semantics of these IO methods must be a best effort match to
those of a normal ruby IO or Socket object, using standard
arguments and raising standard exceptions. Servers are encouraged
to simply pass on real IO objects, although it is recognized that
this approach is not directly compatible with SPDY and HTTP 2.0.
IO provided in rack.hijack_io should preference the
IO::WaitReadable and IO::WaitWritable APIs wherever supported.
There is a deliberate lack of full specification around
rack.hijack_io, as semantics will change from server to server.
Users are encouraged to utilize this API with a knowledge of their
......@@ -192,7 +208,9 @@ server choice, and servers may extend the functionality of
hijack_io to provide additional features to users. The purpose of
rack.hijack is for Rack to "get out of the way", as such, Rack only
provides the minimum of specification and support.
If rack.hijack? is false, then rack.hijack should not be set.
If rack.hijack? is false, then rack.hijack_io should not be set.
==== Response (after headers)
It is also possible to hijack a response after the status and headers
......@@ -201,6 +219,7 @@ In order to do this, an application may set the special header
<tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
accepting an argument that conforms to the <tt>rack.hijack_io</tt>
protocol.
After the headers have been sent, and this hijack callback has been
called, the application is now responsible for the remaining lifecycle
of the IO. The application is also responsible for maintaining HTTP
......@@ -209,8 +228,10 @@ applications will have wanted to specify the header Connection:close in
HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
returning hijacked sockets to the web server. For that purpose, use the
body streaming API instead (progressively yielding strings via each).
Servers must ignore the <tt>body</tt> part of the response tuple when
the <tt>rack.hijack</tt> response API is in use.
The special response header <tt>rack.hijack</tt> must only be set
if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
==== Conventions
......@@ -244,16 +265,20 @@ There must not be a <tt>Content-Length</tt> header when the
=== The Body
The Body must respond to +each+
and must only yield String values.
The Body itself should not be an instance of String, as this will
break in Ruby 1.9.
If the Body responds to +close+, it will be called after iteration. If
the body is replaced by a middleware after action, the original body
must be closed first, if it responds to close.
If the Body responds to +to_path+, it must return a String
identifying the location of a file whose contents are identical
to that produced by calling +each+; this may be used by the
server as an alternative, possibly more efficient way to
transport the response.
The Body commonly is an Array of Strings, the application
instance itself, or a File-like object.
== Thanks
......
......@@ -11,23 +11,11 @@
# All modules meant for use in your application are <tt>autoload</tt>ed here,
# so it should be enough just to <tt>require 'rack'</tt> in your code.
module Rack
# The Rack protocol version number implemented.
VERSION = [1, 3]
# Return the Rack protocol version as a dotted string.
def self.version
VERSION.join(".")
end
RELEASE = "2.1.4"
# Return the Rack release as a dotted string.
def self.release
RELEASE
end
require_relative 'rack/version'
module Rack
HTTP_HOST = 'HTTP_HOST'
HTTP_PORT = 'HTTP_PORT'
HTTP_VERSION = 'HTTP_VERSION'
HTTPS = 'HTTPS'
PATH_INFO = 'PATH_INFO'
......@@ -37,9 +25,9 @@ module Rack
QUERY_STRING = 'QUERY_STRING'
SERVER_PROTOCOL = 'SERVER_PROTOCOL'
SERVER_NAME = 'SERVER_NAME'
SERVER_ADDR = 'SERVER_ADDR'
SERVER_PORT = 'SERVER_PORT'
CACHE_CONTROL = 'Cache-Control'
EXPIRES = 'Expires'
CONTENT_LENGTH = 'Content-Length'
CONTENT_TYPE = 'Content-Type'
SET_COOKIE = 'Set-Cookie'
......@@ -98,6 +86,7 @@ module Rack
autoload :ContentLength, "rack/content_length"
autoload :ContentType, "rack/content_type"
autoload :ETag, "rack/etag"
autoload :Events, "rack/events"
autoload :File, "rack/file"
autoload :Files, "rack/files"
autoload :Deflater, "rack/deflater"
......@@ -108,11 +97,13 @@ module Rack
autoload :Lint, "rack/lint"
autoload :Lock, "rack/lock"
autoload :Logger, "rack/logger"
autoload :MediaType, "rack/media_type"
autoload :MethodOverride, "rack/method_override"
autoload :Mime, "rack/mime"
autoload :NullLogger, "rack/null_logger"
autoload :Recursive, "rack/recursive"
autoload :Reloader, "rack/reloader"
autoload :RewindableInput, "rack/rewindable_input"
autoload :Runtime, "rack/runtime"
autoload :Sendfile, "rack/sendfile"
autoload :Server, "rack/server"
......
# frozen_string_literal: true
require 'rack/request'
module Rack
module Auth
class AbstractRequest
......
# frozen_string_literal: true
require 'rack/auth/abstract/handler'
require 'rack/auth/abstract/request'
require_relative 'abstract/handler'
require_relative 'abstract/request'
require 'base64'
module Rack
......@@ -44,7 +44,7 @@ module Rack
class Request < Auth::AbstractRequest
def basic?
"basic" == scheme
"basic" == scheme && credentials.length == 2
end
def credentials
......
# frozen_string_literal: true
require 'rack/auth/abstract/handler'
require 'rack/auth/digest/request'
require 'rack/auth/digest/params'
require 'rack/auth/digest/nonce'
require_relative '../abstract/handler'
require_relative 'request'
require_relative 'params'
require_relative 'nonce'
require 'digest/md5'
module Rack
......
# frozen_string_literal: true
require 'rack/auth/abstract/request'
require 'rack/auth/digest/params'
require 'rack/auth/digest/nonce'
require_relative '../abstract/request'
require_relative 'params'
require_relative 'nonce'
module Rack
module Auth
......
# frozen_string_literal: true
module Rack
# Proxy for response bodies allowing calling a block when
# the response body is closed (after the response has been fully
# sent to the client).
class BodyProxy
# Set the response body to wrap, and the block to call when the
# response has been fully sent.
def initialize(body, &block)
@body = body
@block = block
@closed = false
end
def respond_to?(method_name, include_all = false)
# Return whether the wrapped body responds to the method.
def respond_to_missing?(method_name, include_all = false)
super or @body.respond_to?(method_name, include_all)
end
# If not already closed, close the wrapped body and
# then call the block the proxy was initialized with.
def close
return if @closed
@closed = true
......@@ -22,20 +30,16 @@ module Rack
end
end
# Whether the proxy is closed. The proxy starts as not closed,
# and becomes closed on the first call to close.
def closed?
@closed
end
# N.B. This method is a special case to address the bug described by #434.
# We are applying this special case for #each only. Future bugs of this
# class will be handled by requesting users to patch their ruby
# implementation, to save adding too many methods in this class.
def each
@body.each { |body| yield body }
end
# Delegate missing methods to the wrapped body.
def method_missing(method_name, *args, &block)
@body.__send__(method_name, *args, &block)
end
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
end
end
......@@ -35,6 +35,32 @@ module Rack
# https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
UTF_8_BOM = '\xef\xbb\xbf'
# Parse the given config file to get a Rack application.
#
# If the config file ends in +.ru+, it is treated as a
# rackup file and the contents will be treated as if
# specified inside a Rack::Builder block, using the given
# options.
#
# If the config file does not end in +.ru+, it is
# required and Rack will use the basename of the file
# to guess which constant will be the Rack application to run.
# The options given will be ignored in this case.
#
# Examples:
#
# Rack::Builder.parse_file('config.ru')
# # Rack application built using Rack::Builder.new
#
# Rack::Builder.parse_file('app.rb')
# # requires app.rb, which can be anywhere in Ruby's
# # load path. After requiring, assumes App constant
# # contains Rack application
#
# Rack::Builder.parse_file('./my_app.rb')
# # requires ./my_app.rb, which should be in the
# # process's current directory. After requiring,
# # assumes MyApp constant contains Rack application
def self.parse_file(config, opts = Server::Options.new)
if config.end_with?('.ru')
return self.load_file(config, opts)
......@@ -45,6 +71,25 @@ module Rack
end
end
# Load the given file as a rackup file, treating the
# contents as if specified inside a Rack::Builder block.
#
# Treats the first comment at the beginning of a line
# that starts with a backslash as options similar to
# options passed on a rackup command line.
#
# Ignores content in the file after +__END__+, so that
# use of +__END__+ will not result in a syntax error.
#
# Example config.ru file:
#
# $ cat config.ru
#
# #\ -p 9393
#
# use Rack::ContentLength
# require './app.rb'
# run App
def self.load_file(path, opts = Server::Options.new)
options = {}
......@@ -52,6 +97,7 @@ module Rack
cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
if cfgfile[/^#\\(.*)/] && opts
warn "Parsing options from the first comment line is deprecated!"
options = opts.parse! $1.split(/\s+/)
end
......@@ -61,16 +107,26 @@ module Rack
return app, options
end
# Evaluate the given +builder_script+ string in the context of
# a Rack::Builder block, returning a Rack application.
def self.new_from_string(builder_script, file = "(rackup)")
eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
TOPLEVEL_BINDING, file, 0
# We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance.
# We cannot use instance_eval(String) as that would resolve constants differently.
binding, builder = TOPLEVEL_BINDING.eval('Rack::Builder.new.instance_eval { [binding, self] }')
eval builder_script, binding, file
builder.to_app
end
# Initialize a new Rack::Builder instance. +default_app+ specifies the
# default application if +run+ is not called later. If a block
# is given, it is evaluted in the context of the instance.
def initialize(default_app = nil, &block)
@use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
instance_eval(&block) if block_given?
end
# Create a new Rack::Builder instance and return the Rack application
# generated from it.
def self.app(default_app = nil, &block)
self.new(default_app, &block).to_app
end
......@@ -121,7 +177,8 @@ module Rack
@run = app
end
# Takes a lambda or block that is used to warm-up the application.
# Takes a lambda or block that is used to warm-up the application. This block is called
# before the Rack application is returned by to_app.
#
# warmup do |app|
# client = Rack::MockRequest.new(app)
......@@ -134,25 +191,31 @@ module Rack
@warmup = prc || block
end
# Creates a route within the application.
# Creates a route within the application. Routes under the mapped path will be sent to
# the Rack application specified by run inside the block. Other requests will be sent to the
# default application specified by run outside the block.
#
# Rack::Builder.app do
# map '/' do
# map '/heartbeat' do
# run Heartbeat
# end
# run App
# end
#
# The +use+ method can also be used here to specify middleware to run under a specific path:
# The +use+ method can also be used inside the block to specify middleware to run under a specific path:
#
# Rack::Builder.app do
# map '/' do
# map '/heartbeat' do
# use Middleware
# run Heartbeat
# end
# run App
# end
#
# This example includes a piece of middleware which will run before requests hit +Heartbeat+.
# This example includes a piece of middleware which will run before +/heartbeat+ requests hit +Heartbeat+.
#
# Note that providing a +path+ of +/+ will ignore any default application given in a +run+ statement
# outside the block.
def map(path, &block)
@map ||= {}
@map[path] = block
......@@ -164,6 +227,7 @@ module Rack
@freeze_app = true
end
# Return the Rack application generated by this instance.
def to_app
app = @map ? generate_map(@run, @map) : @run
fail "missing run or map statement" unless app
......@@ -173,12 +237,17 @@ module Rack
app
end
# Call the Rack application generated by this builder instance. Note that
# this rebuilds the Rack application and runs the warmup code (if any)
# every time it is called, so it should not be used if performance is important.
def call(env)
to_app.call(env)
end
private
# Generate a URLMap instance by generating new Rack applications for each
# map block in this instance.
def generate_map(default_app, mapping)
mapped = default_app ? { '/' => default_app } : {}
mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
......
......@@ -2,25 +2,37 @@
module Rack
# Rack::Cascade tries a request on several apps, and returns the
# first response that is not 404 or 405 (or in a list of configurable
# status codes).
# first response that is not 404 or 405 (or in a list of configured
# status codes). If all applications tried return one of the configured
# status codes, return the last response.
class Cascade
# deprecated, no longer used
NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
# An array of applications to try in order.
attr_reader :apps
def initialize(apps, catch = [404, 405])
# Set the apps to send requests to, and what statuses result in
# cascading. Arguments:
#
# apps: An enumerable of rack applications.
# cascade_for: The statuses to use cascading for. If a response is received
# from an app, the next app is tried.
def initialize(apps, cascade_for = [404, 405])
@apps = []
apps.each { |app| add app }
@catch = {}
[*catch].each { |status| @catch[status] = true }
@cascade_for = {}
[*cascade_for].each { |status| @cascade_for[status] = true }
end
# Call each app in order. If the responses uses a status that requires
# cascading, try the next app. If all responses require cascading,
# return the response from the last app.
def call(env)
result = NotFound
return [404, { CONTENT_TYPE => "text/plain" }, []] if @apps.empty?
result = nil
last_body = nil
@apps.each do |app|
......@@ -33,17 +45,20 @@ module Rack
last_body.close if last_body.respond_to? :close
result = app.call(env)
return result unless @cascade_for.include?(result[0].to_i)
last_body = result[2]
break unless @catch.include?(result[0].to_i)
end
result
end
# Append an app to the list of apps to cascade. This app will
# be tried last.
def add(app)
@apps << app
end
# Whether the given app is one of the apps to cascade to.
def include?(app)
@apps.include?(app)
end
......
# frozen_string_literal: true
require 'rack/utils'
module Rack
# Middleware that applies chunked transfer encoding to response bodies
# when the response does not include a Content-Length header.
#
# This supports the Trailer response header to allow the use of trailing
# headers in the chunked encoding. However, using this requires you manually
# specify a response body that supports a +trailers+ method. Example:
#
# [200, { 'Trailer' => 'Expires'}, ["Hello", "World"]]
# # error raised
#
# body = ["Hello", "World"]
# def body.trailers
# { 'Expires' => Time.now.to_s }
# end
# [200, { 'Trailer' => 'Expires'}, body]
# # No exception raised
class Chunked
include Rack::Utils
# A body wrapper that emits chunked responses
# A body wrapper that emits chunked responses.
class Body
TERM = "\r\n"
TAIL = "0#{TERM}"
include Rack::Utils
# Store the response body to be chunked.
def initialize(body)
@body = body
end
# For each element yielded by the response body, yield
# the element in chunked encoding.
def each(&block)
term = TERM
@body.each do |chunk|
size = chunk.bytesize
next if size == 0
chunk = chunk.b
yield [size.to_s(16), term, chunk, term].join
yield [size.to_s(16), term, chunk.b, term].join
end
yield TAIL
insert_trailers(&block)
yield TERM
yield_trailers(&block)
yield term
end
# Close the response body if the response body supports it.
def close
@body.close if @body.respond_to?(:close)
end
private
def insert_trailers(&block)
# Do nothing as this class does not support trailer headers.
def yield_trailers
end
end
# A body wrapper that emits chunked responses and also supports
# sending Trailer headers. Note that the response body provided to
# initialize must have a +trailers+ method that returns a hash
# of trailer headers, and the rack response itself should have a
# Trailer header listing the headers that the +trailers+ method
# will return.
class TrailerBody < Body
private
def insert_trailers(&block)
# Yield strings for each trailer header.
def yield_trailers
@body.trailers.each_pair do |k, v|
yield "#{k}: #{v}\r\n"
end
......@@ -58,10 +79,11 @@ module Rack
@app = app
end
# pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have
# a version (nor response headers)
# Whether the HTTP version supports chunked encoding (HTTP 1.1 does).
def chunkable_version?(ver)
case ver
# pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have
# a version (nor response headers)
when 'HTTP/1.0', nil, 'HTTP/0.9'
false
else
......@@ -69,24 +91,27 @@ module Rack
end
end
# If the rack app returns a response that should have a body,
# but does not have Content-Length or Transfer-Encoding headers,
# modify the response to use chunked Transfer-Encoding.
def call(env)
status, headers, body = @app.call(env)
headers = HeaderHash.new(headers)
headers = HeaderHash[headers]
if chunkable_version?(env[SERVER_PROTOCOL]) &&
!STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
!headers[CONTENT_LENGTH] &&
!headers[TRANSFER_ENCODING]
if ! chunkable_version?(env[SERVER_PROTOCOL]) ||
STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
headers[CONTENT_LENGTH] ||
headers[TRANSFER_ENCODING]
[status, headers, body]
else
headers.delete(CONTENT_LENGTH)
headers[TRANSFER_ENCODING] = 'chunked'
if headers['Trailer']
[status, headers, TrailerBody.new(body)]
body = TrailerBody.new(body)
else
[status, headers, Body.new(body)]
body = Body.new(body)
end
end
[status, headers, body]
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment