1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

Merge branch 'master' into chase/dont_error_on_pipe_close

This commit is contained in:
Evan Phoenix 2019-02-20 09:33:43 -08:00 committed by GitHub
commit c26cced04b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 964 additions and 430 deletions

3
.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
# Auto detect text files and perform LF normalization
* text eol=lf
*.png binary

8
.gitignore vendored
View file

@ -16,3 +16,11 @@ Gemfile.lock
/test/test_puma.state
/test/test_server.sock
/test/test_control.sock
# windows local build artifacts
/win_gem_test/shared/
/win_gem_test/packages/
/win_gem_test/test_logs/
/Rakefile_wintest
*.gem
/lib/puma/puma_http11.rb

View file

@ -1,6 +1,6 @@
AllCops:
DisabledByDefault: true
TargetRubyVersion: 1.9.3
TargetRubyVersion: 2.2
DisplayCopNames: true
StyleGuideCopsOnly: false
Exclude:
@ -51,41 +51,5 @@ Style/MethodDefParentheses:
Style/TrailingCommaInArguments:
Enabled: true
Performance/Count:
Enabled: true
Performance/Detect:
Enabled: true
Performance/EndWith:
Enabled: true
Performance/FlatMap:
Enabled: true
Performance/HashEachMethods:
Enabled: true
Performance/RangeInclude:
Enabled: true
Performance/RedundantMerge:
Enabled: true
Performance/RedundantSortBy:
Enabled: true
Performance/ReverseEach:
Enabled: true
Performance/Sample:
Enabled: true
Performance/Size:
Enabled: true
Performance/StartWith:
Enabled: true
Performance/TimesMap:
Performance:
Enabled: true

View file

@ -3,27 +3,43 @@ sudo: false
group: beta
language: ruby
cache: bundler
before_install:
# https://github.com/travis-ci/travis-ci/issues/9383#issuecomment-377680108
- gem install bundler
branches:
only:
- "master"
# rubygems 2.7.8 and greater include bundler
- |
rv="$(ruby -e 'STDOUT.write RUBY_VERSION')";
if [ "$rv" \< "2.3" ]; then gem update --system 2.7.8 --no-document
elif [ "$rv" \< "2.6" ]; then gem update --system --no-document --conservative
fi
- ruby -v && gem --version && bundle version
rvm:
- 2.2.10
- 2.3.7
- 2.4.4
- 2.5.1
- 2.3.8
- 2.4.5
- 2.5.3
- ruby-head
- jruby-9.1.17.0
- jruby-head
- rbx-3
- jruby-9.2.0.0
matrix:
fast_finish: true
allow_failures:
include:
- rvm: ruby-head
env: RUBYOPT="--jit"
- rvm: 2.3.8
os: osx
- rvm: 2.5.3
os: osx
- rvm: jruby-head
- rvm: rbx-3
allow_failures:
- rvm: ruby-head
- rvm: ruby-head
env: RUBYOPT="--jit"
- rvm: jruby-head
- rvm: rbx-3
env:
global:
- TESTOPTS="-v"

View file

@ -6,13 +6,15 @@ gem "rdoc"
gem "rake-compiler"
gem "rack", "< 3.0"
gem "minitest", "~> 5.9"
gem "minitest", "~> 5.11"
gem "minitest-retry"
gem "jruby-openssl", :platform => "jruby"
gem "rubocop", "~> 0.50.0"
gem "rubocop", "~> 0.58.0"
if %w(2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1).include? RUBY_VERSION
gem "stopgap_13632", "~> 1.0", :platforms => ["mri", "mingw", "x64_mingw"]
end
gem 'm'

View file

@ -1,3 +1,16 @@
## 3.12.0 / 2018-07-13
* 5 features:
* You can now specify which SSL ciphers the server should support, default is unchanged (#1478)
* The setting for Puma's `max_threads` is now in `Puma.stats` (#1604)
* Pool capacity is now in `Puma.stats` (#1579)
* Installs restricted to Ruby 2.2+ (#1506)
* `--control` is now deprecated in favor of `--control-url` (#1487)
* 2 bugfixes:
* Workers will no longer accept more web requests than they have capacity to process. This prevents an issue where one worker would accept lots of requests while starving other workers (#1563)
* In a test env puma now emits the stack on an exception (#1557)
## 3.11.4 / 2018-04-12
* 2 features:

View file

@ -7,8 +7,8 @@
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/puma/puma?utm\_source=badge&utm\_medium=badge&utm\_campaign=pr-badge)
[![Build Status](https://secure.travis-ci.org/puma/puma.svg)](http://travis-ci.org/puma/puma)
[![AppVeyor](https://img.shields.io/appveyor/ci/nateberkopec/puma.svg)](https://ci.appveyor.com/project/nateberkopec/puma)
[![Dependency Status](https://gemnasium.com/puma/puma.svg)](https://gemnasium.com/puma/puma)
[![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)
Puma is a **simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications** in development and production.
@ -25,7 +25,7 @@ On MRI, there is a Global VM Lock (GVL) that ensures only one thread can run Rub
```
$ gem install puma
$ puma <any rackup (*.ru) file>
```
```
## Frameworks
@ -160,18 +160,31 @@ Need a bit of security? Use SSL sockets:
```
$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert'
```
#### Controlling SSL Cipher Suites
Need to use or avoid specific SSL cipher suites? Use ssl_cipher_filter or ssl_cipher_list options.
#####Ruby:
Need to use or avoid specific SSL cipher suites? Use `ssl_cipher_filter` or `ssl_cipher_list` options.
##### Ruby:
```
$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&ssl_cipher_filter=!aNULL:AES+SHA'
```
#####JRuby:
##### JRuby:
```
$ puma -b 'ssl://127.0.0.1:9292?keystore=path_to_keystore&keystore-pass=keystore_password&ssl_cipher_list=TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA'
```
See https://www.openssl.org/docs/man1.0.2/apps/ciphers.html for cipher filter format and full list of cipher suites.
Don't want to use insecure TLSv1.0 ?
```
$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&no_tlsv1=true'
```
### Control/Status Server
Puma has a built-in status/control app that can be used to query and control Puma itself.

View file

@ -73,16 +73,6 @@ else
task :test => [:compile]
end
task :test => [:ensure_no_puma_gem]
task :ensure_no_puma_gem do
Bundler.with_clean_env do
out = `gem list puma`.strip
if !$?.success? || out != ""
abort "No other puma version should be installed to avoid false positives or loading it by accident but found #{out}"
end
end
end
namespace :test do
desc "Run the integration tests"
task :integration do

View file

@ -1,109 +1,20 @@
# cache cleanup 2018-03-16
init:
- set PATH=C:\ruby%ruby_version%\bin;C:\Program Files\7-Zip;C:\Program Files\AppVeyor\BuildAgent;C:\Program Files\Git\cmd;C:\Windows\system32
# Download current trunk, install OpenSSL via trunk_pkgs.cmd file
- if %ruby_version%==_trunk (
appveyor DownloadFile https://ci.appveyor.com/api/projects/MSP-Greg/ruby-loco/artifacts/ruby_trunk.7z -FileName C:\ruby_trunk.7z &
7z x C:\ruby_trunk.7z -oC:\ruby_trunk &
C:\ruby_trunk\trunk_pkgs.cmd
)
install:
# Install ragel
- if "%ri_file%"=="x64_2" ( C:\msys64\usr\bin\pacman -S --noconfirm mingw-w64-x86_64-ragel )
- if "%ri_file%"=="x86_2" ( C:\msys64\usr\bin\pacman -S --noconfirm mingw-w64-i686-ragel )
# Install ragel & download RI OpenSSL Knapsack package
# RI DevKit is only installed in Ruby23 and Ruby23-x64 folders
# RI2 MSYS2/MinGW OpenSSL package is standard Appveyor item
- if "%ri_file%"=="x86" (
appveyor DownloadFile https://dl.bintray.com/oneclick/OpenKnapsack/x86/openssl-1.0.2j-x86-windows.tar.lzma &
7z e openssl-1.0.2j-x86-windows.tar.lzma &
7z x -y openssl-1.0.2j-x86-windows.tar -oC:\ruby23\DevKit\mingw &
set b_config="--with-ssl-dir=C:/ruby23/DevKit/mingw --with-opt-include=C:/ruby23/DevKit/mingw/include" &
set SSL_CERT_FILE=C:/ruby24-x64/ssl/cert.pem &
C:\msys64\usr\bin\pacman -S --noconfirm mingw-w64-i686-ragel &
set PATH=%PATH%;C:\msys64\ming32\bin
)
- if "%ri_file%"=="x64" (
appveyor DownloadFile https://dl.bintray.com/oneclick/OpenKnapsack/x64/openssl-1.0.2j-x64-windows.tar.lzma &
7z e openssl-1.0.2j-x64-windows.tar.lzma &
7z x -y openssl-1.0.2j-x64-windows.tar -oC:\ruby23-x64\DevKit\mingw &
set b_config="--with-ssl-dir=C:/ruby23-x64/DevKit/mingw --with-opt-include=C:/ruby23-x64/DevKit/mingw/include" &
set SSL_CERT_FILE=C:/ruby24-x64/ssl/cert.pem &
C:\msys64\usr\bin\pacman -S --noconfirm mingw-w64-x86_64-ragel &
set PATH=%PATH%;C:\msys64\ming64\bin
)
- RAKEOPT:
- APPVEYOR: true
- ruby --version
- gem --version
- bundle --version
- bundle install --without documentation --path C:/av_bundle
# Download & install current OpenSSL package for later RubyInstaller2 version(s)
- if %ruby_version%==25-x64 (
set openssl=mingw-w64-x86_64-openssl-1.1.0.g-1-any.pkg.tar.xz
)
- if %ruby_version%==25 (
set openssl=mingw-w64-i686-openssl-1.1.0.g-1-any.pkg.tar.xz
)
- set dl_uri=https://dl.bintray.com/msp-greg/ruby_trunk
- if %ruby_version%==25-x64 (
C:\msys64\usr\bin\bash -lc "pacman-key -r 77D8FA18 --keyserver na.pool.sks-keyservers.net && pacman-key -f 77D8FA18 && pacman-key --lsign-key 77D8FA18" &
appveyor DownloadFile %dl_uri%/%openssl% -FileName C:\%openssl% &
appveyor DownloadFile %dl_uri%/%openssl%.sig -FileName C:\%openssl%.sig &
C:\msys64\usr\bin\pacman -Rdd --noconfirm mingw-w64-x86_64-openssl &
C:\msys64\usr\bin\pacman -Udd --noconfirm --force C:\%openssl%
)
- if %ruby_version%==25 (
C:\msys64\usr\bin\bash -lc "pacman-key -r 77D8FA18 --keyserver na.pool.sks-keyservers.net && pacman-key -f 77D8FA18 && pacman-key --lsign-key 77D8FA18" &
appveyor DownloadFile %dl_uri%/%openssl% -FileName C:\%openssl% &
appveyor DownloadFile %dl_uri%/%openssl%.sig -FileName C:\%openssl%.sig &
C:\msys64\usr\bin\pacman -Rdd --noconfirm mingw-w64-686-openssl &
C:\msys64\usr\bin\pacman -Udd --noconfirm --force C:\%openssl%
)
# download shared script files
- ps: >-
if ( !(Test-Path -Path ./shared -PathType Container) ) {
$uri = 'https://ci.appveyor.com/api/projects/MSP-Greg/av-gem-build-test/artifacts/shared.7z'
$7z = 'C:\Program Files\7-Zip\7z.exe'
$fn = "$env:TEMP\shared.7z"
(New-Object System.Net.WebClient).DownloadFile($uri, $fn)
&$7z x $fn -owin_gem_test 1> $null
Remove-Item -LiteralPath $fn -Force
Write-Host "Downloaded shared files" -ForegroundColor Yellow
}
build_script:
- bundle exec rake -rdevkit compile -- %b_config%
test_script:
- set OPENSSL_DIR=
- bundle exec rake -rdevkit test TESTOPTS="--verbose"
on_finish:
- ruby -v
- ps: .\win_gem_test\puma.ps1 $env:gem_bits
environment:
matrix:
- ruby_version: _trunk
b_config: "--use-system-libraries"
ri_file: x64_2
- ruby_version: 25
b_config: "--use-system-libraries"
ri_file: x86_2
- ruby_version: 25-x64
b_config: "--use-system-libraries"
ri_file: x64_2
- ruby_version: 24
b_config: "--use-system-libraries"
ri_file: x86_2
- ruby_version: 24-x64
b_config: "--use-system-libraries"
ri_file: x64_2
- ruby_version: 23
ri_file: x86
- ruby_version: 23-x64
ri_file: x64
- ruby_version: 22
ri_file: x86
- ruby_version: 22-x64
ri_file: x64
cache:
- C:\av_bundle
branches:
only:
- master
- gem_bits: 64
- gem_bits: 32

View file

@ -20,6 +20,7 @@ Clustered mode is shown/discussed here. Single mode is analogous to having a sin
* By default, a single, separate thread is used to receive HTTP requests across the socket.
* When at least one worker thread is available for work, a connection is accepted and placed in this request buffer
* This thread waits for entire HTTP requests to be received over the connection
* The time spent waiting for the HTTP request body to be received is exposed to the Rack app as `env['puma.request_body_wait']` (milliseconds)
* Once received, the connection is pushed into the "todo" set
* Worker threads pop work off the "todo" set for processing
* The thread processes the request via the rack application (which generates the HTTP response)

View file

@ -38,22 +38,42 @@ Here are some rules of thumb:
* As you grow more confident in the thread safety of your app, you can tune the
workers down and the threads up.
#### Ubuntu / Systemd (Systemctl) Installation
See [systemd.md](systemd.md)
#### Worker utilization
**How do you know if you're got enough (or too many workers)?**
**How do you know if you've got enough (or too many workers)?**
A good question. Due to MRI's GIL, only one thread can be executing Ruby code at a time.
But since so many apps are waiting on IO from DBs, etc., they can utilize threads
to make better use of the process.
The rule of thumb is you never want processes that are pegged all the time. This
means that there is more work to do that the process can get through. On the other
means that there is more work to do than the process can get through. On the other
hand, if you have processes that sit around doing nothing, then they're just eating
up resources.
Watching your CPU utilization over time and aim for about 70% on average. This means
Watch your CPU utilization over time and aim for about 70% on average. This means
you've got capacity still but aren't starving threads.
**Measuring utilization**
Using a timestamp header from an upstream proxy server (eg. nginx or haproxy), it's
possible to get an indication of how long requests have been waiting for a Puma
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";`
* 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
waiting for the client to send the request body.
* haproxy: `%Th` (TLS handshake time) and `%Ti` (idle time before request) can
can also be added as headers.
## Daemonizing
I prefer to not daemonize my servers and use something like `runit` or `upstart` to
@ -62,7 +82,7 @@ makes it easy to figure out what is going on. Additionally, unlike `unicorn`,
puma does not require daemonization to do zero-downtime restarts.
I see people using daemonization because they start puma directly via capistrano
task and thus want it to live on past the `cap deploy`. To this people I said:
task and thus want it to live on past the `cap deploy`. To these people I say:
You need to be using a process monitor. Nothing is making sure puma stays up in
this scenario! You're just waiting for something weird to happen, puma to die,
and to get paged at 3am. Do yourself a favor, at least the process monitoring

View file

@ -22,6 +22,8 @@ But again beware, upgrading an application sometimes involves upgrading the data
If you perform a lot of database migrations, you probably should not use phased restart and use a normal/hot restart instead (`pumactl restart`). That way, no code is shared while deploying (in that case, `preload_app!` might help for quicker deployment, see ["Clustered Mode" in the README](../README.md#clustered-mode)).
**Note**: Hot and phased restarts are only available on MRI, not on JRuby. They are also unavailable on Windows servers.
### Release Directory
If your symlink releases into a common working directory (i.e., `/current` from Capistrano), Puma won't pick up your new changes when running phased restarts without additional configuration. You should set your working directory within Puma's config to specify the directory it should use. This is a change from earlier versions of Puma (< 2.15) that would infer the directory for you.

View file

@ -32,21 +32,26 @@ Type=simple
# Preferably configure a non-privileged user
# User=
# The path to the puma application root
# Also replace the "<WD>" place holders below with this path.
WorkingDirectory=
# The path to the your application code root directory.
# Also replace the "<YOUR_APP_PATH>" place holders below with this path.
# Example /home/username/myapp
WorkingDirectory=<YOUR_APP_PATH>
# Helpful for debugging socket activation, etc.
# Environment=PUMA_DEBUG=1
# The command to start Puma. This variant uses a binstub generated via
# `bundle binstubs puma --path ./sbin` in the WorkingDirectory
# (replace "<WD>" below)
ExecStart=<WD>/sbin/puma -b tcp://0.0.0.0:9292 -b ssl://0.0.0.0:9293?key=key.pem&cert=cert.pem
# SystemD will not run puma even if it is in your path. You must specify
# an absolute URL to puma. For example /usr/local/bin/puma
# Alternatively, create a binstub with `bundle binstubs puma --path ./sbin` in the WorkingDirectory
ExecStart=/<FULLPATH>/bin/puma -C <YOUR_APP_PATH>/puma.rb
# Variant: Rails start.
# ExecStart=/<FULLPATH>/bin/puma -C <YOUR_APP_PATH>/config/puma.rb ../config.ru
# Variant: Use config file with `bind` directives instead:
# ExecStart=<WD>/sbin/puma -C config.rb
# Variant: Use `bundle exec --keep-file-descriptors puma` instead of binstub
# Variant: Specify directives inline.
# ExecStart=/<FULLPATH>/puma -b tcp://0.0.0.0:9292 -b ssl://0.0.0.0:9293?key=key.pem&cert=cert.pem
Restart=always
@ -247,6 +252,12 @@ PIDFile=<WD>/shared/tmp/pids/puma.pid
# reconsider if you actually need the forking config.
Restart=no
# `puma_ctl restart` wouldn't work without this. It's because `pumactl`
# changes PID on restart and systemd stops the service afterwards
# because of the PID change. This option prevents stopping after PID
# change.
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
~~~~

View file

@ -80,8 +80,10 @@
# can also use the "ssl_bind" option.
#
# ssl_bind '127.0.0.1', '9292', {
# cert: path_to_cert,
# key: path_to_key,
# cert: path_to_cert
# ssl_cipher_filter: cipher_filter, # optional
# verify_mode: verify_mode, # default 'none'
# }
# for JRuby additional keys are required:
# keystore: path_to_keystore,
@ -174,7 +176,8 @@
# the given timeout. If not the worker process will be restarted. This is
# not a request timeout, it is to protect against a hung or dead process.
# Setting this value will not protect against slow requests.
# Default value is 60 seconds.
#
# The minimum value is 6 seconds, the default value is 60 seconds.
#
# worker_timeout 60

View file

@ -11,7 +11,7 @@ require 'redis'
# 1. Add this plugin to your 'lib' directory
# 2. In the `puma.rb` config file add the following lines
# === Plugins ===
# require './lib/puma/plugins/redis_stop_puma'
# require './lib/puma/plugin/redis_stop_puma'
# plugin 'redis_stop_puma'
# 3. Now, when you set the redis key "puma::restart::web.1", your web.1 dyno
# will restart

View file

@ -6,11 +6,13 @@ import org.jruby.Ruby;
import org.jruby.runtime.load.BasicLibraryService;
import org.jruby.puma.Http11;
import org.jruby.puma.IOBuffer;
import org.jruby.puma.MiniSSL;
public class PumaHttp11Service implements BasicLibraryService {
public boolean basicLoad(final Ruby runtime) throws IOException {
Http11.createHttp11(runtime);
IOBuffer.createIOBuffer(runtime);
MiniSSL.createMiniSSL(runtime);
return true;
}

View file

@ -142,6 +142,7 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
VALUE obj;
SSL_CTX* ctx;
SSL* ssl;
int ssl_options;
ms_conn* conn = engine_alloc(self, &obj);
@ -164,6 +165,10 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
ID sym_ssl_cipher_filter = rb_intern("ssl_cipher_filter");
VALUE ssl_cipher_filter = rb_funcall(mini_ssl_ctx, sym_ssl_cipher_filter, 0);
ID sym_no_tlsv1 = rb_intern("no_tlsv1");
VALUE no_tlsv1 = rb_funcall(mini_ssl_ctx, sym_no_tlsv1, 0);
ctx = SSL_CTX_new(SSLv23_server_method());
conn->ctx = ctx;
@ -175,7 +180,12 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL);
}
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION);
ssl_options = SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION;
if(RTEST(no_tlsv1)) {
ssl_options |= SSL_OP_NO_TLSv1;
}
SSL_CTX_set_options(ctx, ssl_options);
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
if (!NIL_P(ssl_cipher_filter)) {
@ -189,12 +199,18 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
DH *dh = get_dh1024();
SSL_CTX_set_tmp_dh(ctx, dh);
#ifndef OPENSSL_NO_ECDH
EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_secp521r1);
#if OPENSSL_VERSION_NUMBER < 0x10002000L
// Remove this case if OpenSSL 1.0.1 (now EOL) support is no
// longer needed.
EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (ecdh) {
SSL_CTX_set_tmp_ecdh(ctx, ecdh);
EC_KEY_free(ecdh);
}
#elif OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
// Prior to OpenSSL 1.1.0, servers must manually enable server-side ECDH
// negotiation.
SSL_CTX_set_ecdh_auto(ctx, 1);
#endif
ssl = SSL_new(ctx);
@ -217,7 +233,7 @@ VALUE engine_init_client(VALUE klass) {
VALUE obj;
ms_conn* conn = engine_alloc(klass, &obj);
conn->ctx = SSL_CTX_new(DTLSv1_method());
conn->ctx = SSL_CTX_new(DTLS_method());
conn->ssl = SSL_new(conn->ctx);
SSL_set_app_data(conn->ssl, NULL);
SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL);
@ -433,6 +449,18 @@ void Init_mini_ssl(VALUE puma) {
mod = rb_define_module_under(puma, "MiniSSL");
eng = rb_define_class_under(mod, "Engine", rb_cObject);
// OpenSSL Build / Runtime/Load versions
/* Version of OpenSSL that Puma was compiled with */
rb_define_const(mod, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT));
#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000
/* Version of OpenSSL that Puma loaded with */
rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(OpenSSL_version(OPENSSL_VERSION)));
#else
rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION)));
#endif
rb_define_singleton_method(mod, "check", noop, 0);
eError = rb_define_class_under(mod, "SSLError", rb_eStandardError);

View file

@ -0,0 +1,72 @@
package org.jruby.puma;
import org.jruby.*;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
/**
* @author kares
*/
public class IOBuffer extends RubyObject {
private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new IOBuffer(runtime, klass);
}
};
public static void createIOBuffer(Ruby runtime) {
RubyModule mPuma = runtime.defineModule("Puma");
RubyClass cIOBuffer = mPuma.defineClassUnder("IOBuffer", runtime.getObject(), ALLOCATOR);
cIOBuffer.defineAnnotatedMethods(IOBuffer.class);
}
private static final int DEFAULT_SIZE = 4096;
final ByteList buffer = new ByteList(DEFAULT_SIZE);
IOBuffer(Ruby runtime, RubyClass klass) {
super(runtime, klass);
}
@JRubyMethod
public RubyInteger used(ThreadContext context) {
return context.runtime.newFixnum(buffer.getRealSize());
}
@JRubyMethod
public RubyInteger capacity(ThreadContext context) {
return context.runtime.newFixnum(buffer.unsafeBytes().length);
}
@JRubyMethod
public IRubyObject reset() {
buffer.setRealSize(0);
return this;
}
@JRubyMethod(name = { "to_s", "to_str" })
public RubyString to_s(ThreadContext context) {
return RubyString.newStringShared(context.runtime, buffer.unsafeBytes(), 0, buffer.getRealSize());
}
@JRubyMethod(name = "<<")
public IRubyObject add(IRubyObject str) {
addImpl(str.convertToString());
return this;
}
@JRubyMethod(rest = true)
public IRubyObject append(IRubyObject[] strs) {
for (IRubyObject str : strs) addImpl(str.convertToString());
return this;
}
private void addImpl(RubyString str) {
buffer.append(str.getByteList());
}
}

View file

@ -158,7 +158,13 @@ public class MiniSSL extends RubyObject {
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
engine = sslCtx.createSSLEngine();
String[] protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
String[] protocols;
if(miniSSLContext.callMethod(threadContext, "no_tlsv1").isTrue()) {
protocols = new String[] { "TLSv1.1", "TLSv1.2" };
} else {
protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
}
engine.setEnabledProtocols(protocols);
engine.setUseClientMode(false);

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'uri'
require 'socket'
@ -90,19 +92,19 @@ module Puma
case uri.scheme
when "tcp"
if fd = @inherited_fds.delete(str)
logger.log "* Inherited #{str}"
io = inherit_tcp_listener uri.host, uri.port, fd
logger.log "* Inherited #{str}"
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
logger.log "* Activated #{str}"
io = inherit_tcp_listener uri.host, uri.port, sock
logger.log "* Activated #{str}"
else
params = Util.parse_query uri.query
opt = params.key?('low_latency')
bak = params.fetch('backlog', 1024).to_i
logger.log "* Listening on #{str}"
io = add_tcp_listener uri.host, uri.port, opt, bak
logger.log "* Listening on #{str}"
end
@listeners << [str, io] if io
@ -110,14 +112,12 @@ module Puma
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
if fd = @inherited_fds.delete(str)
logger.log "* Inherited #{str}"
io = inherit_unix_listener path, fd
logger.log "* Inherited #{str}"
elsif sock = @activated_sockets.delete([ :unix, path ])
logger.log "* Activated #{str}"
io = inherit_unix_listener path, sock
logger.log "* Activated #{str}"
else
logger.log "* Listening on #{str}"
umask = nil
mode = nil
backlog = 1024
@ -139,6 +139,7 @@ module Puma
end
io = add_unix_listener path, umask, mode, backlog
logger.log "* Listening on #{str}"
end
@listeners << [str, io]
@ -186,6 +187,8 @@ module Puma
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
end
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
if params['verify_mode']
ctx.verify_mode = case params['verify_mode']
when "peer"
@ -204,11 +207,11 @@ module Puma
logger.log "* Inherited #{str}"
io = inherit_ssl_listener fd, ctx
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
logger.log "* Activated #{str}"
io = inherit_ssl_listener sock, ctx
logger.log "* Activated #{str}"
else
logger.log "* Listening on #{str}"
io = add_ssl_listener uri.host, uri.port, ctx
logger.log "* Listening on #{str}"
end
@listeners << [str, io] if io

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'optparse'
require 'uri'

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
class IO
# We need to use this for a jruby work around on both 1.8 and 1.9.
# So this either creates the constant (on 1.8), or harmlessly
@ -145,8 +147,11 @@ module Puma
def decode_chunk(chunk)
if @partial_part_left > 0
if @partial_part_left <= chunk.size
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
if @partial_part_left > 2
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
end
chunk = chunk[@partial_part_left..-1]
@partial_part_left = 0
else
@body << chunk
@partial_part_left -= chunk.size
@ -168,9 +173,9 @@ module Puma
if len == 0
@body.rewind
rest = io.read
rest = rest[2..-1] if rest.start_with?("\r\n")
@buffer = rest.empty? ? nil : rest
@requests_served += 1
@ready = true
set_ready
return true
end
@ -208,7 +213,7 @@ module Puma
while true
begin
chunk = @io.read_nonblock(4096)
rescue Errno::EAGAIN
rescue IO::WaitReadable
return false
rescue SystemCallError, IOError
raise ConnectionError, "Connection error detected during read"
@ -218,8 +223,7 @@ module Puma
unless chunk
@body.close
@buffer = nil
@requests_served += 1
@ready = true
set_ready
raise EOFError
end
@ -228,6 +232,8 @@ module Puma
end
def setup_body
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
if @env[HTTP_EXPECT] == CONTINUE
# TODO allow a hook here to check the headers before
# going forward
@ -252,8 +258,7 @@ module Puma
unless cl
@buffer = body.empty? ? nil : body
@body = EmptyBody
@requests_served += 1
@ready = true
set_ready
return true
end
@ -262,8 +267,7 @@ module Puma
if remain <= 0
@body = StringIO.new(body)
@buffer = nil
@requests_served += 1
@ready = true
set_ready
return true
end
@ -298,8 +302,7 @@ module Puma
# No data means a closed socket
unless data
@buffer = nil
@requests_served += 1
@ready = true
set_ready
raise EOFError
end
@ -335,8 +338,7 @@ module Puma
# No data means a closed socket
unless data
@buffer = nil
@requests_served += 1
@ready = true
set_ready
raise EOFError
end
@ -413,8 +415,7 @@ module Puma
unless chunk
@body.close
@buffer = nil
@requests_served += 1
@ready = true
set_ready
raise EOFError
end
@ -423,8 +424,7 @@ module Puma
if remain <= 0
@body.rewind
@buffer = nil
@requests_served += 1
@ready = true
set_ready
return true
end
@ -433,6 +433,14 @@ module Puma
false
end
def set_ready
if @body_read_start
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
end
@requests_served += 1
@ready = true
end
def write_400
begin
@io << ERROR_400_RESPONSE

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'puma/runner'
require 'puma/util'
require 'puma/plugin'
@ -292,7 +294,9 @@ module Puma
begin
b = server.backlog || 0
r = server.running || 0
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r} }\n!
t = server.pool_capacity || 0
m = server.max_threads || 0
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
io << payload
rescue IOError
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Puma
# Rack::CommonLogger forwards every request to the given +app+, and
# logs a line in the

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'puma/rack/builder'
require 'puma/plugin'
require 'puma/const'

View file

@ -1,4 +1,6 @@
#encoding: utf-8
# frozen_string_literal: true
module Puma
class UnsupportedOption < RuntimeError
end
@ -98,8 +100,8 @@ module Puma
# too taxing on performance.
module Const
PUMA_VERSION = VERSION = "3.11.4".freeze
CODE_NAME = "Love Song".freeze
PUMA_VERSION = VERSION = "3.12.0".freeze
CODE_NAME = "Llamas in Pajamas".freeze
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
FAST_TRACK_KA_TIMEOUT = 0.2

View file

@ -1,8 +1,10 @@
# frozen_string_literal: true
require 'optparse'
require 'puma/state_file'
require 'puma/const'
require 'puma/detect'
require 'puma/configuration'
require_relative 'state_file'
require_relative 'const'
require_relative 'detect'
require_relative 'configuration'
require 'uri'
require 'socket'
@ -129,7 +131,7 @@ module Puma
uri = URI.parse @control_url
# create server object by scheme
@server = case uri.scheme
server = case uri.scheme
when "tcp"
TCPSocket.new uri.host, uri.port
when "unix"
@ -147,9 +149,9 @@ module Puma
url = url + "?token=#{@control_auth_token}"
end
@server << "GET #{url} HTTP/1.0\r\n\r\n"
server << "GET #{url} HTTP/1.0\r\n\r\n"
unless data = @server.read
unless data = server.read
raise "Server closed connection before responding"
end
@ -172,8 +174,8 @@ module Puma
message "Command #{@command} sent success"
message response.last if @command == "stats" || @command == "gc-stats"
end
@server.close
ensure
server.close if server && !server.closed?
end
def send_signal

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'puma/launcher'
require 'puma/configuration'

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Process
# This overrides the default version because it is broken if it

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Puma
module Delegation
def forward(what, who)

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Puma
IS_JRUBY = defined?(JRUBY_VERSION)

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Puma
# The methods that are available for use inside the config file.
# These same methods are used in Puma cli and the rack handler
@ -55,6 +57,14 @@ module Puma
@plugins.clear
end
def set_default_host(host)
@options[:default_host] = host
end
def default_host
@options[:default_host] || Configuration::DefaultTCPHost
end
def inject(&blk)
instance_eval(&blk)
end
@ -138,7 +148,7 @@ module Puma
# Define the TCP port to bind to. Use +bind+ for more advanced options.
#
def port(port, host=nil)
host ||= Configuration::DefaultTCPHost
host ||= default_host
bind "tcp://#{host}:#{port}"
end
@ -285,12 +295,14 @@ module Puma
def ssl_bind(host, port, opts)
verify = opts.fetch(:verify_mode, 'none')
no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
if defined?(JRUBY_VERSION)
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}"
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}"
else
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&verify_mode=#{verify}"
ssl_cipher_filter = "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" if opts[:ssl_cipher_filter]
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}"
end
end
@ -365,6 +377,21 @@ module Puma
alias_method :after_worker_boot, :after_worker_fork
# Code to run out-of-band when the worker is idle.
# These hooks run immediately after a request has finished
# processing and there are no busy threads on the worker.
# The worker doesn't accept new requests until this code finishes.
#
# This hook is useful for running out-of-band garbage collection
# or scheduling asynchronous tasks to execute after a response.
#
# This can be called multiple times to add hooks.
#
def out_of_band(&block)
@options[:out_of_band] ||= []
@options[:out_of_band] << block
end
# The directory to operate out of.
def directory(dir)
@options[:directory] = dir.to_s
@ -424,6 +451,13 @@ module Puma
# that have not checked in within the given +timeout+.
# This mitigates hung processes. Default value is 60 seconds.
def worker_timeout(timeout)
timeout = Integer(timeout)
min = Cluster::WORKER_CHECK_INTERVAL
if timeout <= min
raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
end
@options[:worker_timeout] = Integer(timeout)
end
@ -493,7 +527,7 @@ module Puma
when Hash
if hdr = val[:header]
@options[:remote_address] = :header
@options[:remote_address_header] = "HTTP_" + hdr.upcase.gsub("-", "_")
@options[:remote_address_header] = "HTTP_" + hdr.upcase.tr("-", "_")
else
raise "Invalid value for set_remote_address - #{val.inspect}"
end

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'puma/const'
require "puma/null_io"
require 'stringio'

View file

@ -1,7 +1,4 @@
require 'puma/detect'
# frozen_string_literal: true
if Puma.jruby?
require 'puma/java_io_buffer'
else
require 'puma/puma_http11'
end
require 'puma/detect'
require 'puma/puma_http11'

View file

@ -1,45 +0,0 @@
require 'java'
# Conservative native JRuby/Java implementation of IOBuffer
# backed by a ByteArrayOutputStream and conversion between
# Ruby String and Java bytes
module Puma
class JavaIOBuffer < java.io.ByteArrayOutputStream
field_reader :buf
end
class IOBuffer
BUF_DEFAULT_SIZE = 4096
def initialize
@buf = JavaIOBuffer.new(BUF_DEFAULT_SIZE)
end
def reset
@buf.reset
end
def <<(str)
bytes = str.to_java_bytes
@buf.write(bytes, 0, bytes.length)
end
def append(*strs)
strs.each { |s| self << s; }
end
def to_s
String.from_java_bytes @buf.to_byte_array
end
alias_method :to_str, :to_s
def used
@buf.size
end
def capacity
@buf.buf.length
end
end
end

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'ffi'
module Puma

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'puma/events'
require 'puma/detect'
@ -63,8 +65,8 @@ module Puma
generate_restart_data
if clustered? && (Puma.jruby? || Puma.windows?)
unsupported 'worker mode not supported on JRuby or Windows'
if clustered? && !Process.respond_to?(:fork)
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
end
if @options[:daemon] && Puma.windows?

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
begin
require 'io/wait'
rescue LoadError
rescue LoadError
end
module Puma
@ -175,6 +177,11 @@ module Puma
class Context
attr_accessor :verify_mode
attr_reader :no_tlsv1
def initialize
@no_tlsv1 = false
end
if defined?(JRUBY_VERSION)
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
@ -213,11 +220,18 @@ module Puma
@ca = ca
end
def check
raise "Key not configured" unless @key
raise "Cert not configured" unless @cert
end
end
def no_tlsv1=(tlsv1)
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
@no_tlsv1 = tlsv1
end
end
VERIFY_NONE = 0

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Puma
# Provides an IO-like object that always appears to contain no data.
# Used as the value for rack.input when the request has no body.

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Puma
class UnknownPlugin < RuntimeError; end

View file

@ -1,33 +0,0 @@
# :stopdoc:
require 'uri/common'
# Issue:
# http://bugs.ruby-lang.org/issues/5925
#
# Relevant commit:
# https://github.com/ruby/ruby/commit/edb7cdf1eabaff78dfa5ffedfbc2e91b29fa9ca1
module URI
begin
256.times do |i|
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
end
TBLENCWWWCOMP_[' '] = '+'
TBLENCWWWCOMP_.freeze
256.times do |i|
h, l = i>>4, i&15
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
end
TBLDECWWWCOMP_['+'] = ' '
TBLDECWWWCOMP_.freeze
rescue Exception
end
end
# :startdoc:

View file

@ -110,7 +110,8 @@ module Puma::Rack
has_options = false
server.valid_options.each do |name, description|
next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
info << " -O %-21s %s" % [name, description]
has_options = true
end

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'puma/util'
require 'puma/minissl'
@ -21,7 +23,7 @@ module Puma
#
# When the request is written to by the client then the `IO.select` will "wake up" and
# return the references to any objects that caused it to "wake". The reactor
# then loops through each of these request objects, and sees if they're complete. If they
# then loops through each of these request objects, and sees if they're complete. If they
# have a full header and body then the reactor passes the request to a thread pool.
# Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request.
#
@ -36,7 +38,7 @@ module Puma
# Creates an instance of Puma::Reactor
#
# The `server` argument is an instance of `Puma::Server`
# this is used to write a response for "low level errors"
# that is used to write a response for "low level errors"
# when there is an exception inside of the reactor.
#
# The `app_pool` is an instance of `Puma::ThreadPool`.
@ -92,7 +94,7 @@ module Puma
# `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
#
# Each element in the first entry is iterated over. The `Puma::Client` object is not
# the `@ready` pipe, so the reactor checks to see if it has the fully header and body with
# the `@ready` pipe, so the reactor checks to see if it has the full header and body with
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
# then the request is passed off to the `@app_pool` thread pool so that a "worker thread"
# can pick up the request and begin to execute application logic. This is done
@ -108,9 +110,9 @@ module Puma
# In addition to being woken via a write to one of the sockets the `IO.select` will
# periodically "time out" of the sleep. One of the functions of this is to check for
# any requests that have "timed out". At the end of the loop it's checked to see if
# the first element in the `@timeout` array has exceed it's allowed time. If so,
# the client object is removed from the timeout aray, a 408 response is written.
# Then it's connection is closed, and the object is removed from the `sockets` array
# the first element in the `@timeout` array has exceed its allowed time. If so,
# the client object is removed from the timeout array, a 408 response is written.
# Then its connection is closed, and the object is removed from the `sockets` array
# that watches for new data.
#
# This behavior loops until all the objects that have timed out have been removed.
@ -292,7 +294,7 @@ module Puma
#
# The main body of the reactor loop is in `run_internal` and it
# will sleep on `IO.select`. When a new connection is added to the
# reactor it cannot be added directly to the `sockets` aray, because
# reactor it cannot be added directly to the `sockets` array, because
# the `IO.select` will not be watching for it yet.
#
# Instead what needs to happen is that `IO.select` needs to be woken up,

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'puma/server'
require 'puma/const'

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'stringio'
require 'puma/thread_pool'
@ -14,10 +16,6 @@ require 'puma/util'
require 'puma/puma_http11'
unless Puma.const_defined? "IOBuffer"
require 'puma/io_buffer'
end
require 'socket'
module Puma
@ -77,7 +75,6 @@ module Puma
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
@binder = Binder.new(events)
@own_binder = true
@leak_stack_on_error = true
@ -100,7 +97,6 @@ module Puma
def inherit_binder(bind)
@binder = bind
@own_binder = false
end
def tcp_mode!
@ -168,6 +164,18 @@ module Puma
@thread_pool and @thread_pool.spawned
end
# This number represents the number of requests that
# the server is capable of taking right now.
#
# For example if the number is 5 then it means
# there are 5 threads sitting idle ready to take
# a request. If one request comes in, then the
# value would be 4 until it finishes processing.
def pool_capacity
@thread_pool and @thread_pool.pool_capacity
end
# Lopez Mode == raw tcp apps
def run_lopez_mode(background=true)
@ -257,10 +265,10 @@ module Puma
end
# Prevent can't modify frozen IOError (RuntimeError)
@notify.close rescue nil
if @status != :restart and @own_binder
@binder.close
begin
@notify.close
rescue IOError
# no biggy
end
end
@ -385,7 +393,10 @@ module Puma
end
pool << client
pool.wait_until_not_full
busy_threads = pool.wait_until_not_full
if busy_threads == 0
@options[:out_of_band].each(&:call) if @options[:out_of_band]
end
end
rescue SystemCallError
# nothing
@ -417,10 +428,6 @@ module Puma
ensure
@check.close
@notify.close
if @status != :restart and @own_binder
@binder.close
end
end
@events.fire :state, :done
@ -929,6 +936,10 @@ module Puma
@events.debug "Drained #{count} additional connections."
end
if @status != :restart
@binder.close
end
if @thread_pool
if timeout = @options[:force_shutdown_after]
@thread_pool.shutdown timeout.to_i

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'puma/runner'
require 'puma/detect'
require 'puma/plugin'
@ -14,7 +16,9 @@ module Puma
def stats
b = @server.backlog || 0
r = @server.running || 0
%Q!{ "backlog": #{b}, "running": #{r} }!
t = @server.pool_capacity || 0
m = @server.max_threads || 0
%Q!{ "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
end
def restart
@ -22,7 +26,7 @@ module Puma
end
def stop
@server.stop false
@server.stop(false) if @server
end
def halt
@ -32,7 +36,7 @@ module Puma
def stop_blocked
log "- Gracefully stopping, waiting for requests to finish"
@control.stop(true) if @control
@server.stop(true)
@server.stop(true) if @server
end
def jruby_daemon?

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'yaml'
module Puma

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Puma
class TCPLogger
def initialize(logger, app, quiet=false)

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'thread'
module Puma
@ -58,7 +60,7 @@ module Puma
@clean_thread_locals = false
end
attr_reader :spawned, :trim_requested
attr_reader :spawned, :trim_requested, :waiting
attr_accessor :clean_thread_locals
def self.clean_thread_locals
@ -73,6 +75,10 @@ module Puma
@mutex.synchronize { @todo.size }
end
def pool_capacity
waiting + (@max - spawned)
end
# :nodoc:
#
# Must be called with @mutex held!
@ -188,6 +194,9 @@ module Puma
# method would not block and another request would be added into the reactor
# by the server. This would continue until a fully bufferend request
# makes it through the reactor and can then be processed by the thread pool.
#
# Returns the current number of busy threads, or +nil+ if shutting down.
#
def wait_until_not_full
@mutex.synchronize do
while true
@ -197,7 +206,8 @@ module Puma
# is work queued that cannot be handled by waiting
# threads, then accept more work until we would
# spin up the max number of threads.
return if @todo.size - @waiting < @max - @spawned
busy_threads = @spawned - @waiting + @todo.size
return busy_threads if @max > busy_threads
@not_full.wait @mutex
end

View file

@ -1,10 +1,6 @@
major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
# frozen_string_literal: true
if major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
require 'puma/rack/backports/uri/common_193'
else
require 'uri/common'
end
require 'uri/common'
module Puma
module Util

View file

@ -49,6 +49,9 @@ module Rack
self.set_host_port_to_config(host, port, user_config)
end
if default_options[:Host]
file_config.set_default_host(default_options[:Host])
end
self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)
user_config.app app

View file

@ -10,6 +10,8 @@ end
begin
require "bundler/setup"
# bundler/setup may not load bundler
require "bundler" unless Bundler.const_defined?(:ORIGINAL_ENV)
rescue LoadError
warn "Failed to load bundler ... this should only happen during package building"
end
@ -70,12 +72,31 @@ if ENV['CI']
end
module SkipTestsBasedOnRubyEngine
def skip_on_jruby
skip "Skipped on JRuby" if Puma.jruby?
# called with one or more params, like skip_on :jruby, :windows
# optional suffix kwarg is appended to the skip message
# optional suffix bt should generally not used
def skip_on(*engs, suffix: '', bt: caller)
skip_msg = false
engs.each do |eng|
skip_msg = case eng
when :jruby then "Skipped on JRuby#{suffix}" if Puma.jruby?
when :windows then "Skipped on Windows#{suffix}" if Puma.windows?
when :appveyor then "Skipped on Appveyor#{suffix}" if ENV["APPVEYOR"]
when :ci then "Skipped on ENV['CI']#{suffix}" if ENV["CI"]
else false
end
skip skip_msg, bt if skip_msg
end
end
def skip_on_appveyor
skip "Skipped on Appveyor" if ENV["APPVEYOR"]
# called with only one param
def skip_unless(eng, bt: caller)
skip_msg = case eng
when :jruby then "Skip unless JRuby" unless Puma.jruby?
when :windows then "Skip unless Windows" unless Puma.windows?
else false
end
skip skip_msg, bt if skip_msg
end
end

4
test/rackup/10seconds.ru Normal file
View file

@ -0,0 +1,4 @@
run lambda { |env|
sleep 10
[200, {}, ["Hello World"]]
}

View file

@ -10,7 +10,7 @@ class TestBinder < Minitest::Test
end
def test_localhost_addresses_dont_alter_listeners_for_tcp_addresses
skip_on_jruby
skip_on :jruby
@binder.parse(["tcp://localhost:10001"], @events)
@ -18,8 +18,7 @@ class TestBinder < Minitest::Test
end
def test_localhost_addresses_dont_alter_listeners_for_ssl_addresses
skip_on_appveyor
skip_on_jruby
skip_on :jruby
key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
@ -30,8 +29,7 @@ class TestBinder < Minitest::Test
end
def test_binder_parses_ssl_cipher_filter
skip_on_appveyor
skip_on_jruby
skip_on :jruby
key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
@ -45,15 +43,54 @@ class TestBinder < Minitest::Test
end
def test_binder_parses_jruby_ssl_options
skip unless Puma.jruby?
skip_unless :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"
@binder.parse(["ssl://0.0.0.0?keystore=#{keystore}&ssl_cipher_list=#{ssl_cipher_list}"], @events)
@binder.parse(["ssl://0.0.0.0:8080?keystore=#{keystore}&keystore-pass=&ssl_cipher_list=#{ssl_cipher_list}"], @events)
ssl= @binder.instance_variable_get(:@ios)[0]
ctx = ssl.instance_variable_get(:@ctx)
assert_equal(keystore, ctx.keystore)
assert_equal(ssl_cipher_list, ctx.ssl_cipher_list)
end
def test_binder_parses_tlsv1_disabled
skip_on :jruby
key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
@binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}&no_tlsv1=true"], @events)
ssl = @binder.instance_variable_get(:@ios).first
ctx = ssl.instance_variable_get(:@ctx)
assert_equal(true, ctx.no_tlsv1)
end
def test_binder_parses_tlsv1_enabled
skip_on :jruby
key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
@binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}&no_tlsv1=false"], @events)
ssl = @binder.instance_variable_get(:@ios).first
ctx = ssl.instance_variable_get(:@ctx)
refute(ctx.no_tlsv1)
end
def test_binder_parses_tlsv1_unspecified_defaults_to_enabled
skip_on :jruby
key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
@binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}"], @events)
ssl = @binder.instance_variable_get(:@ios).first
ctx = ssl.instance_variable_get(:@ctx)
refute(ctx.no_tlsv1)
end
end

View file

@ -56,15 +56,15 @@ class TestCLI < Minitest::Test
s = TCPSocket.new "127.0.0.1", 9877
s << "GET /stats HTTP/1.0\r\n\r\n"
body = s.read
assert_equal '{ "backlog": 0, "running": 0 }', body.split(/\r?\n/).last
assert_equal '{ "backlog": 0, "running": 0 }', Puma.stats
assert_equal '{ "backlog": 0, "running": 0, "pool_capacity": 16, "max_threads": 16 }', body.split(/\r?\n/).last
assert_equal '{ "backlog": 0, "running": 0, "pool_capacity": 16, "max_threads": 16 }', Puma.stats
cli.launcher.stop
t.join
end
unless Puma.jruby? || Puma.windows?
def test_control_clustered
skip_on :jruby, :windows, suffix: " - Puma::Binder::UNIXServer is not defined"
url = "unix://#{@tmp_path}"
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
@ -95,13 +95,14 @@ class TestCLI < Minitest::Test
s = UNIXSocket.new @tmp_path
s << "GET /stats HTTP/1.0\r\n\r\n"
body = s.read
assert_match(/\{ "workers": 2, "phase": 0, "booted_workers": 2, "old_workers": 0, "worker_status": \[\{ "pid": \d+, "index": 0, "phase": 0, "booted": true, "last_checkin": "[^"]+", "last_status": \{ "backlog":0, "running":2 \} \},\{ "pid": \d+, "index": 1, "phase": 0, "booted": true, "last_checkin": "[^"]+", "last_status": \{ "backlog":0, "running":2 \} \}\] \}/, body.split("\r\n").last)
assert_match(/\{ "workers": 2, "phase": 0, "booted_workers": 2, "old_workers": 0, "worker_status": \[\{ "pid": \d+, "index": 0, "phase": 0, "booted": true, "last_checkin": "[^"]+", "last_status": \{ "backlog":0, "running":2, "pool_capacity":2, "max_threads": 2 \} \},\{ "pid": \d+, "index": 1, "phase": 0, "booted": true, "last_checkin": "[^"]+", "last_status": \{ "backlog":0, "running":2, "pool_capacity":2, "max_threads": 2 \} \}\] \}/, body.split("\r\n").last)
cli.launcher.stop
t.join
end
def test_control
skip_on :jruby, :windows, suffix: " - Puma::Binder::UNIXServer is not defined"
url = "unix://#{@tmp_path}"
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
@ -118,13 +119,14 @@ class TestCLI < Minitest::Test
s << "GET /stats HTTP/1.0\r\n\r\n"
body = s.read
assert_equal '{ "backlog": 0, "running": 0 }', body.split("\r\n").last
assert_equal '{ "backlog": 0, "running": 0, "pool_capacity": 16, "max_threads": 16 }', body.split("\r\n").last
cli.launcher.stop
t.join
end
def test_control_stop
skip_on :jruby, :windows, suffix: " - Puma::Binder::UNIXServer is not defined"
url = "unix://#{@tmp_path}"
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
@ -147,6 +149,7 @@ class TestCLI < Minitest::Test
end
def test_control_gc_stats
skip_on :jruby, :windows, suffix: " - Puma::Binder::UNIXServer is not defined"
url = "unix://#{@tmp_path}"
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
@ -201,6 +204,7 @@ class TestCLI < Minitest::Test
end
def test_tmp_control
skip_on :jruby
url = "tcp://127.0.0.1:8232"
cli = Puma::CLI.new ["--state", @tmp_path, "--control", "auto"]
cli.launcher.write_state
@ -217,6 +221,7 @@ class TestCLI < Minitest::Test
end
def test_state_file_callback_filtering
skip_on :jruby, :windows, suffix: " - worker mode not supported"
cli = Puma::CLI.new [ "--config", "test/config/state_file_testing_config.rb",
"--state", @tmp_path ]
cli.launcher.write_state
@ -227,8 +232,6 @@ class TestCLI < Minitest::Test
assert_empty keys_not_stripped
end
end # JRUBY or Windows
def test_state
url = "tcp://127.0.0.1:8232"
cli = Puma::CLI.new ["--state", @tmp_path, "--control", url]

View file

@ -43,6 +43,41 @@ class TestConfigFile < Minitest::Test
end
end
def test_ssl_bind
skip_on :jruby
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
cert: "/path/to/cert",
key: "/path/to/key",
verify_mode: "the_verify_mode",
}
end
conf.load
ssl_binding = "ssl://0.0.0.0:9292?cert=/path/to/cert&key=/path/to/key&verify_mode=the_verify_mode"
assert_equal [ssl_binding], conf.options[:binds]
end
def test_ssl_bind_with_cipher_filter
skip_on :jruby
cipher_filter = "!aNULL:AES+SHA"
conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
cert: "cert",
key: "key",
ssl_cipher_filter: cipher_filter,
}
end
conf.load
ssl_binding = conf.options[:binds].first
assert ssl_binding.include?("&ssl_cipher_filter=#{cipher_filter}")
end
def test_lowlevel_error_handler_DSL
conf = Puma::Configuration.new do |c|
c.load "test/config/app.rb"

View file

@ -2,6 +2,7 @@ require_relative "helper"
require "puma/cli"
require "puma/control_cli"
require "open3"
# These don't run on travis because they're too fragile
@ -67,7 +68,7 @@ class TestIntegration < Minitest::Test
end
def restart_server_and_listen(argv)
skip_on_appveyor
skip_on :windows
server(argv)
s = connect
initial_reply = read_body(s)
@ -115,7 +116,7 @@ class TestIntegration < Minitest::Test
end
def test_stop_via_pumactl
skip if Puma.jruby? || Puma.windows?
skip_on :jruby, :windows
conf = Puma::Configuration.new do |c|
c.quiet
@ -148,7 +149,7 @@ class TestIntegration < Minitest::Test
end
def test_phased_restart_via_pumactl
skip if Puma.jruby? || Puma.windows? || ENV['CI']
skip_on :jruby, :windows, :ci, suffix: " - UNIX sockets are not recommended"
conf = Puma::Configuration.new do |c|
c.quiet
@ -181,7 +182,7 @@ class TestIntegration < Minitest::Test
until done
@events.stdout.rewind
log = @events.stdout.readlines.join("")
if log.match(/- Worker \d \(pid: \d+\) booted, phase: 1/)
if log =~ /- Worker \d \(pid: \d+\) booted, phase: 1/
assert_match(/TERM sent/, log)
assert_match(/- Worker \d \(pid: \d+\) booted, phase: 1/, log)
done = true
@ -195,7 +196,7 @@ class TestIntegration < Minitest::Test
end
def test_kill_unknown_via_pumactl
skip if Puma.jruby? || Puma.windows?
skip_on :jruby, :windows
# we run ls to get a 'safe' pid to pass off as puma in cli stop
# do not want to accidently kill a valid other process
@ -220,7 +221,7 @@ class TestIntegration < Minitest::Test
end
def test_restart_closes_keepalive_sockets_workers
skip_on_jruby
skip_on :jruby
_, new_reply = restart_server_and_listen("-q -w 2 test/rackup/hello.ru")
assert_equal "Hello World", new_reply
end
@ -229,7 +230,7 @@ class TestIntegration < Minitest::Test
def test_restart_restores_environment
# jruby has a bug where setting `nil` into the ENV or `delete` do not change the
# next workers ENV
skip_on_jruby
skip_on :jruby
initial_reply, new_reply = restart_server_and_listen("-q test/rackup/hello-env.ru")
@ -239,7 +240,7 @@ class TestIntegration < Minitest::Test
end
def test_term_signal_exit_code_in_single_mode
skip if Puma.jruby? || Puma.windows?
skip_on :jruby, :windows
pid = start_forked_server("test/rackup/hello.ru")
_, status = stop_forked_server(pid)
@ -248,11 +249,38 @@ class TestIntegration < Minitest::Test
end
def test_term_signal_exit_code_in_clustered_mode
skip if Puma.jruby? || Puma.windows?
skip_on :jruby, :windows
pid = start_forked_server("-w 2 test/rackup/hello.ru")
_, status = stop_forked_server(pid)
assert_equal 15, status
end
def test_not_accepts_new_connections_after_term_signal
skip_on :jruby, :windows
server('test/rackup/10seconds.ru')
_stdin, curl_stdout, _stderr, curl_wait_thread = Open3.popen3("curl 127.0.0.1:#{@tcp_port}")
sleep 1 # ensure curl send a request
Process.kill(:TERM, @server.pid)
true while @server.gets !~ /Gracefully stopping/ # wait for server to begin graceful shutdown
# Invoke a request which must be rejected
_stdin, _stdout, rejected_curl_stderr, rejected_curl_wait_thread = Open3.popen3("curl 127.0.0.1:#{@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
curl_wait_thread.join
rejected_curl_wait_thread.join
assert_match /Hello World/, curl_stdout.read
assert_match /Connection refused/, rejected_curl_stderr.read
Process.wait(@server.pid)
@server = nil # prevent `#teardown` from killing already killed server
end
end

View file

@ -658,6 +658,83 @@ EOF
assert_equal "hello", body
end
def test_chunked_request_pause_between_cr_lf_after_size_of_second_chunk
body = nil
@server.app = proc { |env|
body = env['rack.input'].read
[200, {}, [""]]
}
@server.add_tcp_listener @host, @port
@server.run
part1 = 'a' * 4200
chunked_body = "#{part1.size.to_s(16)}\r\n#{part1}\r\n1\r\nb\r\n0\r\n\r\n"
sock = TCPSocket.new @host, @server.connected_port
sock << "PUT /path HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n"
sleep 0.1
sock << chunked_body[0..-10]
sleep 0.1
sock << chunked_body[-9..-1]
data = sock.read
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal (part1 + 'b'), body
end
def test_chunked_request_pause_between_closing_cr_lf
body = nil
@server.app = proc { |env|
body = env['rack.input'].read
[200, {}, [""]]
}
@server.add_tcp_listener @host, @port
@server.run
sock = TCPSocket.new @host, @server.connected_port
sock << "PUT /path HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r"
sleep 1
sock << "\n0\r\n\r\n"
data = sock.read
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal 'hello', body
end
def test_chunked_request_pause_before_closing_cr_lf
body = nil
@server.app = proc { |env|
body = env['rack.input'].read
[200, {}, [""]]
}
@server.add_tcp_listener @host, @port
@server.run
sock = TCPSocket.new @host, @server.connected_port
sock << "PUT /path HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello"
sleep 1
sock << "\r\n0\r\n\r\n"
data = sock.read
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
assert_equal 'hello', body
end
def test_chunked_request_header_case
body = nil
@server.app = proc { |env|
@ -677,6 +754,55 @@ EOF
assert_equal "hello", body
end
def test_chunked_keep_alive
body = nil
@server.app = proc { |env|
body = env['rack.input'].read
[200, {}, [""]]
}
@server.add_tcp_listener @host, @port
@server.run
sock = TCPSocket.new @host, @server.connected_port
sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n"
h = header(sock)
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
assert_equal "hello", body
sock.close
end
def test_chunked_keep_alive_two_back_to_back
body = nil
@server.app = proc { |env|
body = env['rack.input'].read
[200, {}, [""]]
}
@server.add_tcp_listener @host, @port
@server.run
sock = TCPSocket.new @host, @server.connected_port
sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n"
h = header(sock)
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
assert_equal "hello", body
sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ngood\r\n3\r\nbye\r\n0\r\n\r\n"
sleep 0.1
h = header(sock)
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h
assert_equal "goodbye", body
sock.close
end
def test_empty_header_values
@server.app = proc { |env| [200, {"X-Empty-Header" => ""}, []] }
@ -691,4 +817,44 @@ EOF
assert_equal "HTTP/1.0 200 OK\r\nX-Empty-Header: \r\n\r\n", data
end
def test_request_body_wait
request_body_wait = nil
@server.app = proc { |env|
request_body_wait = env['puma.request_body_wait']
[204, {}, []]
}
@server.add_tcp_listener @host, @port
@server.run
sock = TCPSocket.new @host, @server.connected_port
sock << "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nh"
sleep 1
sock << "ello"
sock.gets
assert request_body_wait >= 1000
end
def test_request_body_wait_chunked
request_body_wait = nil
@server.app = proc { |env|
request_body_wait = env['puma.request_body_wait']
[204, {}, []]
}
@server.add_tcp_listener @host, @port
@server.run
sock = TCPSocket.new @host, @server.connected_port
sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n"
sleep 1
sock << "4\r\nello\r\n0\r\n"
sock.gets
assert request_body_wait >= 1000
end
end

View file

@ -1,4 +1,10 @@
require_relative "helper"
require "puma/minissl"
require "puma/puma_http11"
#———————————————————————————————————————————————————————————————————————————————
# NOTE: ALL TESTS BYPASSED IF DISABLE_SSL IS TRUE
#———————————————————————————————————————————————————————————————————————————————
class SSLEventsHelper < ::Puma::Events
attr_accessor :addr, :cert, :error
@ -11,7 +17,11 @@ class SSLEventsHelper < ::Puma::Events
end
DISABLE_SSL = begin
Puma::Server.class
Puma::MiniSSL.check
puts "", RUBY_DESCRIPTION
puts "Puma::MiniSSL OPENSSL_LIBRARY_VERSION: #{Puma::MiniSSL::OPENSSL_LIBRARY_VERSION}",
" OPENSSL_VERSION: #{Puma::MiniSSL::OPENSSL_VERSION}", ""
rescue
true
else
@ -102,18 +112,18 @@ class TestPumaServerSSL < Minitest::Test
end
def test_ssl_v3_rejection
@http.ssl_version='SSLv3'
@http.ssl_version= :SSLv3
assert_raises(OpenSSL::SSL::SSLError) do
@http.start do
Net::HTTP::Get.new '/'
end
end
unless Puma.jruby?
assert_match(/wrong version number|no protocols available/, @events.error.message) if @events.error
assert_match(/wrong version number|no protocols available|version too low/, @events.error.message) if @events.error
end
end
end
end unless DISABLE_SSL
# client-side TLS authentication tests
class TestPumaServerSSLClient < Minitest::Test
@ -137,14 +147,14 @@ class TestPumaServerSSLClient < Minitest::Test
events = SSLEventsHelper.new STDOUT, STDERR
server = Puma::Server.new app, events
ssl_listener = server.add_ssl_listener host, port, ctx
server.add_ssl_listener host, port, ctx
server.run
http = Net::HTTP.new host, port
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
blk.call(http)
yield http
client_error = false
begin
@ -152,7 +162,7 @@ class TestPumaServerSSLClient < Minitest::Test
req = Net::HTTP::Get.new "/", {}
http.request(req)
end
rescue OpenSSL::SSL::SSLError
rescue OpenSSL::SSL::SSLError, EOFError
client_error = true
end
@ -165,7 +175,7 @@ class TestPumaServerSSLClient < Minitest::Test
assert_equal host, events.addr if error
assert_equal subject, events.cert.subject.to_s if subject
end
ensure
server.stop(true)
end
@ -211,4 +221,4 @@ class TestPumaServerSSLClient < Minitest::Test
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
end
end
end
end unless DISABLE_SSL

View file

@ -18,12 +18,10 @@ class TestPumaControlCli < Minitest::Test
end
def find_open_port
begin
server = TCPServer.new("127.0.0.1", 0)
server.addr[1]
ensure
server.close
end
server = TCPServer.new("127.0.0.1", 0)
server.addr[1]
ensure
server.close
end
def test_config_file
@ -32,8 +30,6 @@ class TestPumaControlCli < Minitest::Test
end
def test_control_url
skip if Puma.jruby? || Puma.windows?
host = "127.0.0.1"
port = find_open_port
url = "tcp://#{host}:#{port}/"

View file

@ -2,7 +2,7 @@ require_relative "helper"
require "rack/handler/puma"
class TestPumaUnixSocket < Minitest::Test
class TestHandlerGetStrSym < Minitest::Test
def test_handler
handler = Rack::Handler.get(:puma)
assert_equal Rack::Handler::Puma, handler
@ -46,11 +46,11 @@ class TestPathHandler < Minitest::Test
thread.join if thread
end
def test_handler_boots
skip_on_appveyor
in_handler(app) do |launcher|
hit(["http://0.0.0.0:#{ launcher.connected_port }/test"])
host = windows? ? "127.0.1.1" : "0.0.0.0"
opts = { Host: host }
in_handler(app, opts) do |launcher|
hit(["http://#{host}:#{ launcher.connected_port }/test"])
assert_equal("/test", @input["PATH_INFO"])
end
end
@ -123,6 +123,44 @@ class TestUserSuppliedOptionsIsEmpty < Minitest::Test
end
end
end
def test_default_host_when_using_config_file
user_port = 5001
file_port = 6001
Dir.mktmpdir do |d|
Dir.chdir(d) do
FileUtils.mkdir("config")
File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" }
@options[:Host] = "localhost"
@options[:Port] = user_port
conf = Rack::Handler::Puma.config(->{}, @options)
conf.load
assert_equal ["tcp://localhost:#{file_port}"], conf.options[:binds]
end
end
end
def test_default_host_when_using_config_file_with_explicit_host
user_port = 5001
file_port = 6001
Dir.mktmpdir do |d|
Dir.chdir(d) do
FileUtils.mkdir("config")
File.open("config/puma.rb", "w") { |f| f << "port #{file_port}, '1.2.3.4'" }
@options[:Host] = "localhost"
@options[:Port] = user_port
conf = Rack::Handler::Puma.config(->{}, @options)
conf.load
assert_equal ["tcp://1.2.3.4:#{file_port}"], conf.options[:binds]
end
end
end
end
class TestUserSuppliedOptionsIsNotPresent < Minitest::Test

View file

@ -1,35 +1,33 @@
require_relative "helper"
# UNIX sockets are not recommended on JRuby
# (or Windows)
unless Puma.jruby? || Puma.windows?
class TestPumaUnixSocket < Minitest::Test
class TestPumaUnixSocket < Minitest::Test
App = lambda { |env| [200, {}, ["Works"]] }
App = lambda { |env| [200, {}, ["Works"]] }
Path = "test/puma.sock"
Path = "test/puma.sock"
def setup
@server = Puma::Server.new App
@server.add_unix_listener Path
@server.run
end
def setup
# UNIX sockets are not recommended on JRuby or Windows
skip_on :jruby, :windows, suffix: " - UNIX sockets are not recommended"
@server = Puma::Server.new App
@server.add_unix_listener Path
@server.run
end
def teardown
@server.stop(true)
File.unlink Path if File.exist? Path
end
def teardown
@server.stop(true) if @server
File.unlink Path if File.exist? Path
end
def test_server
sock = UNIXSocket.new Path
def test_server
sock = UNIXSocket.new Path
sock << "GET / HTTP/1.0\r\nHost: blah.com\r\n\r\n"
sock << "GET / HTTP/1.0\r\nHost: blah.com\r\n\r\n"
expected = "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nWorks"
expected = "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nWorks"
assert_equal expected, sock.read(expected.size)
assert_equal expected, sock.read(expected.size)
sock.close
end
sock.close
end
end

View file

@ -47,11 +47,11 @@ do_start_one() {
PIDFILE=$1/tmp/puma/pid
if [ -e $PIDFILE ]; then
PID=`cat $PIDFILE`
# If the puma isn't running, run it, otherwise restart it.
# If the puma is running, restart it, otherwise run it.
if ps -p $PID > /dev/null; then
do_start_one_do $1
else
do_restart_one $1
else
do_start_one_do $1
fi
else
do_start_one_do $1
@ -106,8 +106,6 @@ do_stop_one() {
if [ -e $PIDFILE ]; then
PID=`cat $PIDFILE`
if ps -p $PID > /dev/null; then
log_daemon_msg "---> Puma $1 isn't running."
else
log_daemon_msg "---> About to kill PID `cat $PIDFILE`"
if [ "$USE_LOCAL_BUNDLE" -eq 1 ]; then
cd $1 && bundle exec pumactl --state $STATEFILE stop
@ -116,6 +114,8 @@ do_stop_one() {
fi
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE $STATEFILE
else
log_daemon_msg "---> Puma $1 isn't running."
fi
else
log_daemon_msg "---> No puma here..."

View file

@ -0,0 +1,11 @@
# rake -f Rakefile_wintest -N -R norakelib
require "rake/testtask"
Rake::TestTask.new(:win_test) do |t|
t.libs << "test"
t.warning = false
t.options = '--verbose'
end
task :default => [:win_test]

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'rubygems'
require 'rubygems/package'
spec = Gem::Specification.load("./puma.gemspec")
spec.files.concat ['Rakefile_wintest', 'lib/puma/puma_http11.rb']
spec.files.concat Dir['lib/**/*.so']
spec.test_files = Dir['{examples,test}/**/*.*']
# below lines are required and not gem specific
spec.platform = ARGV[0]
spec.required_ruby_version = [">= #{ARGV[1]}", "< #{ARGV[2]}"]
spec.extensions = []
if spec.respond_to?(:metadata=)
spec.metadata.delete("msys2_mingw_dependencies")
spec.metadata['commit'] = ENV['commit_info']
end
Gem::Package.build(spec)

58
win_gem_test/puma.ps1 Normal file
View file

@ -0,0 +1,58 @@
# PowerShell script for building & testing SQLite3-Ruby fat binary gem
# Code by MSP-Greg, see https://github.com/MSP-Greg/av-gem-build-test
# load utility functions, pass 64 or 32
. $PSScriptRoot\shared\appveyor_setup.ps1 $args[0]
if ($LastExitCode) { exit }
# above is required code
#———————————————————————————————————————————————————————————————— above for all repos
Make-Const gem_name 'puma'
Make-Const repo_name 'puma'
Make-Const url_repo 'https://github.com/puma/puma.git'
#———————————————————————————————————————————————————————————————— lowest ruby version
Make-Const ruby_vers_low 22
# null = don't compile; false = compile, ignore test (allow failure);
# true = compile & test
Make-Const trunk $false ; Make-Const trunk_x64 $false
Make-Const trunk_JIT $null ; Make-Const trunk_x64_JIT $null
#———————————————————————————————————————————————————————————————— make info
Make-Const dest_so 'lib\puma'
Make-Const exts @(
@{ 'conf' = 'ext/puma_http11/extconf.rb' ; 'so' = 'puma_http11' }
)
Make-Const write_so_require $true
#———————————————————————————————————————————————————————————————— Pre-Compile
# runs before compiling starts on every ruby version
function Pre-Compile {
# load the correct OpenSSL version in the build system
Check-OpenSSL
Write-Host Compiling With $env:SSL_VERS
}
#———————————————————————————————————————————————————————————————— Run-Tests
function Run-Tests {
# call with comma separated list of gems to install or update
Update-Gems minitest, minitest-retry, rack, rake
$env:CI = 1
rake -f Rakefile_wintest -N -R norakelib | Set-Content -Path $log_name -PassThru -Encoding UTF8
# add info after test results
$(ruby -ropenssl -e "STDOUT.puts $/ + OpenSSL::OPENSSL_LIBRARY_VERSION") |
Add-Content -Path $log_name -PassThru -Encoding UTF8
minitest # collects test results
}
#———————————————————————————————————————————————————————————————— below for all repos
# below is required code
Make-Const dir_gem $(Convert-Path $PSScriptRoot\..)
Make-Const dir_ps $PSScriptRoot
Push-Location $PSScriptRoot
.\shared\make.ps1
.\shared\test.ps1
Pop-Location
exit $ttl_errors_fails + $exit_code