mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Merge branch 'master' into json-require
This commit is contained in:
commit
b93ac88d61
46 changed files with 557 additions and 172 deletions
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
@ -5,7 +5,7 @@ Please describe your pull request. Thank you for contributing! You're the best.
|
|||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] I have reviewed the [guidelines for contributing](../blob/master/CONTRIBUTING.md) to this repository.
|
||||
- [ ] I have added an entry to [History.md](../blob/master/History.md) if this PR fixes a bug or adds a feature. If it doesn't need an entry to HISTORY.md, I have added `[changelog skip]` the pull request title.
|
||||
- [ ] I have added an entry to [History.md](../blob/master/History.md) if this PR fixes a bug or adds a feature. If it doesn't need an entry to HISTORY.md, I have added `[changelog skip]` or `[ci skip]` to the pull request title.
|
||||
- [ ] I have added appropriate tests if this PR fixes a bug or adds a feature.
|
||||
- [ ] My pull request is 100 lines added/removed or less so that it can be easily reviewed.
|
||||
- [ ] If this PR doesn't need tests (docs change), I added `[ci skip]` to the title of the PR.
|
||||
|
|
4
.github/workflows/puma.yml
vendored
4
.github/workflows/puma.yml
vendored
|
@ -37,7 +37,6 @@ jobs:
|
|||
uses: MSP-Greg/setup-ruby-pkgs@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
bundler: 1
|
||||
apt-get: ragel
|
||||
brew: ragel
|
||||
mingw: _upgrade_ openssl ragel
|
||||
|
@ -49,13 +48,12 @@ jobs:
|
|||
if ('${{ matrix.ruby }}' -lt '2.3') {
|
||||
gem update --system 2.7.10 --no-document
|
||||
}
|
||||
bundle install --jobs 4 --retry 3 --path=.bundle/puma
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: compile
|
||||
run: bundle exec rake compile
|
||||
|
||||
- name: rubocop
|
||||
if: startsWith(matrix.ruby, '2.')
|
||||
run: bundle exec rake rubocop
|
||||
|
||||
- name: test
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -3,7 +3,7 @@ source "https://rubygems.org"
|
|||
gemspec
|
||||
|
||||
gem "rdoc"
|
||||
gem "rake-compiler"
|
||||
gem "rake-compiler", "~> 0.9.4"
|
||||
|
||||
gem "nio4r", "~> 2.0"
|
||||
gem "rack", "~> 1.6"
|
||||
|
|
20
History.md
20
History.md
|
@ -1,3 +1,9 @@
|
|||
### Master
|
||||
* Bugfixes
|
||||
* Resolve issue with threadpool waiting counter decrement when thread is killed
|
||||
* Constrain rake-compiler version to 0.9.4 to fix `ClassNotFound` exception when using MiniSSL with Java8.
|
||||
* Ensure that TCP_CORK is usable
|
||||
|
||||
## 5.0.0
|
||||
|
||||
* Features
|
||||
|
@ -6,11 +12,12 @@
|
|||
* EXPERIMENTAL: Added `nakayoshi_fork` option. Reduce memory usage in preloaded cluster-mode apps by GCing before fork and compacting, where available. (#2093, #2256)
|
||||
* Added pumactl `thread-backtraces` command to print thread backtraces (#2054)
|
||||
* Added incrementing `requests_count` to `Puma.stats`. (#2106)
|
||||
* Increased maximum URI path length from 2048 to 8196 bytes (#2167)
|
||||
* Increased maximum URI path length from 2048 to 8192 bytes (#2167, #2344)
|
||||
* `lowlevel_error_handler` is now called during a forced threadpool shutdown, and if a callable with 3 arguments is set, we now also pass the status code (#2203)
|
||||
* Faster phased restart and worker timeout (#2220)
|
||||
* Added `state_permission` to config DSL to set state file permissions (#2238)
|
||||
* Added `Puma.stats_hash`, which returns a stats in Hash instead of a JSON string (#2086, #2253)
|
||||
* `rack.multithread` and `rack.multiprocess` now dynamically resolved by `max_thread` and `workers` respectively (#2288)
|
||||
|
||||
* Deprecations, Removals and Breaking API Changes
|
||||
* `--control` has been removed. Use `--control-url` (#1487)
|
||||
|
@ -24,9 +31,11 @@
|
|||
* Daemonization has been removed without replacement. (#2170)
|
||||
* Changed #connected_port to #connected_ports (#2076)
|
||||
* Configuration: `environment` is read from `RAILS_ENV`, if `RACK_ENV` can't be found (#2022)
|
||||
* Log binding on http:// for TCP bindings to make it clickable
|
||||
|
||||
* Bugfixes
|
||||
* Fix JSON loading issues on phased-restarts (#2269)
|
||||
* Improve shutdown reliability (#2312, #2338)
|
||||
* Close client http connections made to an ssl server with TLSv1.3 (#2116)
|
||||
* Do not set user_config to quiet by default to allow for file config (#2074)
|
||||
* Always close SSL connection in Puma::ControlCLI (#2211)
|
||||
|
@ -46,6 +55,8 @@
|
|||
* Fix `UserFileDefaultOptions#fetch` to properly use `default` (#2233)
|
||||
* Improvements to `out_of_band` hook (#2234)
|
||||
* Prefer the rackup file specified by the CLI (#2225)
|
||||
* Fix for spawning subprocesses with fork_worker option (#2267)
|
||||
* Set `CONTENT_LENGTH` for chunked requests (#2287)
|
||||
|
||||
* Refactor
|
||||
* Remove unused loader argument from Plugin initializer (#2095)
|
||||
|
@ -58,6 +69,13 @@
|
|||
* Support parallel tests in verbose progress reporting (#2223)
|
||||
* Refactor error handling in server accept loop (#2239)
|
||||
|
||||
## 4.3.4/4.3.5 and 3.12.5/3.12.6 / 2020-05-22
|
||||
|
||||
Each patchlevel release contains a separate security fix. We recommend simply upgrading to 4.3.5/3.12.6.
|
||||
|
||||
* Security
|
||||
* Fix: Fixed two separate HTTP smuggling vulnerabilities that used the Transfer-Encoding header. CVE-2020-11076 and CVE-2020-11077.
|
||||
|
||||
## 4.3.3 and 3.12.4 / 2020-02-28
|
||||
|
||||
* Bugfixes
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
[![Code Climate](https://codeclimate.com/github/puma/puma.svg)](https://codeclimate.com/github/puma/puma)
|
||||
[![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=puma&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=puma&package-manager=bundler&version-scheme=semver)
|
||||
[![StackOverflow](http://img.shields.io/badge/stackoverflow-Puma-blue.svg)]( http://stackoverflow.com/questions/tagged/puma )
|
||||
[![StackOverflow](https://img.shields.io/badge/stackoverflow-Puma-blue.svg)]( https://stackoverflow.com/questions/tagged/puma )
|
||||
|
||||
Puma is a **simple, fast, multi-threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications**.
|
||||
|
||||
|
@ -27,7 +27,7 @@ $ gem install puma
|
|||
$ puma
|
||||
```
|
||||
|
||||
Without arguments, puma will look for a rackup (.ru) file in
|
||||
Without arguments, puma will look for a rackup (.ru) file in
|
||||
working directory called `config.ru`.
|
||||
|
||||
## Frameworks
|
||||
|
@ -135,7 +135,7 @@ Preloading can’t be used with phased restart, since phased restart kills and r
|
|||
If puma encounters an error outside of the context of your application, it will respond with a 500 and a simple
|
||||
textual error message (see `lowlevel_error` in [this file](https://github.com/puma/puma/blob/master/lib/puma/server.rb)).
|
||||
You can specify custom behavior for this scenario. For example, you can report the error to your third-party
|
||||
error-tracking service (in this example, [rollbar](http://rollbar.com)):
|
||||
error-tracking service (in this example, [rollbar](https://rollbar.com)):
|
||||
|
||||
```ruby
|
||||
lowlevel_error_handler do |e|
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
![http://bit.ly/2iJuFky](images/puma-general-arch.png)
|
||||
![https://bit.ly/2iJuFky](images/puma-general-arch.png)
|
||||
|
||||
Puma is a threaded web server, processing requests across a TCP or UNIX socket.
|
||||
|
||||
|
@ -12,7 +12,7 @@ Clustered mode is shown/discussed here. Single mode is analogous to having a sin
|
|||
|
||||
## Connection pipeline
|
||||
|
||||
![http://bit.ly/2zwzhEK](images/puma-connection-flow.png)
|
||||
![https://bit.ly/2zwzhEK](images/puma-connection-flow.png)
|
||||
|
||||
* Upon startup, Puma listens on a TCP or UNIX socket.
|
||||
* The backlog of this socket is configured (with a default of 1024), determining how many established but unaccepted connections can exist concurrently.
|
||||
|
@ -29,7 +29,7 @@ Clustered mode is shown/discussed here. Single mode is analogous to having a sin
|
|||
|
||||
### Disabling `queue_requests`
|
||||
|
||||
![http://bit.ly/2zxCJ1Z](images/puma-connection-flow-no-reactor.png)
|
||||
![https://bit.ly/2zxCJ1Z](images/puma-connection-flow-no-reactor.png)
|
||||
|
||||
The `queue_requests` option is `true` by default, enabling the separate thread used to buffer requests as described above.
|
||||
|
||||
|
|
|
@ -20,7 +20,10 @@ Welcome back!
|
|||
Puma was originally conceived as a thread-only webserver, but grew the ability to
|
||||
also use processes in version 2.
|
||||
|
||||
Here are some rules of thumb:
|
||||
To run puma in single mode (e.g. for a development environment) you will need to
|
||||
set the number of workers to 0, anything above will run in cluster mode.
|
||||
|
||||
Here are some rules of thumb for cluster mode:
|
||||
|
||||
### MRI
|
||||
|
||||
|
@ -66,7 +69,8 @@ thread to become available.
|
|||
|
||||
* Have your upstream proxy set a header with the time it received the request:
|
||||
* nginx: `proxy_set_header X-Request-Start "${msec}";`
|
||||
* haproxy: `http-request set-header X-Request-Start "%t";`
|
||||
* haproxy >= 1.9: `http-request set-header X-Request-Start t=%[date()]%[date_us()]`
|
||||
* haproxy < 1.9: `http-request set-header X-Request-Start t=%[date()]`
|
||||
* In your Rack middleware, determine the amount of time elapsed since `X-Request-Start`.
|
||||
* To improve accuracy, you will want to subtract time spent waiting for slow clients:
|
||||
* `env['puma.request_body_wait']` contains the number of milliseconds Puma spent
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
The [unix signal](http://en.wikipedia.org/wiki/Unix_signal) is a method of sending messages between [processes](http://en.wikipedia.org/wiki/Process_(computing)). When a signal is sent, the operating system interrupts the target process's normal flow of execution. There are standard signals that are used to stop a process but there are also custom signals that can be used for other purposes. This document is an attempt to list all supported signals that Puma will respond to. In general, signals need only be sent to the master process of a cluster.
|
||||
The [unix signal](https://en.wikipedia.org/wiki/Unix_signal) is a method of sending messages between [processes](https://en.wikipedia.org/wiki/Process_(computing)). When a signal is sent, the operating system interrupts the target process's normal flow of execution. There are standard signals that are used to stop a process but there are also custom signals that can be used for other purposes. This document is an attempt to list all supported signals that Puma will respond to. In general, signals need only be sent to the master process of a cluster.
|
||||
|
||||
## Sending Signals
|
||||
|
||||
If you are new to signals it can be useful to see how they can be used. When a process is created in a *nix like operating system it will have a [PID - or process identifier](http://en.wikipedia.org/wiki/Process_identifier) that can be used to send signals to the process. For demonstration we will create an infinitely running process by tailing a file:
|
||||
If you are new to signals it can be useful to see how they can be used. When a process is created in a *nix like operating system it will have a [PID - or process identifier](https://en.wikipedia.org/wiki/Process_identifier) that can be used to send signals to the process. For demonstration we will create an infinitely running process by tailing a file:
|
||||
|
||||
```sh
|
||||
$ echo "foo" >> my.log
|
||||
|
@ -17,13 +17,13 @@ $ ps aux | grep tail
|
|||
schneems 87152 0.0 0.0 2432772 492 s032 S+ 12:46PM 0:00.00 tail -f my.log
|
||||
```
|
||||
|
||||
You can send a signal in Ruby using the [Process module](http://www.ruby-doc.org/core-2.1.1/Process.html#kill-method):
|
||||
You can send a signal in Ruby using the [Process module](https://www.ruby-doc.org/core-2.1.1/Process.html#kill-method):
|
||||
|
||||
```
|
||||
$ irb
|
||||
> puts pid
|
||||
=> 87152
|
||||
Process.detach(pid) # http://ruby-doc.org/core-2.1.1/Process.html#method-c-detach
|
||||
Process.detach(pid) # https://ruby-doc.org/core-2.1.1/Process.html#method-c-detach
|
||||
Process.kill("TERM", pid)
|
||||
```
|
||||
|
||||
|
|
|
@ -14,12 +14,14 @@
|
|||
|
||||
/*
|
||||
* capitalizes all lower-case ASCII characters,
|
||||
* converts dashes to underscores.
|
||||
* converts dashes to underscores, and underscores to commas.
|
||||
*/
|
||||
static void snake_upcase_char(char *c)
|
||||
{
|
||||
if (*c >= 'a' && *c <= 'z')
|
||||
*c &= ~0x20;
|
||||
else if (*c == '_')
|
||||
*c = ',';
|
||||
else if (*c == '-')
|
||||
*c = '_';
|
||||
}
|
||||
|
|
|
@ -12,12 +12,14 @@
|
|||
|
||||
/*
|
||||
* capitalizes all lower-case ASCII characters,
|
||||
* converts dashes to underscores.
|
||||
* converts dashes to underscores, and underscores to commas.
|
||||
*/
|
||||
static void snake_upcase_char(char *c)
|
||||
{
|
||||
if (*c >= 'a' && *c <= 'z')
|
||||
*c &= ~0x20;
|
||||
else if (*c == '_')
|
||||
*c = ',';
|
||||
else if (*c == '-')
|
||||
*c = '_';
|
||||
}
|
||||
|
|
|
@ -173,7 +173,7 @@ public class MiniSSL extends RubyObject {
|
|||
engine.setEnabledProtocols(protocols);
|
||||
engine.setUseClientMode(false);
|
||||
|
||||
long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
|
||||
long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger("to_i").getLongValue();
|
||||
if ((verify_mode & 0x1) != 0) { // 'peer'
|
||||
engine.setWantClientAuth(true);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ DEF_MAX_LENGTH(FIELD_NAME, 256);
|
|||
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
|
||||
DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
|
||||
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
|
||||
DEF_MAX_LENGTH(REQUEST_PATH, 8196);
|
||||
DEF_MAX_LENGTH(REQUEST_PATH, 8192);
|
||||
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
||||
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ require 'socket'
|
|||
require 'puma/const'
|
||||
require 'puma/util'
|
||||
require 'puma/minissl/context_builder'
|
||||
require 'puma/configuration'
|
||||
|
||||
module Puma
|
||||
class Binder
|
||||
|
@ -13,7 +14,7 @@ module Puma
|
|||
|
||||
RACK_VERSION = [1,6].freeze
|
||||
|
||||
def initialize(events)
|
||||
def initialize(events, conf = Configuration.new)
|
||||
@events = events
|
||||
@listeners = []
|
||||
@inherited_fds = {}
|
||||
|
@ -23,8 +24,8 @@ module Puma
|
|||
@proto_env = {
|
||||
"rack.version".freeze => RACK_VERSION,
|
||||
"rack.errors".freeze => events.stderr,
|
||||
"rack.multithread".freeze => true,
|
||||
"rack.multiprocess".freeze => false,
|
||||
"rack.multithread".freeze => conf.options[:max_threads] > 1,
|
||||
"rack.multiprocess".freeze => conf.options[:workers] >= 1,
|
||||
"rack.run_once".freeze => false,
|
||||
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
|
||||
|
||||
|
@ -113,7 +114,7 @@ module Puma
|
|||
i.local_address.ip_unpack.join(':')
|
||||
end
|
||||
|
||||
logger.log "* #{log_msg} on tcp://#{addr}"
|
||||
logger.log "* #{log_msg} on http://#{addr}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -308,8 +308,16 @@ module Puma
|
|||
|
||||
te = @env[TRANSFER_ENCODING2]
|
||||
|
||||
if te && CHUNKED.casecmp(te) == 0
|
||||
return setup_chunked_body(body)
|
||||
if te
|
||||
if te.include?(",")
|
||||
te.split(",").each do |part|
|
||||
if CHUNKED.casecmp(part.strip) == 0
|
||||
return setup_chunked_body(body)
|
||||
end
|
||||
end
|
||||
elsif CHUNKED.casecmp(te) == 0
|
||||
return setup_chunked_body(body)
|
||||
end
|
||||
end
|
||||
|
||||
@chunked_body = false
|
||||
|
@ -412,7 +420,10 @@ module Puma
|
|||
raise EOFError
|
||||
end
|
||||
|
||||
return true if decode_chunk(chunk)
|
||||
if decode_chunk(chunk)
|
||||
@env[CONTENT_LENGTH] = @chunked_content_length
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -424,20 +435,28 @@ module Puma
|
|||
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
||||
@body.binmode
|
||||
@tempfile = @body
|
||||
@chunked_content_length = 0
|
||||
|
||||
return decode_chunk(body)
|
||||
if decode_chunk(body)
|
||||
@env[CONTENT_LENGTH] = @chunked_content_length
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
def write_chunk(str)
|
||||
@chunked_content_length += @body.write(str)
|
||||
end
|
||||
|
||||
def decode_chunk(chunk)
|
||||
if @partial_part_left > 0
|
||||
if @partial_part_left <= chunk.size
|
||||
if @partial_part_left > 2
|
||||
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
||||
write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
|
||||
end
|
||||
chunk = chunk[@partial_part_left..-1]
|
||||
@partial_part_left = 0
|
||||
else
|
||||
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
||||
write_chunk(chunk) if @partial_part_left > 2 # don't include the last \r\n
|
||||
@partial_part_left -= chunk.size
|
||||
return false
|
||||
end
|
||||
|
@ -484,12 +503,12 @@ module Puma
|
|||
|
||||
case
|
||||
when got == len
|
||||
@body << part[0..-3] # to skip the ending \r\n
|
||||
write_chunk(part[0..-3]) # to skip the ending \r\n
|
||||
when got <= len - 2
|
||||
@body << part
|
||||
write_chunk(part)
|
||||
@partial_part_left = len - part.size
|
||||
when got == len - 1 # edge where we get just \r but not \n
|
||||
@body << part[0..-2]
|
||||
write_chunk(part[0..-2])
|
||||
@partial_part_left = len - part.size
|
||||
end
|
||||
else
|
||||
|
|
|
@ -248,6 +248,7 @@ module Puma
|
|||
$0 = title
|
||||
|
||||
Signal.trap "SIGINT", "IGNORE"
|
||||
Signal.trap "SIGCHLD", "DEFAULT"
|
||||
|
||||
fork_worker = @options[:fork_worker] && index == 0
|
||||
|
||||
|
@ -284,9 +285,11 @@ module Puma
|
|||
|
||||
if fork_worker
|
||||
restart_server.clear
|
||||
worker_pids = []
|
||||
Signal.trap "SIGCHLD" do
|
||||
Process.wait(-1, Process::WNOHANG) rescue nil
|
||||
wakeup!
|
||||
wakeup! if worker_pids.reject! do |p|
|
||||
Process.wait(p, Process::WNOHANG) rescue true
|
||||
end
|
||||
end
|
||||
|
||||
Thread.new do
|
||||
|
@ -303,7 +306,7 @@ module Puma
|
|||
elsif idx == 0 # restart server
|
||||
restart_server << true << false
|
||||
else # fork worker
|
||||
pid = spawn_worker(idx, master)
|
||||
worker_pids << pid = spawn_worker(idx, master)
|
||||
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Puma
|
||||
# Rack::CommonLogger forwards every request to the given +app+, and
|
||||
# logs a line in the
|
||||
# {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
|
||||
# {Apache common log format}[https://httpd.apache.org/docs/1.3/logs.html#common]
|
||||
# to the +logger+.
|
||||
#
|
||||
# If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
|
||||
|
@ -16,7 +16,7 @@ module Puma
|
|||
# (which is called without arguments in order to make the error appear for
|
||||
# sure)
|
||||
class CommonLogger
|
||||
# Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
|
||||
# Common Log Format: https://httpd.apache.org/docs/1.3/logs.html#common
|
||||
#
|
||||
# lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
|
||||
#
|
||||
|
|
|
@ -443,8 +443,8 @@ module Puma
|
|||
#
|
||||
# @note Cluster mode only.
|
||||
# @example
|
||||
# on_worker_fork do
|
||||
# puts 'Before worker fork...'
|
||||
# on_worker_boot do
|
||||
# puts 'Before worker boot...'
|
||||
# end
|
||||
def on_worker_boot(&block)
|
||||
@options[:before_worker_boot] ||= []
|
||||
|
@ -769,7 +769,7 @@ module Puma
|
|||
# also increase time to boot and fork. See your logs for details on how much
|
||||
# time this adds to your boot process. For most apps, it will be less than one
|
||||
# second.
|
||||
def nakayoshi_fork(enabled=false)
|
||||
def nakayoshi_fork(enabled=true)
|
||||
@options[:nakayoshi_fork] = enabled
|
||||
end
|
||||
end
|
||||
|
|
96
lib/puma/error_logger.rb
Normal file
96
lib/puma/error_logger.rb
Normal file
|
@ -0,0 +1,96 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'puma/const'
|
||||
|
||||
module Puma
|
||||
# The implementation of a detailed error logging.
|
||||
#
|
||||
class ErrorLogger
|
||||
include Const
|
||||
|
||||
attr_reader :ioerr
|
||||
|
||||
REQUEST_FORMAT = %{"%s %s%s" - (%s)}
|
||||
|
||||
def initialize(ioerr)
|
||||
@ioerr = ioerr
|
||||
@ioerr.sync = true
|
||||
|
||||
@debug = ENV.key? 'PUMA_DEBUG'
|
||||
end
|
||||
|
||||
def self.stdio
|
||||
new $stderr
|
||||
end
|
||||
|
||||
# Print occured error details.
|
||||
# +options+ hash with additional options:
|
||||
# - +error+ is an exception object
|
||||
# - +req+ the http request
|
||||
# - +text+ (default nil) custom string to print in title
|
||||
# and before all remaining info.
|
||||
#
|
||||
def info(options={})
|
||||
ioerr.puts title(options)
|
||||
end
|
||||
|
||||
# Print occured error details only if
|
||||
# environment variable PUMA_DEBUG is defined.
|
||||
# +options+ hash with additional options:
|
||||
# - +error+ is an exception object
|
||||
# - +req+ the http request
|
||||
# - +text+ (default nil) custom string to print in title
|
||||
# and before all remaining info.
|
||||
#
|
||||
def debug(options={})
|
||||
return unless @debug
|
||||
|
||||
error = options[:error]
|
||||
req = options[:req]
|
||||
|
||||
string_block = []
|
||||
string_block << title(options)
|
||||
string_block << request_dump(req) if req
|
||||
string_block << error_backtrace(options) if error
|
||||
|
||||
ioerr.puts string_block.join("\n")
|
||||
end
|
||||
|
||||
def title(options={})
|
||||
text = options[:text]
|
||||
req = options[:req]
|
||||
error = options[:error]
|
||||
|
||||
string_block = ["#{Time.now}"]
|
||||
string_block << " #{text}" if text
|
||||
string_block << " (#{request_title(req)})" if request_parsed?(req)
|
||||
string_block << ": #{error.inspect}" if error
|
||||
string_block.join('')
|
||||
end
|
||||
|
||||
def request_dump(req)
|
||||
"Headers: #{request_headers(req)}\n" \
|
||||
"Body: #{req.body}"
|
||||
end
|
||||
|
||||
def request_title(req)
|
||||
env = req.env
|
||||
|
||||
REQUEST_FORMAT % [
|
||||
env[REQUEST_METHOD],
|
||||
env[REQUEST_PATH] || env[PATH_INFO],
|
||||
env[QUERY_STRING] || "",
|
||||
env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
|
||||
]
|
||||
end
|
||||
|
||||
def request_headers(req)
|
||||
headers = req.env.select { |key, _| key.start_with?('HTTP_') }
|
||||
headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
|
||||
end
|
||||
|
||||
def request_parsed?(req)
|
||||
req && req.env[REQUEST_METHOD]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'puma/const'
|
||||
require "puma/null_io"
|
||||
require 'puma/error_logger'
|
||||
require 'stringio'
|
||||
|
||||
module Puma
|
||||
|
@ -23,8 +23,6 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
include Const
|
||||
|
||||
# Create an Events object that prints to +stdout+ and +stderr+.
|
||||
#
|
||||
def initialize(stdout, stderr)
|
||||
|
@ -36,6 +34,7 @@ module Puma
|
|||
@stderr.sync = true
|
||||
|
||||
@debug = ENV.key? 'PUMA_DEBUG'
|
||||
@error_logger = ErrorLogger.new(@stderr)
|
||||
|
||||
@hooks = Hash.new { |h,k| h[k] = [] }
|
||||
end
|
||||
|
@ -66,7 +65,8 @@ module Puma
|
|||
# Write +str+ to +@stdout+
|
||||
#
|
||||
def log(str)
|
||||
@stdout.puts format(str)
|
||||
@stdout.puts format(str) if @stdout.respond_to? :puts
|
||||
rescue Errno::EPIPE
|
||||
end
|
||||
|
||||
def write(str)
|
||||
|
@ -80,7 +80,7 @@ module Puma
|
|||
# Write +str+ to +@stderr+
|
||||
#
|
||||
def error(str)
|
||||
@stderr.puts format("ERROR: #{str}")
|
||||
@error_logger.info(text: format("ERROR: #{str}"))
|
||||
exit 1
|
||||
end
|
||||
|
||||
|
@ -88,42 +88,45 @@ module Puma
|
|||
formatter.call(str)
|
||||
end
|
||||
|
||||
# An HTTP parse error has occurred.
|
||||
# +server+ is the Server object, +env+ the request, and +error+ a
|
||||
# parsing exception.
|
||||
# An HTTP connection error has occurred.
|
||||
# +error+ a connection exception, +req+ the request,
|
||||
# and +text+ additional info
|
||||
#
|
||||
def parse_error(server, env, error)
|
||||
@stderr.puts "#{Time.now}: HTTP parse error, malformed request " \
|
||||
"(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
|
||||
"#{error.inspect}"
|
||||
def connection_error(error, req, text="HTTP connection error")
|
||||
@error_logger.info(error: error, req: req, text: text)
|
||||
end
|
||||
|
||||
# An HTTP parse error has occurred.
|
||||
# +error+ a parsing exception,
|
||||
# and +req+ the request.
|
||||
#
|
||||
def parse_error(error, req)
|
||||
@error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
|
||||
end
|
||||
|
||||
# An SSL error has occurred.
|
||||
# +server+ is the Server object, +peeraddr+ peer address, +peercert+
|
||||
# any peer certificate (if present), and +error+ an exception object.
|
||||
# +error+ an exception object, +peeraddr+ peer address,
|
||||
# and +peercert+ any peer certificate (if present).
|
||||
#
|
||||
def ssl_error(server, peeraddr, peercert, error)
|
||||
def ssl_error(error, peeraddr, peercert)
|
||||
subject = peercert ? peercert.subject : nil
|
||||
@stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
|
||||
@error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
|
||||
end
|
||||
|
||||
# An unknown error has occurred.
|
||||
# +server+ is the Server object, +error+ an exception object,
|
||||
# +kind+ some additional info, and +env+ the request.
|
||||
# +error+ an exception object, +req+ the request,
|
||||
# and +text+ additional info
|
||||
#
|
||||
def unknown_error(server, error, kind="Unknown", env=nil)
|
||||
if error.respond_to? :render
|
||||
error.render "#{Time.now}: #{kind} error", @stderr
|
||||
else
|
||||
if env
|
||||
string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
|
||||
string_block << error.inspect
|
||||
else
|
||||
string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
|
||||
end
|
||||
string_block << error.backtrace
|
||||
@stderr.puts string_block.join("\n")
|
||||
end
|
||||
def unknown_error(error, req=nil, text="Unknown error")
|
||||
@error_logger.info(error: error, req: req, text: text)
|
||||
end
|
||||
|
||||
# Log occurred error debug dump.
|
||||
# +error+ an exception object, +req+ the request,
|
||||
# and +text+ additional info
|
||||
#
|
||||
def debug_error(error, req=nil, text="")
|
||||
@error_logger.debug(error: error, req: req, text: text)
|
||||
end
|
||||
|
||||
def on_booted(&block)
|
||||
|
|
|
@ -47,7 +47,7 @@ module Puma
|
|||
@original_argv = @argv.dup
|
||||
@config = conf
|
||||
|
||||
@binder = Binder.new(@events)
|
||||
@binder = Binder.new(@events, conf)
|
||||
@binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
|
||||
@binder.create_activated_fds(ENV).each { |k| ENV.delete k }
|
||||
|
||||
|
@ -111,6 +111,7 @@ module Puma
|
|||
sf.pid = Process.pid
|
||||
sf.control_url = @options[:control_url]
|
||||
sf.control_auth_token = @options[:control_auth_token]
|
||||
sf.running_from = File.expand_path('.')
|
||||
|
||||
sf.save path, permission
|
||||
end
|
||||
|
@ -172,12 +173,13 @@ module Puma
|
|||
case @status
|
||||
when :halt
|
||||
log "* Stopping immediately!"
|
||||
@runner.stop_control
|
||||
when :run, :stop
|
||||
graceful_stop
|
||||
when :restart
|
||||
log "* Restarting..."
|
||||
ENV.replace(previous_env)
|
||||
@runner.before_restart
|
||||
@runner.stop_control
|
||||
restart!
|
||||
when :exit
|
||||
# nothing
|
||||
|
|
|
@ -252,7 +252,7 @@ module Puma
|
|||
c.close
|
||||
clear_monitor mon
|
||||
|
||||
@events.ssl_error @server, addr, cert, e
|
||||
@events.ssl_error e, addr, cert
|
||||
|
||||
# The client doesn't know HTTP well
|
||||
rescue HttpParserError => e
|
||||
|
@ -263,7 +263,7 @@ module Puma
|
|||
|
||||
clear_monitor mon
|
||||
|
||||
@events.parse_error @server, c.env, e
|
||||
@events.parse_error e, c
|
||||
rescue StandardError => e
|
||||
@server.lowlevel_error(e, c.env)
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ module Puma
|
|||
@events.log str
|
||||
end
|
||||
|
||||
def before_restart
|
||||
def stop_control
|
||||
@control.stop(true) if @control
|
||||
end
|
||||
|
||||
|
|
|
@ -98,10 +98,22 @@ module Puma
|
|||
@binder = bind
|
||||
end
|
||||
|
||||
class << self
|
||||
# :nodoc:
|
||||
def tcp_cork_supported?
|
||||
RbConfig::CONFIG['host_os'] =~ /linux/ &&
|
||||
Socket.const_defined?(:IPPROTO_TCP) &&
|
||||
Socket.const_defined?(:TCP_CORK) &&
|
||||
Socket.const_defined?(:SOL_TCP) &&
|
||||
Socket.const_defined?(:TCP_INFO)
|
||||
end
|
||||
private :tcp_cork_supported?
|
||||
end
|
||||
|
||||
# On Linux, use TCP_CORK to better control how the TCP stack
|
||||
# packetizes our stream. This improves both latency and throughput.
|
||||
#
|
||||
if RUBY_PLATFORM =~ /linux/
|
||||
if tcp_cork_supported?
|
||||
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
||||
|
||||
# 6 == Socket::IPPROTO_TCP
|
||||
|
@ -109,7 +121,7 @@ module Puma
|
|||
# 1/0 == turn on/off
|
||||
def cork_socket(socket)
|
||||
begin
|
||||
socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
|
||||
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
|
||||
rescue IOError, SystemCallError
|
||||
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
||||
end
|
||||
|
@ -117,7 +129,7 @@ module Puma
|
|||
|
||||
def uncork_socket(socket)
|
||||
begin
|
||||
socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
|
||||
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
|
||||
rescue IOError, SystemCallError
|
||||
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
||||
end
|
||||
|
@ -207,14 +219,16 @@ module Puma
|
|||
|
||||
client.close
|
||||
|
||||
@events.ssl_error self, addr, cert, e
|
||||
@events.ssl_error e, addr, cert
|
||||
rescue HttpParserError => e
|
||||
client.write_error(400)
|
||||
client.close
|
||||
|
||||
@events.parse_error self, client.env, e
|
||||
rescue ConnectionError, EOFError
|
||||
@events.parse_error e, client
|
||||
rescue ConnectionError, EOFError => e
|
||||
client.close
|
||||
|
||||
@events.connection_error e, client
|
||||
else
|
||||
if process_now
|
||||
process_client client, buffer
|
||||
|
@ -300,7 +314,7 @@ module Puma
|
|||
end
|
||||
end
|
||||
rescue Object => e
|
||||
@events.unknown_error self, e, "Listen loop"
|
||||
@events.unknown_error e, nil, "Listen loop"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -313,10 +327,14 @@ module Puma
|
|||
end
|
||||
graceful_shutdown if @status == :stop || @status == :restart
|
||||
rescue Exception => e
|
||||
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
||||
STDERR.puts e.backtrace
|
||||
@events.unknown_error e, nil, "Exception handling servers"
|
||||
ensure
|
||||
@check.close unless @check.closed? # Ruby 2.2 issue
|
||||
begin
|
||||
@check.close unless @check.closed?
|
||||
rescue Errno::EBADF, RuntimeError
|
||||
# RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
|
||||
# Errno::EBADF is infrequently raised
|
||||
end
|
||||
@notify.close
|
||||
@notify = nil
|
||||
@check = nil
|
||||
|
@ -406,7 +424,7 @@ module Puma
|
|||
|
||||
close_socket = true
|
||||
|
||||
@events.ssl_error self, addr, cert, e
|
||||
@events.ssl_error e, addr, cert
|
||||
|
||||
# The client doesn't know HTTP well
|
||||
rescue HttpParserError => e
|
||||
|
@ -414,7 +432,7 @@ module Puma
|
|||
|
||||
client.write_error(400)
|
||||
|
||||
@events.parse_error self, client.env, e
|
||||
@events.parse_error e, client
|
||||
|
||||
# Server error
|
||||
rescue StandardError => e
|
||||
|
@ -422,8 +440,7 @@ module Puma
|
|||
|
||||
client.write_error(500)
|
||||
|
||||
@events.unknown_error self, e, "Read"
|
||||
|
||||
@events.unknown_error e, nil, "Read"
|
||||
ensure
|
||||
buffer.reset
|
||||
|
||||
|
@ -433,7 +450,7 @@ module Puma
|
|||
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
||||
# Already closed
|
||||
rescue StandardError => e
|
||||
@events.unknown_error self, e, "Client"
|
||||
@events.unknown_error e, nil, "Client"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -469,7 +486,7 @@ module Puma
|
|||
|
||||
env[PATH_INFO] = env[REQUEST_PATH]
|
||||
|
||||
# From http://www.ietf.org/rfc/rfc3875 :
|
||||
# From https://www.ietf.org/rfc/rfc3875 :
|
||||
# "Script authors should be aware that the REMOTE_ADDR and
|
||||
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
||||
# may not identify the ultimate source of the request.
|
||||
|
@ -558,12 +575,44 @@ module Puma
|
|||
end
|
||||
|
||||
fast_write client, "\r\n".freeze
|
||||
rescue ConnectionError
|
||||
rescue ConnectionError => e
|
||||
@events.debug_error e
|
||||
# noop, if we lost the socket we just won't send the early hints
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Fixup any headers with , in the name to have _ now. We emit
|
||||
# headers with , in them during the parse phase to avoid ambiguity
|
||||
# with the - to _ conversion for critical headers. But here for
|
||||
# compatibility, we'll convert them back. This code is written to
|
||||
# avoid allocation in the common case (ie there are no headers
|
||||
# with , in their names), that's why it has the extra conditionals.
|
||||
|
||||
to_delete = nil
|
||||
to_add = nil
|
||||
|
||||
env.each do |k,v|
|
||||
if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
|
||||
if to_delete
|
||||
to_delete << k
|
||||
else
|
||||
to_delete = [k]
|
||||
end
|
||||
|
||||
unless to_add
|
||||
to_add = {}
|
||||
end
|
||||
|
||||
to_add[k.tr(",", "_")] = v
|
||||
end
|
||||
end
|
||||
|
||||
if to_delete
|
||||
to_delete.each { |k| env.delete(k) }
|
||||
env.merge! to_add
|
||||
end
|
||||
|
||||
# A rack extension. If the app writes #call'ables to this
|
||||
# array, we will invoke them when the request is done.
|
||||
#
|
||||
|
@ -585,12 +634,12 @@ module Puma
|
|||
return :async
|
||||
end
|
||||
rescue ThreadPool::ForceShutdown => e
|
||||
@events.unknown_error self, e, "Rack app", env
|
||||
@events.unknown_error e, req, "Rack app"
|
||||
@events.log "Detected force shutdown of a thread"
|
||||
|
||||
status, headers, res_body = lowlevel_error(e, env, 503)
|
||||
rescue Exception => e
|
||||
@events.unknown_error self, e, "Rack app", env
|
||||
@events.unknown_error e, req, "Rack app"
|
||||
|
||||
status, headers, res_body = lowlevel_error(e, env, 500)
|
||||
end
|
||||
|
@ -880,7 +929,7 @@ module Puma
|
|||
@check, @notify = Puma::Util.pipe unless @notify
|
||||
begin
|
||||
@notify << message
|
||||
rescue IOError
|
||||
rescue IOError, NoMethodError, Errno::EPIPE
|
||||
# The server, in another thread, is shutting down
|
||||
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
||||
rescue RuntimeError => e
|
||||
|
|
|
@ -19,7 +19,7 @@ module Puma
|
|||
@options = YAML.load File.read(path)
|
||||
end
|
||||
|
||||
FIELDS = %w!control_url control_auth_token pid!
|
||||
FIELDS = %w!control_url control_auth_token pid running_from!
|
||||
|
||||
FIELDS.each do |f|
|
||||
define_method f do
|
||||
|
|
|
@ -122,8 +122,11 @@ module Puma
|
|||
@out_of_band_pending = false
|
||||
end
|
||||
not_full.signal
|
||||
not_empty.wait mutex
|
||||
@waiting -= 1
|
||||
begin
|
||||
not_empty.wait mutex
|
||||
ensure
|
||||
@waiting -= 1
|
||||
end
|
||||
end
|
||||
|
||||
work = todo.shift
|
||||
|
|
|
@ -15,13 +15,13 @@ Gem::Specification.new do |s|
|
|||
end
|
||||
s.files = `git ls-files -- bin docs ext lib tools`.split("\n") +
|
||||
%w[History.md LICENSE README.md]
|
||||
s.homepage = "http://puma.io"
|
||||
s.homepage = "https://puma.io"
|
||||
|
||||
if s.respond_to?(:metadata=)
|
||||
s.metadata = {
|
||||
"bug_tracker_uri" => "https://github.com/puma/puma/issues",
|
||||
"changelog_uri" => "https://github.com/puma/puma/blob/master/History.md",
|
||||
"homepage_uri" => "http://puma.io",
|
||||
"homepage_uri" => "https://puma.io",
|
||||
"source_code_uri" => "https://github.com/puma/puma"
|
||||
}
|
||||
end
|
||||
|
|
3
test/config/t2_conf.rb
Normal file
3
test/config/t2_conf.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
log_requests
|
||||
stdout_redirect "t2-stdout"
|
||||
pidfile "t2-pid"
|
|
@ -58,8 +58,11 @@ module TimeoutEveryTestCase
|
|||
class TestTookTooLong < Timeout::Error
|
||||
end
|
||||
|
||||
def run(*)
|
||||
::Timeout.timeout(RUBY_ENGINE == 'ruby' ? 60 : 120, TestTookTooLong) { super }
|
||||
def time_it
|
||||
t0 = Minitest.clock_time
|
||||
::Timeout.timeout(RUBY_ENGINE == 'ruby' ? 60 : 120, TestTookTooLong) { yield }
|
||||
ensure
|
||||
self.time = Minitest.clock_time - t0
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ module SSLHelper
|
|||
def ssl_query
|
||||
@ssl_query ||= if Puma.jruby?
|
||||
@keystore = File.expand_path "../../../examples/puma/keystore.jks", __FILE__
|
||||
@ssl_cipher_list = "TLS_DHE_RSA_WITH_DES_CBC_SHA,TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA"
|
||||
"keystore=#{@keystore}&keystore-pass=pswd&ssl_cipher_list=#{@ssl_cipher_list}"
|
||||
@ssl_cipher_list = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||
"keystore=#{@keystore}&keystore-pass=blahblah&ssl_cipher_list=#{@ssl_cipher_list}"
|
||||
else
|
||||
@cert = File.expand_path "../../../examples/puma/cert_puma.pem", __FILE__
|
||||
@key = File.expand_path "../../../examples/puma/puma_keypair.pem", __FILE__
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
require "puma"
|
||||
require "puma/detect"
|
||||
|
||||
TESTS_TO_RUN = if Process.respond_to?(:fork)
|
||||
%w[t2 t3]
|
||||
else
|
||||
%w[t2]
|
||||
end
|
||||
return unless Process.respond_to?(:fork)
|
||||
|
||||
results = TESTS_TO_RUN.map do |test|
|
||||
system("ruby -rrubygems test/shell/#{test}.rb ") # > /dev/null 2>&1
|
||||
end
|
||||
|
||||
if results.any? { |r| r != true }
|
||||
exit 1
|
||||
else
|
||||
if system("ruby -rrubygems test/shell/t3.rb ")
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
end
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
system "ruby -rrubygems -Ilib bin/pumactl -F test/shell/t2_conf.rb start &"
|
||||
|
||||
sleep 1 until system "curl http://localhost:10103/"
|
||||
|
||||
out=`ruby -rrubygems -Ilib bin/pumactl -F test/shell/t2_conf.rb status`
|
||||
|
||||
system "ruby -rrubygems -Ilib bin/pumactl -F test/shell/t2_conf.rb stop"
|
||||
|
||||
sleep 1
|
||||
|
||||
log = File.read("t2-stdout")
|
||||
|
||||
File.unlink "t2-stdout" if File.file? "t2-stdout"
|
||||
|
||||
if log =~ %r(GET / HTTP/1\.1) && !File.file?("t2-pid") && out == "Puma is started\n"
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
end
|
|
@ -1,5 +0,0 @@
|
|||
log_requests
|
||||
stdout_redirect "t2-stdout"
|
||||
pidfile "t2-pid"
|
||||
bind "tcp://0.0.0.0:10103"
|
||||
rackup File.expand_path('../rackup/hello.ru', File.dirname(__FILE__))
|
|
@ -6,6 +6,7 @@ require_relative "helpers/ssl"
|
|||
require "puma/binder"
|
||||
require "puma/puma_http11"
|
||||
require "puma/events"
|
||||
require "puma/configuration"
|
||||
|
||||
class TestBinderBase < Minitest::Test
|
||||
include SSLHelper
|
||||
|
@ -15,6 +16,13 @@ class TestBinderBase < Minitest::Test
|
|||
@binder = Puma::Binder.new(@events)
|
||||
end
|
||||
|
||||
def teardown
|
||||
@binder.ios.reject! { |io| Minitest::Mock === io || io.to_io.closed? }
|
||||
@binder.close
|
||||
@binder.unix_paths.select! { |path| File.exist? path }
|
||||
@binder.close_listeners
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ssl_context_for_binder(binder = @binder)
|
||||
|
@ -64,7 +72,7 @@ class TestBinder < TestBinderBase
|
|||
def test_correct_zero_port
|
||||
@binder.parse ["tcp://localhost:0"], @events
|
||||
|
||||
m = %r!tcp://127.0.0.1:(\d+)!.match(@events.stdout.string)
|
||||
m = %r!http://127.0.0.1:(\d+)!.match(@events.stdout.string)
|
||||
port = m[1].to_i
|
||||
|
||||
refute_equal 0, port
|
||||
|
@ -84,9 +92,9 @@ class TestBinder < TestBinderBase
|
|||
def test_logs_all_localhost_bindings
|
||||
@binder.parse ["tcp://localhost:0"], @events
|
||||
|
||||
assert_match %r!tcp://127.0.0.1:(\d+)!, @events.stdout.string
|
||||
assert_match %r!http://127.0.0.1:(\d+)!, @events.stdout.string
|
||||
if Socket.ip_address_list.any? {|i| i.ipv6_loopback? }
|
||||
assert_match %r!tcp://\[::1\]:(\d+)!, @events.stdout.string
|
||||
assert_match %r!http://\[::1\]:(\d+)!, @events.stdout.string
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -288,6 +296,34 @@ class TestBinder < TestBinderBase
|
|||
File.unlink(path) rescue nil # JRuby race?
|
||||
end
|
||||
|
||||
def test_rack_multithread_default_configuration
|
||||
binder = Puma::Binder.new(@events)
|
||||
|
||||
assert binder.proto_env["rack.multithread"]
|
||||
end
|
||||
|
||||
def test_rack_multithread_custom_configuration
|
||||
conf = Puma::Configuration.new(max_threads: 1)
|
||||
|
||||
binder = Puma::Binder.new(@events, conf)
|
||||
|
||||
refute binder.proto_env["rack.multithread"]
|
||||
end
|
||||
|
||||
def test_rack_multiprocess_default_configuration
|
||||
binder = Puma::Binder.new(@events)
|
||||
|
||||
refute binder.proto_env["rack.multiprocess"]
|
||||
end
|
||||
|
||||
def test_rack_multiprocess_custom_configuration
|
||||
conf = Puma::Configuration.new(workers: 1)
|
||||
|
||||
binder = Puma::Binder.new(@events, conf)
|
||||
|
||||
assert binder.proto_env["rack.multiprocess"]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_activates_sockets(path: nil, port: nil, url: nil, sock: nil)
|
||||
|
@ -315,13 +351,17 @@ class TestBinder < TestBinderBase
|
|||
unix: "unix://test/#{name}_server.sock"
|
||||
}
|
||||
|
||||
expected_logs = prepared_paths.dup.tap do |logs|
|
||||
logs[:tcp] = logs[:tcp].gsub('tcp://', 'http://')
|
||||
end
|
||||
|
||||
tested_paths = [prepared_paths[order[0]], prepared_paths[order[1]]]
|
||||
|
||||
@binder.parse tested_paths, @events
|
||||
stdout = @events.stdout.string
|
||||
|
||||
order.each do |prot|
|
||||
assert_match prepared_paths[prot], stdout
|
||||
assert_match expected_logs[prot], stdout
|
||||
end
|
||||
ensure
|
||||
@binder.close_listeners if order.include?(:unix) && UNIX_SKT_EXIST
|
||||
|
@ -331,7 +371,7 @@ end
|
|||
class TestBinderJRuby < TestBinderBase
|
||||
def test_binder_parses_jruby_ssl_options
|
||||
keystore = File.expand_path "../../examples/puma/keystore.jks", __FILE__
|
||||
ssl_cipher_list = "TLS_DHE_RSA_WITH_DES_CBC_SHA,TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA"
|
||||
ssl_cipher_list = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||
|
||||
@binder.parse ["ssl://0.0.0.0:8080?#{ssl_query}"], @events
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ class TestCLI < Minitest::Test
|
|||
cntl = UniquePort.call
|
||||
url = "tcp://127.0.0.1:#{cntl}/"
|
||||
|
||||
cli = Puma::CLI.new [ "--control-url", url,
|
||||
cli = Puma::CLI.new ["-b", "tcp://127.0.0.1:0",
|
||||
"--control-url", url,
|
||||
"--control-token", "",
|
||||
"test/rackup/lobster.ru"], @events
|
||||
|
||||
|
@ -66,14 +67,14 @@ class TestCLI < Minitest::Test
|
|||
end
|
||||
|
||||
def test_control_for_ssl
|
||||
skip_on :jruby # Hangs on CI, TODO fix
|
||||
require "net/http"
|
||||
control_port = UniquePort.call
|
||||
control_host = "127.0.0.1"
|
||||
control_url = "ssl://#{control_host}:#{control_port}?#{ssl_query}"
|
||||
token = "token"
|
||||
|
||||
cli = Puma::CLI.new ["--control-url", control_url,
|
||||
cli = Puma::CLI.new ["-b", "tcp://127.0.0.1:0",
|
||||
"--control-url", control_url,
|
||||
"--control-token", token,
|
||||
"test/rackup/lobster.ru"], @events
|
||||
|
||||
|
|
70
test/test_error_logger.rb
Normal file
70
test/test_error_logger.rb
Normal file
|
@ -0,0 +1,70 @@
|
|||
require 'puma/error_logger'
|
||||
require_relative "helper"
|
||||
|
||||
class TestErrorLogger < Minitest::Test
|
||||
Req = Struct.new(:env, :body)
|
||||
|
||||
def test_stdio
|
||||
error_logger = Puma::ErrorLogger.stdio
|
||||
|
||||
assert_equal STDERR, error_logger.ioerr
|
||||
end
|
||||
|
||||
def test_info_with_only_error
|
||||
_, err = capture_io do
|
||||
Puma::ErrorLogger.stdio.info(error: StandardError.new('ready'))
|
||||
end
|
||||
|
||||
assert_match %r!#<StandardError: ready>!, err
|
||||
end
|
||||
|
||||
def test_info_with_request
|
||||
env = {
|
||||
'REQUEST_METHOD' => 'GET',
|
||||
'PATH_INFO' => '/debug',
|
||||
'HTTP_X_FORWARDED_FOR' => '8.8.8.8'
|
||||
}
|
||||
req = Req.new(env, '{"hello":"world"}')
|
||||
|
||||
_, err = capture_io do
|
||||
Puma::ErrorLogger.stdio.info(error: StandardError.new, req: req)
|
||||
end
|
||||
|
||||
assert_match %r!\("GET /debug" - \(8\.8\.8\.8\)\)!, err
|
||||
end
|
||||
|
||||
def test_info_with_text
|
||||
_, err = capture_io do
|
||||
Puma::ErrorLogger.stdio.info(text: 'The client disconnected while we were reading data')
|
||||
end
|
||||
|
||||
assert_match %r!The client disconnected while we were reading data!, err
|
||||
end
|
||||
|
||||
def test_debug_without_debug_mode
|
||||
_, err = capture_io do
|
||||
Puma::ErrorLogger.stdio.debug(text: 'blank')
|
||||
end
|
||||
|
||||
assert_empty err
|
||||
end
|
||||
|
||||
def test_debug_with_debug_mode
|
||||
with_debug_mode do
|
||||
_, err = capture_io do
|
||||
Puma::ErrorLogger.stdio.debug(text: 'non-blank')
|
||||
end
|
||||
|
||||
assert_match %r!non-blank!, err
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_debug_mode
|
||||
original_debug, ENV["PUMA_DEBUG"] = ENV["PUMA_DEBUG"], "1"
|
||||
yield
|
||||
ensure
|
||||
ENV["PUMA_DEBUG"] = original_debug
|
||||
end
|
||||
end
|
|
@ -1,3 +1,4 @@
|
|||
require 'puma/events'
|
||||
require_relative "helper"
|
||||
|
||||
class TestEvents < Minitest::Test
|
||||
|
@ -119,14 +120,16 @@ class TestEvents < Minitest::Test
|
|||
did_exit = false
|
||||
|
||||
_, err = capture_io do
|
||||
Puma::Events.stdio.error("interrupted")
|
||||
begin
|
||||
Puma::Events.stdio.error("interrupted")
|
||||
rescue SystemExit
|
||||
did_exit = true
|
||||
ensure
|
||||
assert did_exit
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal "ERROR: interrupted", err
|
||||
rescue SystemExit
|
||||
did_exit = true
|
||||
ensure
|
||||
assert did_exit
|
||||
assert_match %r!ERROR: interrupted!, err
|
||||
end
|
||||
|
||||
def test_pid_formatter
|
||||
|
@ -175,7 +178,8 @@ class TestEvents < Minitest::Test
|
|||
sock << "GET #{path}?a=#{params} HTTP/1.1\r\nConnection: close\r\n\r\n"
|
||||
sock.read
|
||||
sleep 0.1 # important so that the previous data is sent as a packet
|
||||
assert_match %r!HTTP parse error, malformed request \(#{path}\)!, events.stderr.string
|
||||
assert_match %r!HTTP parse error, malformed request!, events.stderr.string
|
||||
assert_match %r!\("GET #{path}" - \(-\)\)!, events.stderr.string
|
||||
server.stop(true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -144,14 +144,14 @@ class Http11ParserTest < Minitest::Test
|
|||
parser = Puma::HttpParser.new
|
||||
req = {}
|
||||
|
||||
# Support URI path length to a max of 8196
|
||||
# Support URI path length to a max of 8192
|
||||
path = "/" + rand_data(7000, 100)
|
||||
http = "GET #{path} HTTP/1.1\r\n\r\n"
|
||||
parser.execute(req, http, 0)
|
||||
assert_equal path, req['REQUEST_PATH']
|
||||
parser.reset
|
||||
|
||||
# Raise exception if URI path length > 8196
|
||||
# Raise exception if URI path length > 8192
|
||||
path = "/" + rand_data(9000, 100)
|
||||
http = "GET #{path} HTTP/1.1\r\n\r\n"
|
||||
assert_raises Puma::HttpParserError do
|
||||
|
|
|
@ -168,6 +168,20 @@ RUBY
|
|||
refute_includes pids, get_worker_pids(1, WORKERS - 1)
|
||||
end
|
||||
|
||||
def test_fork_worker_spawn
|
||||
cli_server '', config: <<RUBY
|
||||
workers 1
|
||||
fork_worker 0
|
||||
app do |_|
|
||||
pid = spawn('ls', [:out, :err]=>'/dev/null')
|
||||
sleep 0.01
|
||||
exitstatus = Process.detach(pid).value.exitstatus
|
||||
[200, {}, [exitstatus.to_s]]
|
||||
end
|
||||
RUBY
|
||||
assert_equal '0', read_body(connect)
|
||||
end
|
||||
|
||||
def test_nakayoshi
|
||||
cli_server "-w #{WORKERS} test/rackup/hello.ru", config: <<RUBY
|
||||
nakayoshi_fork true
|
||||
|
@ -373,7 +387,7 @@ RUBY
|
|||
# used with thread_run to define correct 'refused' errors
|
||||
def thread_run_refused(unix: false)
|
||||
if unix
|
||||
DARWIN ? [Errno::ENOENT, IOError] : [Errno::ENOENT]
|
||||
[Errno::ENOENT, IOError]
|
||||
else
|
||||
DARWIN ? [Errno::ECONNREFUSED, Errno::EPIPE, EOFError] :
|
||||
[Errno::ECONNREFUSED]
|
||||
|
|
|
@ -42,13 +42,16 @@ class TestIntegrationPumactl < TestIntegration
|
|||
|
||||
def ctl_unix(signal='stop')
|
||||
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
||||
cli_server "-q test/rackup/sleep.ru --control-url unix://#{@control_path} --control-token #{TOKEN} -S #{@state_path}", unix: true
|
||||
stderr = Tempfile.new(%w(stderr .log))
|
||||
cli_server "-q test/rackup/sleep.ru --control-url unix://#{@control_path} --control-token #{TOKEN} -S #{@state_path}",
|
||||
config: "stdout_redirect nil, '#{stderr.path}'",
|
||||
unix: true
|
||||
|
||||
cli_pumactl signal, unix: true
|
||||
|
||||
_, status = Process.wait2(@pid)
|
||||
assert_equal 0, status
|
||||
|
||||
refute_match 'error', File.read(stderr.path)
|
||||
@server = nil
|
||||
end
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class TestIntegrationSingle < TestIntegration
|
|||
_stdin, _stdout, rejected_curl_stderr, rejected_curl_wait_thread = Open3.popen3("curl #{HOST}:#{@tcp_port}")
|
||||
|
||||
assert nil != Process.getpgid(@server.pid) # ensure server is still running
|
||||
assert nil != Process.getpgid(rejected_curl_wait_thread[:pid]) # ensure first curl invokation still in progress
|
||||
assert nil != Process.getpgid(curl_wait_thread[:pid]) # ensure first curl invocation still in progress
|
||||
|
||||
curl_wait_thread.join
|
||||
rejected_curl_wait_thread.join
|
||||
|
@ -131,4 +131,26 @@ class TestIntegrationSingle < TestIntegration
|
|||
|
||||
assert_match(%r!GET / HTTP/1\.1!, log)
|
||||
end
|
||||
|
||||
def test_puma_started_log_writing
|
||||
skip_unless_signal_exist? :TERM
|
||||
|
||||
suppress_output = '> /dev/null 2>&1'
|
||||
|
||||
cli_server '-C test/config/t2_conf.rb test/rackup/hello.ru'
|
||||
|
||||
system "curl http://localhost:#{@tcp_port}/ #{suppress_output}"
|
||||
|
||||
out=`#{BASE} bin/pumactl -F test/config/t2_conf.rb status`
|
||||
|
||||
stop_server
|
||||
|
||||
log = File.read('t2-stdout')
|
||||
|
||||
File.unlink 't2-stdout' if File.file? 't2-stdout'
|
||||
|
||||
assert_match(%r!GET / HTTP/1\.1!, log)
|
||||
assert(!File.file?("t2-pid"))
|
||||
assert_equal("Puma is started\n", out)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,7 +41,7 @@ class TestPersistent < Minitest::Test
|
|||
def lines(count, s=@client)
|
||||
str = "".dup
|
||||
Timeout.timeout(5) do
|
||||
count.times { str << s.gets }
|
||||
count.times { str << (s.gets || "") }
|
||||
end
|
||||
str
|
||||
end
|
||||
|
|
|
@ -153,7 +153,7 @@ class TestPumaServer < Minitest::Test
|
|||
|
||||
req = Net::HTTP::Get.new("/")
|
||||
req['HOST'] = "example.com"
|
||||
req['X_FORWARDED_PROTO'] = "https,http"
|
||||
req['X-FORWARDED-PROTO'] = "https,http"
|
||||
|
||||
port = @server.connected_ports[0]
|
||||
res = Net::HTTP.start @host, port do |http|
|
||||
|
@ -498,8 +498,10 @@ EOF
|
|||
|
||||
def test_chunked_request
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -507,12 +509,15 @@ EOF
|
|||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
|
||||
assert_equal "hello", body
|
||||
assert_equal 5, content_length
|
||||
end
|
||||
|
||||
def test_chunked_request_pause_before_value
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -525,12 +530,15 @@ EOF
|
|||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
|
||||
assert_equal "hello", body
|
||||
assert_equal 5, content_length
|
||||
end
|
||||
|
||||
def test_chunked_request_pause_between_chunks
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -543,12 +551,15 @@ EOF
|
|||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
|
||||
assert_equal "hello", body
|
||||
assert_equal 5, content_length
|
||||
end
|
||||
|
||||
def test_chunked_request_pause_mid_count
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -561,12 +572,15 @@ EOF
|
|||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
|
||||
assert_equal "hello", body
|
||||
assert_equal 5, content_length
|
||||
end
|
||||
|
||||
def test_chunked_request_pause_before_count_newline
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -579,12 +593,15 @@ EOF
|
|||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
|
||||
assert_equal "hello", body
|
||||
assert_equal 5, content_length
|
||||
end
|
||||
|
||||
def test_chunked_request_pause_mid_value
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -597,12 +614,15 @@ EOF
|
|||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
|
||||
assert_equal "hello", body
|
||||
assert_equal 5, content_length
|
||||
end
|
||||
|
||||
def test_chunked_request_pause_between_cr_lf_after_size_of_second_chunk
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -624,12 +644,15 @@ EOF
|
|||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
|
||||
assert_equal (part1 + 'b'), body
|
||||
assert_equal 4201, content_length
|
||||
end
|
||||
|
||||
def test_chunked_request_pause_between_closing_cr_lf
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -643,12 +666,15 @@ EOF
|
|||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
|
||||
assert_equal 'hello', body
|
||||
assert_equal 5, content_length
|
||||
end
|
||||
|
||||
def test_chunked_request_pause_before_closing_cr_lf
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -662,12 +688,15 @@ EOF
|
|||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
|
||||
assert_equal 'hello', body
|
||||
assert_equal 5, content_length
|
||||
end
|
||||
|
||||
def test_chunked_request_header_case
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -675,12 +704,15 @@ EOF
|
|||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
|
||||
assert_equal "hello", body
|
||||
assert_equal 5, content_length
|
||||
end
|
||||
|
||||
def test_chunked_keep_alive
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -690,14 +722,17 @@ EOF
|
|||
|
||||
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
|
||||
assert_equal "hello", body
|
||||
assert_equal 5, content_length
|
||||
|
||||
sock.close
|
||||
end
|
||||
|
||||
def test_chunked_keep_alive_two_back_to_back
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
||||
|
@ -715,6 +750,7 @@ EOF
|
|||
h = header(sock)
|
||||
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
|
||||
assert_equal "hello", body
|
||||
assert_equal 5, content_length
|
||||
assert_equal true, last_crlf_written
|
||||
|
||||
last_crlf_writer.join
|
||||
|
@ -726,16 +762,19 @@ EOF
|
|||
|
||||
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
|
||||
assert_equal "goodbye", body
|
||||
assert_equal 7, content_length
|
||||
|
||||
sock.close
|
||||
end
|
||||
|
||||
def test_chunked_keep_alive_two_back_to_back_with_set_remote_address
|
||||
body = nil
|
||||
content_length = nil
|
||||
remote_addr =nil
|
||||
@server = Puma::Server.new @app, @events, { remote_address: :header, remote_address_header: 'HTTP_X_FORWARDED_FOR'}
|
||||
server_run app: ->(env) {
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
remote_addr = env['REMOTE_ADDR']
|
||||
[200, {}, [""]]
|
||||
}
|
||||
|
@ -745,6 +784,7 @@ EOF
|
|||
h = header sock
|
||||
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
|
||||
assert_equal "hello", body
|
||||
assert_equal 5, content_length
|
||||
assert_equal "127.0.0.1", remote_addr
|
||||
|
||||
sock << "GET / HTTP/1.1\r\nX-Forwarded-For: 127.0.0.2\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ngood\r\n3\r\nbye\r\n0\r\n\r\n"
|
||||
|
@ -754,6 +794,7 @@ EOF
|
|||
|
||||
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
|
||||
assert_equal "goodbye", body
|
||||
assert_equal 7, content_length
|
||||
assert_equal "127.0.0.2", remote_addr
|
||||
|
||||
sock.close
|
||||
|
|
|
@ -11,10 +11,10 @@ require "net/http"
|
|||
class SSLEventsHelper < ::Puma::Events
|
||||
attr_accessor :addr, :cert, :error
|
||||
|
||||
def ssl_error(server, peeraddr, peercert, error)
|
||||
def ssl_error(error, peeraddr, peercert)
|
||||
self.error = error
|
||||
self.addr = peeraddr
|
||||
self.cert = peercert
|
||||
self.error = error
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -150,7 +150,6 @@ class TestPumaControlCli < TestConfigFileBase
|
|||
end
|
||||
|
||||
def test_control_ssl
|
||||
skip_on :jruby # Hanging on JRuby, TODO fix
|
||||
host = "127.0.0.1"
|
||||
port = UniquePort.call
|
||||
url = "ssl://#{host}:#{port}?#{ssl_query}"
|
||||
|
|
|
@ -17,7 +17,9 @@ class TestRedirectIO < TestIntegration
|
|||
def teardown
|
||||
super
|
||||
|
||||
paths = [@out_file_path, @err_file_path, @old_out_file_path, @old_err_file_path].compact
|
||||
paths = (skipped? ? [@out_file_path, @err_file_path] :
|
||||
[@out_file_path, @err_file_path, @old_out_file_path, @old_err_file_path]).compact
|
||||
|
||||
File.unlink(*paths)
|
||||
@out_file = nil
|
||||
@err_file = nil
|
||||
|
|
|
@ -264,6 +264,21 @@ class TestThreadPool < Minitest::Test
|
|||
end
|
||||
assert_equal 0, pool.spawned
|
||||
assert_equal 2, rescued.length
|
||||
refute rescued.any?(&:alive?)
|
||||
refute rescued.compact.any?(&:alive?)
|
||||
end
|
||||
|
||||
def test_correct_waiting_count_for_killed_threads
|
||||
pool = new_pool(1, 1) { |_| }
|
||||
sleep 1
|
||||
|
||||
# simulate our waiting worker thread getting killed for whatever reason
|
||||
pool.instance_eval { @workers[0].kill }
|
||||
sleep 1
|
||||
pool.reap
|
||||
sleep 1
|
||||
|
||||
pool << 0
|
||||
sleep 1
|
||||
assert_equal 0, pool.backlog
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue