mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Merge branch 'fix_up_config_defaults' of github.com:jalevin/puma into fix_up_config_defaults
This commit is contained in:
commit
b257dfe422
78 changed files with 1034 additions and 1499 deletions
102
.github/workflows/puma.yml
vendored
Normal file
102
.github/workflows/puma.yml
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: >-
|
||||
${{ matrix.os }} ${{ matrix.ruby }}
|
||||
env:
|
||||
CI: true
|
||||
TESTOPTS: -v
|
||||
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
if: |
|
||||
!(contains(github.event.pull_request.title, '[ci skip]')
|
||||
|| contains(github.event.head_commit.message, '[ci skip]'))
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu, macos, windows ]
|
||||
ruby: [ 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, head, jruby, truffleruby-head ]
|
||||
include:
|
||||
- { os: windows , ruby: mingw }
|
||||
exclude:
|
||||
- { os: windows , ruby: head }
|
||||
- { os: windows , ruby: jruby }
|
||||
- { os: windows , ruby: truffleruby-head }
|
||||
|
||||
steps:
|
||||
- name: repo checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: load ruby, ragel
|
||||
uses: MSP-Greg/setup-ruby-pkgs@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
bundler: 1
|
||||
apt-get: ragel
|
||||
brew: ragel
|
||||
mingw: _upgrade_ openssl ragel
|
||||
|
||||
- name: bundle install
|
||||
shell: pwsh
|
||||
run: |
|
||||
# update RubyGems in Ruby 2.2, bundle install
|
||||
if ('${{ matrix.ruby }}' -lt '2.3') {
|
||||
gem update --system 2.7.10 --no-document
|
||||
}
|
||||
bundle install --jobs 4 --retry 3 --path=.bundle/puma
|
||||
|
||||
- name: compile
|
||||
run: bundle exec rake compile
|
||||
|
||||
- name: rubocop
|
||||
if: startsWith(matrix.ruby, '2.')
|
||||
run: bundle exec rake rubocop
|
||||
|
||||
- name: test
|
||||
timeout-minutes: 10
|
||||
run: bundle exec rake test:all
|
||||
|
||||
allowedFailures:
|
||||
name: >-
|
||||
optional: ${{ matrix.os }} ${{ matrix.ruby }}
|
||||
env:
|
||||
CI: true
|
||||
TESTOPTS: -v
|
||||
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
if: |
|
||||
!(contains(github.event.pull_request.title, '[ci skip]')
|
||||
|| contains(github.event.head_commit.message, '[ci skip]'))
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { os: ubuntu, ruby: jruby-head }
|
||||
|
||||
steps:
|
||||
- name: repo checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: load ruby, ragel
|
||||
uses: MSP-Greg/setup-ruby-pkgs@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
apt-get: ragel
|
||||
brew: ragel
|
||||
|
||||
- name: bundle install
|
||||
run: |
|
||||
bundle install --jobs 4 --retry 3 --path=.bundle/puma
|
||||
|
||||
- name: compile
|
||||
continue-on-error: true
|
||||
run: bundle exec rake compile
|
||||
|
||||
- name: test
|
||||
timeout-minutes: 10
|
||||
continue-on-error: true
|
||||
if: success()
|
||||
run: bundle exec rake
|
106
.github/workflows/ruby.yml
vendored
106
.github/workflows/ruby.yml
vendored
|
@ -1,106 +0,0 @@
|
|||
name: Puma
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: >-
|
||||
${{ matrix.os }}, ${{ matrix.ruby }}
|
||||
env:
|
||||
CI: true
|
||||
TESTOPTS: -v
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-18.04, macos ]
|
||||
ruby: [ 2.3, 2.4, 2.5, 2.6, 2.7, ruby-head ]
|
||||
|
||||
steps:
|
||||
- name: repo checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: load ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
|
||||
- name: ubuntu & macos - install ragel
|
||||
if: startsWith(matrix.os, 'ubuntu') || startsWith(matrix.os, 'macos')
|
||||
run: |
|
||||
if [ "${{ matrix.os }}" == "macos" ]; then
|
||||
brew install ragel
|
||||
else
|
||||
sudo apt-get -qy install ragel
|
||||
fi
|
||||
|
||||
- name: bundle install
|
||||
run: bundle install --jobs 4 --retry 3 --path=.bundle/puma
|
||||
- name: compile
|
||||
run: bundle exec rake compile
|
||||
|
||||
# two test steps due to pre-installed Bundler not working with
|
||||
# frozen strings before 2.0
|
||||
|
||||
- name: test with frozen string
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
RUBYOPT: --enable-frozen-string-literal
|
||||
if: matrix.ruby >= '2.6'
|
||||
run: bundle exec rake
|
||||
|
||||
- name: test without frozen string
|
||||
timeout-minutes: 10
|
||||
if: matrix.ruby < '2.6'
|
||||
run: bundle exec rake
|
||||
|
||||
win32:
|
||||
name: >-
|
||||
${{ matrix.os }}, ${{ matrix.ruby }}
|
||||
env:
|
||||
CI: true
|
||||
TESTOPTS: -v
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ windows-latest ]
|
||||
ruby: [ 2.3, 2.4, 2.5, 2.6, 2.7, ruby-head ]
|
||||
|
||||
steps:
|
||||
- name: repo checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: windows - update MSYS2, openssl, ragel
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
uses: MSP-Greg/actions-ruby@v1
|
||||
with:
|
||||
base: update
|
||||
mingw: openssl ragel
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
|
||||
- name: bundle install
|
||||
run: bundle install --jobs 4 --retry 3 --path=.bundle/puma
|
||||
|
||||
- name: compile
|
||||
if: matrix.ruby >= '2.4'
|
||||
run: bundle exec rake compile
|
||||
|
||||
# Ruby 2.3 uses a OpenSSL package that is not MSYS2
|
||||
- name: compile
|
||||
if: matrix.ruby == '2.3'
|
||||
run: bundle exec rake compile -- --with-openssl-dir=C:/openssl-win
|
||||
|
||||
- name: test with frozen string
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
RUBYOPT: --enable-frozen-string-literal
|
||||
if: matrix.ruby >= '2.6'
|
||||
run: bundle exec rake
|
||||
|
||||
- name: test without frozen string
|
||||
timeout-minutes: 10
|
||||
if: matrix.ruby < '2.6'
|
||||
run: bundle exec rake
|
23
.rubocop.yml
23
.rubocop.yml
|
@ -6,6 +6,8 @@ AllCops:
|
|||
Exclude:
|
||||
- 'tmp/**/*'
|
||||
- 'vendor/**/*'
|
||||
- 'examples/**/*'
|
||||
- 'pkg/**/*'
|
||||
- 'Rakefile'
|
||||
|
||||
Layout/SpaceAfterColon:
|
||||
|
@ -53,3 +55,24 @@ Style/TrailingCommaInArguments:
|
|||
|
||||
Performance:
|
||||
Enabled: true
|
||||
|
||||
Metrics/ParameterLists:
|
||||
Max: 7
|
||||
|
||||
Performance/RedundantMatch:
|
||||
Enabled: true
|
||||
|
||||
Performance/RedundantBlockCall:
|
||||
Enabled: true
|
||||
|
||||
Performance/StringReplacement:
|
||||
Enabled: true
|
||||
|
||||
Layout/AccessModifierIndentation:
|
||||
EnforcedStyle: indent
|
||||
|
||||
Style/WhileUntilModifier:
|
||||
Enabled: true
|
||||
|
||||
Style/TernaryParentheses:
|
||||
Enabled: true
|
||||
|
|
|
@ -39,19 +39,7 @@ Layout/EmptyLinesAroundModuleBody:
|
|||
# 5 offenses
|
||||
Layout/IndentationWidth:
|
||||
Enabled: true
|
||||
|
||||
# 3 offenses
|
||||
Layout/AccessModifierIndentation:
|
||||
EnforcedStyle: indent
|
||||
|
||||
# 2 offenses
|
||||
Style/WhileUntilModifier:
|
||||
Enabled: true
|
||||
|
||||
# 1 offense
|
||||
Style/TernaryParentheses:
|
||||
Enabled: true
|
||||
|
||||
|
||||
# >200 offenses for 80
|
||||
# 58 offenses for 100
|
||||
# 18 offenses for 120
|
||||
|
@ -64,19 +52,3 @@ Metrics/LineLength:
|
|||
- https
|
||||
IgnoreCopDirectives: false
|
||||
IgnoredPatterns: []
|
||||
|
||||
# 1 offense
|
||||
Metrics/ParameterLists:
|
||||
Max: 5
|
||||
|
||||
# 1 offense
|
||||
Performance/RedundantMatch:
|
||||
Enabled: true
|
||||
|
||||
# 1 offense
|
||||
Performance/RedundantBlockCall:
|
||||
Enabled: true
|
||||
|
||||
# 1 offense
|
||||
Performance/StringReplacement:
|
||||
Enabled: true
|
54
.travis.yml
54
.travis.yml
|
@ -1,54 +0,0 @@
|
|||
dist: bionic
|
||||
language: ruby
|
||||
cache: bundler
|
||||
|
||||
before_install:
|
||||
# rubygems 2.7.8 and greater include bundler, leave 2.6.0 untouched
|
||||
- |
|
||||
r_eng="$(ruby -e 'STDOUT.write RUBY_ENGINE')";
|
||||
rv="$(ruby -e 'STDOUT.write RUBY_VERSION')";
|
||||
if [ "$r_eng" == "ruby" ]; then
|
||||
if [ "$rv" \< "2.3" ]; then gem update --system 2.7.10 --no-document
|
||||
elif [ "$rv" \< "2.6" ]; then gem update --system --no-document --conservative
|
||||
fi
|
||||
fi
|
||||
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||
brew update
|
||||
brew install ragel
|
||||
fi
|
||||
- ruby -v && gem --version && bundle version
|
||||
|
||||
before_script:
|
||||
- if [ "$jit" == "yes" ]; then export RUBYOPT=--jit ; fi ; echo RUBYOPT is $RUBYOPT
|
||||
- bundle exec rake compile
|
||||
|
||||
script:
|
||||
- bundle exec rake
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- rvm: 2.2.10
|
||||
dist: trusty
|
||||
env:
|
||||
- OS="Trusty 14.04 OpenSSL 1.0.1"
|
||||
- RUBYOPT=""
|
||||
- rvm: jruby-9.2.10.0
|
||||
env:
|
||||
- JRUBY_OPTS="--debug"
|
||||
- JAVA_OPTS="--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens java.base/java.util.zip=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.security.cert=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED"
|
||||
- rvm: jruby-head
|
||||
env:
|
||||
- JRUBY_OPTS="--debug"
|
||||
- JAVA_OPTS="--add-opens java.base/sun.nio.ch=org.jruby.dist --add-opens java.base/java.io=org.jruby.dist --add-opens java.base/java.util.zip=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.security.cert=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED"
|
||||
- rvm: truffleruby
|
||||
|
||||
allow_failures:
|
||||
- rvm: jruby-9.2.10.0
|
||||
- rvm: jruby-head
|
||||
- rvm: truffleruby
|
||||
|
||||
env:
|
||||
global:
|
||||
- TESTOPTS="-v"
|
||||
- RUBYOPT="--enable-frozen-string-literal"
|
|
@ -8,8 +8,8 @@ There are lots of ways to contribute to puma. Some examples include:
|
|||
|
||||
* creating a [bug report] or [feature request]
|
||||
* verifying [existing bug reports] and adding [reproduction steps]
|
||||
* reviewing [pull requests] and testing the changes on your own machine
|
||||
* writing or editing documentation
|
||||
* reviewing [pull requests] and testing the changes locally, on your own machine
|
||||
* writing or editing [documentation]
|
||||
* improving test coverage
|
||||
* fixing a [reproducing bug] or adding a new feature
|
||||
|
||||
|
@ -17,14 +17,17 @@ There are lots of ways to contribute to puma. Some examples include:
|
|||
[feature request]: https://github.com/puma/puma/issues/new?template=feature_request.md
|
||||
[existing bug reports]: https://github.com/puma/puma/issues?q=is%3Aopen+is%3Aissue+label%3Aneeds-repro
|
||||
[pull requests]: https://github.com/puma/puma/pulls
|
||||
[documentation]: https://github.com/puma/puma/tree/master/docs
|
||||
[reproduction steps]: https://github.com/puma/puma/blob/CONTRIBUTING.md#reproduction-steps
|
||||
[reproducing bug]: https://github.com/puma/puma/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3Abug
|
||||
|
||||
Newbies welcome! We would be happy to help you make your first contribution to a F/OSS project.
|
||||
|
||||
## Setup
|
||||
|
||||
Clone down the Puma repository.
|
||||
|
||||
You will need to install [ragel] to generate puma's extension code.
|
||||
You will need to install [ragel] (use Ragel version 7.0.0.9) to generate puma's extension code.
|
||||
|
||||
macOS:
|
||||
|
||||
|
@ -68,7 +71,7 @@ bundle exec rake test:all
|
|||
To run a single test file:
|
||||
|
||||
```sh
|
||||
ruby -Ilib test/test_binder.rb
|
||||
bundle exec ruby test/test_binder.rb
|
||||
```
|
||||
|
||||
Or use [`m`](https://github.com/qrush/m):
|
||||
|
@ -112,7 +115,7 @@ bundle exec puma -C <path/to/config.rb> <path/to/rackup.ru>
|
|||
```
|
||||
|
||||
As an example, using one of the test rack apps:
|
||||
[`test/rackup/hello.ru`][rackup], and one of the test config files:
|
||||
[`test/rackup/hello.ru`][rackup file], and one of the test config files:
|
||||
[`test/config/settings.rb`][config], you would run the test app with:
|
||||
|
||||
```sh
|
||||
|
@ -144,6 +147,7 @@ check](https://github.com/puma/puma/pull/1991).
|
|||
|
||||
Puma can be a bit intimidating for your first contribution because there's a lot of concepts here that you've probably never had to think about before - Rack, sockets, forking, threads etc. Here are some helpful links for learning more about things related to Puma:
|
||||
|
||||
* [Puma's Architecture docs](https://github.com/puma/puma/blob/master/docs/architecture.md)
|
||||
* [The Rack specification](https://www.rubydoc.info/github/rack/rack/file/SPEC)
|
||||
* The Ruby docs for IO.pipe, TCPServer/Socket.
|
||||
* [nio4r documentation](https://github.com/socketry/nio4r/wiki/Getting-Started)
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -6,7 +6,7 @@ gem "rdoc"
|
|||
gem "rake-compiler"
|
||||
|
||||
gem "nio4r", "~> 2.0"
|
||||
gem "rack", "< 3.0"
|
||||
gem "rack", "~> 1.6"
|
||||
gem "minitest", "~> 5.11"
|
||||
gem "minitest-retry"
|
||||
gem "minitest-proveit"
|
||||
|
|
31
History.md
31
History.md
|
@ -1,41 +1,54 @@
|
|||
## Master
|
||||
|
||||
* Features
|
||||
* Improve SSL connection closing in Puma::ControlCLI (#2211)
|
||||
* Add pumactl `thread-backtraces` command to print thread backtraces (#2053)
|
||||
* Configuration: `environment` is read from `RAILS_ENV`, if `RACK_ENV` can't be found (#2022)
|
||||
* Do not set user_config to quiet by default to allow for file config (#2074)
|
||||
* `Puma.stats` now returns a Hash instead of a JSON string (#2086)
|
||||
* `GC.compact` is called before fork if available (#2093)
|
||||
* Add `requests_count` to workers stats. (#2106)
|
||||
* Changed #connected_port to #connected_ports (#2076)
|
||||
* Increases maximum URI path length from 2048 to 8196 bytes (#2167)
|
||||
* Force shutdown responses can be overridden by using the `lowlevel_error_handler` config (#2203)
|
||||
|
||||
* Deprecations, Removals and Breaking API Changes
|
||||
* `Puma.stats` now returns a Hash instead of a JSON string (#2086)
|
||||
* `--control` has been removed. Use `--control-url` (#1487)
|
||||
* `worker_directory` has been removed. Use `directory`
|
||||
* min_threads now set by environment variables PUMA_MIN_THREADS and MIN_THREADS
|
||||
* max_threads now set by environment variables PUMA_MAX_THREADS and MAX_THREADS
|
||||
* max_threads default to 5 in MRI or 16 for all other interpretters
|
||||
* preload by default if workers > 1 and interpretter supports workers
|
||||
|
||||
* `tcp_mode` has been removed without replacement. (#2169)
|
||||
* Daemonization has been removed without replacement. (#2170)
|
||||
* Changed #connected_port to #connected_ports (#2076)
|
||||
|
||||
* Bugfixes
|
||||
* Windows update extconf.rb for use with ssp and varied Ruby/MSYS2 combinations (#2069)
|
||||
* Ensure control server Unix socket is closed on shutdown (#2112)
|
||||
* Preserve `BUNDLE_GEMFILE` env var when using `prune_bundler` (#1893)
|
||||
* Send 408 request timeout even when queue requests is disabled (#2119)
|
||||
* Rescue IO::WaitReadable instead of EAGAIN for blocking read (#2121)
|
||||
* Ensure `BUNDLE_GEMFILE` is unspecified in workers if unspecified in master when using `prune_bundler` (#2154)
|
||||
* Rescue and log exceptions in hooks defined by users (on_worker_boot, after_worker_fork etc) (#1551)
|
||||
* Read directly from the socket in #read_and_drop to avoid raising further SSL errors (#2198)
|
||||
* Set `Connection: closed` header when queue requests is disabled (#2216)
|
||||
* Pass queued requests to thread pool on server shutdown (#2122)
|
||||
|
||||
* Refactor
|
||||
* Remove unused loader argument from Plugin initializer (#2095)
|
||||
* Simplify `Configuration.random_token` and remove insecure fallback (#2102)
|
||||
* Simplify `Runner#start_control` URL parsing (#2111)
|
||||
* Removed the IOBuffer extension and replaced with Ruby (#1980)
|
||||
|
||||
* Update `Rack::Handler::Puma.run` to use `**options` (#2189)
|
||||
|
||||
## 4.3.3 and 3.12.4 / 2020-02-28
|
||||
* Bugfixes
|
||||
* Fix: Fixes a problem where we weren't splitting headers correctly on newlines (#2132)
|
||||
* Security
|
||||
* Fix: Prevent HTTP Response splitting via CR in early hints.
|
||||
|
||||
## 4.3.2 and 3.12.3 / 2020-02-27
|
||||
* Bugfixes
|
||||
* Fix: Fixes a problem where we weren't splitting headers correctly on newlines (#2132)
|
||||
* Security
|
||||
* Fix: Prevent HTTP Response splitting via CR in early hints. CVE-2020-5249.
|
||||
|
||||
## 4.3.2 and 3.12.3 / 2020-02-27 (YANKED)
|
||||
|
||||
* Security
|
||||
* Fix: Prevent HTTP Response splitting via CR/LF in header values. CVE-2020-5247.
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
|
||||
# Puma: A Ruby Web Server Built For Concurrency
|
||||
|
||||
[](https://gitter.im/puma/puma?utm\_source=badge&utm\_medium=badge&utm\_campaign=pr-badge)
|
||||
[](https://github.com/puma/puma/actions)
|
||||
[](https://travis-ci.org/puma/puma)
|
||||
|
||||
[](https://codeclimate.com/github/puma/puma)
|
||||
[](https://dependabot.com/compatibility-score.html?dependency-name=puma&package-manager=bundler&version-scheme=semver)
|
||||
[]( http://stackoverflow.com/questions/tagged/puma )
|
||||
|
||||
Puma is a **simple, fast, multi-threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications**.
|
||||
|
||||
|
@ -270,7 +269,7 @@ It is common to use process monitors with Puma. Modern process monitors like sys
|
|||
provide continuous monitoring and restarts for increased
|
||||
reliability in production environments:
|
||||
|
||||
* [tools/jungle](https://github.com/puma/puma/tree/master/tools/jungle) for sysvinit (init.d) and upstart
|
||||
* [docs/jungle](https://github.com/puma/puma/tree/master/docs/jungle) for rc.d and upstart
|
||||
* [docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md)
|
||||
|
||||
## Community Extensions
|
||||
|
|
8
benchmarks/wrk/jruby_ssl_realistic_response.sh
Executable file
8
benchmarks/wrk/jruby_ssl_realistic_response.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
bundle exec ruby bin/puma \
|
||||
-t 4 -b "ssl://localhost:9292?keystore=examples/puma/keystore.jks&keystore-pass=blahblah&verify_mode=none" \
|
||||
test/rackup/realistic_response.ru &
|
||||
PID1=$!
|
||||
sleep 5
|
||||
wrk -c 4 -d 30 --latency https://localhost:9292
|
||||
|
||||
kill $PID1
|
8
benchmarks/wrk/ssl_realistic_response.sh
Executable file
8
benchmarks/wrk/ssl_realistic_response.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
bundle exec ruby bin/puma \
|
||||
-t 4 -b "ssl://localhost:9292?key=examples%2Fpuma%2Fpuma_keypair.pem&cert=examples%2Fpuma%2Fcert_puma.pem&verify_mode=none" \
|
||||
test/rackup/realistic_response.ru &
|
||||
PID1=$!
|
||||
sleep 5
|
||||
wrk -c 4 -d 30 --latency https://localhost:9292
|
||||
|
||||
kill $PID1
|
|
@ -74,7 +74,9 @@ thread to become available.
|
|||
* haproxy: `%Th` (TLS handshake time) and `%Ti` (idle time before request) can
|
||||
can also be added as headers.
|
||||
|
||||
## Daemonizing
|
||||
## Should I daemonize?
|
||||
|
||||
Daemonization was removed in Puma 5.0. For alternatives, continue reading.
|
||||
|
||||
I prefer to not daemonize my servers and use something like `runit` or `upstart` to
|
||||
monitor them as child processes. This gives them fast response to crashes and
|
||||
|
|
13
docs/jungle/README.md
Normal file
13
docs/jungle/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Puma as a service
|
||||
|
||||
## Upstart
|
||||
|
||||
See `/docs/jungle/upstart` for Ubuntu's upstart scripts.
|
||||
|
||||
## Systemd
|
||||
|
||||
See [/docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md).
|
||||
|
||||
## rc.d
|
||||
|
||||
See `/docs/jungle/rc.d` for FreeBSD's rc.d scripts
|
|
@ -13,9 +13,7 @@ desired, using an application or instance specific name.
|
|||
|
||||
Note that this uses the systemd preferred "simple" type where the
|
||||
start command remains running in the foreground (does not fork and
|
||||
exit). See also, the
|
||||
[Alternative Forking Configuration](#alternative-forking-configuration)
|
||||
below.
|
||||
exit).
|
||||
|
||||
~~~~ ini
|
||||
[Unit]
|
||||
|
@ -209,66 +207,6 @@ Apr 07 08:40:19 hx puma[28320]: * Activated ssl://0.0.0.0:9234?key=key.pem&cert=
|
|||
Apr 07 08:40:19 hx puma[28320]: Use Ctrl-C to stop
|
||||
~~~~
|
||||
|
||||
## Alternative Forking Configuration
|
||||
|
||||
Other systems/tools might expect or need puma to be run as a
|
||||
"traditional" forking server, for example so that the `pumactl`
|
||||
command can be used directly and outside of systemd for
|
||||
stop/start/restart. This use case is incompatible with systemd socket
|
||||
activation, so it should not be configured. Below is an alternative
|
||||
puma.service config sample, using `Type=forking` and the `--daemon`
|
||||
flag in `ExecStart`. Here systemd is playing a role more equivalent to
|
||||
SysV init.d, where it is responsible for starting Puma on boot
|
||||
(multi-user.target) and stopping it on shutdown, but is not performing
|
||||
continuous restarts. Therefore running Puma in cluster mode, where the
|
||||
master can restart workers, is highly recommended. See the systemd
|
||||
[Restart] directive for details.
|
||||
|
||||
~~~~ ini
|
||||
[Unit]
|
||||
Description=Puma HTTP Forking Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
# Background process configuration (use with --daemon in ExecStart)
|
||||
Type=forking
|
||||
|
||||
# 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 command to start Puma
|
||||
# (replace "<WD>" below)
|
||||
ExecStart=bundle exec puma -C <WD>/shared/puma.rb --daemon
|
||||
|
||||
# The command to stop Puma
|
||||
# (replace "<WD>" below)
|
||||
ExecStop=bundle exec pumactl -S <WD>/shared/tmp/pids/puma.state stop
|
||||
|
||||
# Path to PID file so that systemd knows which is the master process
|
||||
PIDFile=<WD>/shared/tmp/pids/puma.pid
|
||||
|
||||
# Should systemd restart puma?
|
||||
# Use "no" (the default) to ensure no interference when using
|
||||
# stop/start/restart via `pumactl`. The "on-failure" setting might
|
||||
# work better for this purpose, but you must test it.
|
||||
# Use "always" if only `systemctl` is used for start/stop/restart, and
|
||||
# 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
|
||||
~~~~
|
||||
|
||||
### capistrano3-puma
|
||||
|
||||
By default,
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
# TCP mode
|
||||
|
||||
Puma also could be used as a TCP server to process incoming TCP
|
||||
connections.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
TCP mode can be enabled with CLI option `--tcp-mode`:
|
||||
|
||||
```
|
||||
$ puma --tcp-mode
|
||||
```
|
||||
|
||||
Default ip and port to listen to are `0.0.0.0` and `9292`. You can configure
|
||||
them with `--port` and `--bind` options:
|
||||
|
||||
```
|
||||
$ puma --tcp-mode --bind tcp://127.0.0.1:9293
|
||||
$ puma --tcp-mode --port 9293
|
||||
```
|
||||
|
||||
TCP mode could be set with a configuration file as well with `tcp_mode`
|
||||
and `tcp_mode!` methods:
|
||||
|
||||
```
|
||||
# config/puma.rb
|
||||
tcp_mode
|
||||
```
|
||||
|
||||
When Puma starts in the TCP mode it prints the corresponding message:
|
||||
|
||||
```
|
||||
puma --tcp-mode
|
||||
Puma starting in single mode...
|
||||
...
|
||||
* Mode: Lopez Express (tcp)
|
||||
```
|
||||
|
||||
|
||||
## How to declare an application
|
||||
|
||||
An application to process TCP connections should be declared as a
|
||||
callable object which accepts `env` and `socket` arguments.
|
||||
|
||||
`env` argument is a Hash with following structure:
|
||||
|
||||
```ruby
|
||||
{ "thread" => {}, "REMOTE_ADDR" => "127.0.0.1:51133", "log" => "#<Proc:0x000..." }
|
||||
```
|
||||
|
||||
It consists of:
|
||||
* `thread` - a Hash for each thread in the thread pool that could be
|
||||
used to store information between requests
|
||||
* `REMOTE_ADDR` - a client ip address
|
||||
* `log` - a proc object to write something down
|
||||
|
||||
`log` object could be used this way:
|
||||
|
||||
```ruby
|
||||
env['log'].call('message to log')
|
||||
#> 19/Oct/2019 20:28:53 - 127.0.0.1:51266 - message to log
|
||||
```
|
||||
|
||||
|
||||
## Example of an application
|
||||
|
||||
Let's look at an example of a simple application which just echoes
|
||||
incoming string:
|
||||
|
||||
```ruby
|
||||
# config/puma.rb
|
||||
app do |env, socket|
|
||||
s = socket.gets
|
||||
socket.puts "Echo #{s}"
|
||||
end
|
||||
```
|
||||
|
||||
We can easily access the TCP server with `telnet` command and receive an
|
||||
echo:
|
||||
|
||||
```shell
|
||||
telnet 0.0.0.0 9293
|
||||
Trying 0.0.0.0...
|
||||
Connected to 0.0.0.0.
|
||||
Escape character is '^]'.
|
||||
sssss
|
||||
Echo sssss
|
||||
^CConnection closed by foreign host.
|
||||
```
|
||||
|
||||
|
||||
## Socket management
|
||||
|
||||
After the application finishes, Puma closes the socket. In order to
|
||||
prevent this, the application should set `env['detach'] = true`.
|
|
@ -6,7 +6,6 @@ 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 {
|
||||
|
|
|
@ -2,7 +2,7 @@ require 'mkmf'
|
|||
|
||||
dir_config("puma_http11")
|
||||
|
||||
if $mingw
|
||||
if $mingw && RUBY_VERSION >= '2.4'
|
||||
append_cflags '-fstack-protector-strong -D_FORTIFY_SOURCE=2'
|
||||
append_ldflags '-fstack-protector-strong -l:libssp.a'
|
||||
have_library 'ssp'
|
||||
|
|
|
@ -30,8 +30,8 @@ public class Http11 extends RubyObject {
|
|||
public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the 12288 allowed length.";
|
||||
public final static int MAX_FRAGMENT_LENGTH = 1024;
|
||||
public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 1024 allowed length.";
|
||||
public final static int MAX_REQUEST_PATH_LENGTH = 2048;
|
||||
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 2048 allowed length.";
|
||||
public final static int MAX_REQUEST_PATH_LENGTH = 8192;
|
||||
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 8192 allowed length.";
|
||||
public final static int MAX_QUERY_STRING_LENGTH = 1024 * 10;
|
||||
public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the 10240 allowed length.";
|
||||
public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
|
||||
|
@ -197,7 +197,7 @@ public class Http11 extends RubyObject {
|
|||
validateMaxLength(runtime, parser.nread,MAX_HEADER_LENGTH, MAX_HEADER_LENGTH_ERR);
|
||||
|
||||
if(hp.has_error()) {
|
||||
throw newHTTPParserError(runtime, "Invalid HTTP format, parsing fails.");
|
||||
throw newHTTPParserError(runtime, "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
|
||||
} else {
|
||||
return runtime.newFixnum(parser.nread);
|
||||
}
|
||||
|
|
|
@ -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, 2048);
|
||||
DEF_MAX_LENGTH(REQUEST_PATH, 8196);
|
||||
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
||||
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
|
||||
|
||||
|
@ -367,7 +367,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
|
|||
VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
|
||||
|
||||
if(puma_parser_has_error(http)) {
|
||||
rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
|
||||
rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
|
||||
} else {
|
||||
return INT2FIX(puma_parser_nread(http));
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ module Puma
|
|||
class Binder
|
||||
include Puma::Const
|
||||
|
||||
RACK_VERSION = [1,3].freeze
|
||||
RACK_VERSION = [1,6].freeze
|
||||
|
||||
def initialize(events)
|
||||
@events = events
|
||||
|
@ -43,7 +43,8 @@ module Puma
|
|||
@ios = []
|
||||
end
|
||||
|
||||
attr_reader :ios
|
||||
attr_reader :ios, :listeners, :unix_paths, :proto_env, :envs, :activated_sockets, :inherited_fds
|
||||
attr_writer :ios, :listeners
|
||||
|
||||
def env(sock)
|
||||
@envs.fetch(sock, @proto_env)
|
||||
|
@ -53,37 +54,36 @@ module Puma
|
|||
@ios.each { |i| i.close }
|
||||
end
|
||||
|
||||
def import_from_env
|
||||
remove = []
|
||||
def connected_ports
|
||||
ios.map { |io| io.addr[1] }.uniq
|
||||
end
|
||||
|
||||
ENV.each do |k,v|
|
||||
if k =~ /PUMA_INHERIT_\d+/
|
||||
fd, url = v.split(":", 2)
|
||||
@inherited_fds[url] = fd.to_i
|
||||
remove << k
|
||||
elsif k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
|
||||
v.to_i.times do |num|
|
||||
fd = num + 3
|
||||
sock = TCPServer.for_fd(fd)
|
||||
begin
|
||||
key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
|
||||
rescue ArgumentError
|
||||
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
||||
if addr =~ /\:/
|
||||
addr = "[#{addr}]"
|
||||
end
|
||||
key = [ :tcp, addr, port ]
|
||||
end
|
||||
@activated_sockets[key] = sock
|
||||
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
||||
end
|
||||
remove << k << 'LISTEN_PID'
|
||||
def create_inherited_fds(env_hash)
|
||||
env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
|
||||
fd, url = v.split(":", 2)
|
||||
@inherited_fds[url] = fd.to_i
|
||||
end.keys # pass keys back for removal
|
||||
end
|
||||
|
||||
# systemd socket activation.
|
||||
# LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
|
||||
# LISTEN_PID = PID of the service process, aka us
|
||||
# see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
|
||||
def create_activated_fds(env_hash)
|
||||
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
||||
env_hash['LISTEN_FDS'].to_i.times do |index|
|
||||
sock = TCPServer.for_fd(socket_activation_fd(index))
|
||||
key = begin # Try to parse as a path
|
||||
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
|
||||
rescue ArgumentError # Try to parse as a port/ip
|
||||
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
||||
addr = "[#{addr}]" if addr =~ /\:/
|
||||
[:tcp, addr, port]
|
||||
end
|
||||
@activated_sockets[key] = sock
|
||||
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
||||
end
|
||||
|
||||
remove.each do |k|
|
||||
ENV.delete k
|
||||
end
|
||||
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
||||
end
|
||||
|
||||
def parse(binds, logger, log_msg = 'Listening')
|
||||
|
@ -204,12 +204,6 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
def loopback_addresses
|
||||
Socket.ip_address_list.select do |addrinfo|
|
||||
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
||||
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
||||
end
|
||||
|
||||
# Tell the server to listen on host +host+, port +port+.
|
||||
# If +optimize_for_latency+ is true (the default) then clients connecting
|
||||
# will be optimized for latency over throughput.
|
||||
|
@ -237,10 +231,6 @@ module Puma
|
|||
tcp_server
|
||||
end
|
||||
|
||||
def connected_ports
|
||||
ios.map { |io| io.addr[1] }
|
||||
end
|
||||
|
||||
def inherit_tcp_listener(host, port, fd)
|
||||
if fd.kind_of? TCPServer
|
||||
s = fd
|
||||
|
@ -361,26 +351,37 @@ module Puma
|
|||
end
|
||||
|
||||
def close_listeners
|
||||
@listeners.each do |l, io|
|
||||
io.close
|
||||
listeners.each do |l, io|
|
||||
io.close unless io.closed? # Ruby 2.2 issue
|
||||
uri = URI.parse(l)
|
||||
next unless uri.scheme == 'unix'
|
||||
unix_path = "#{uri.host}#{uri.path}"
|
||||
File.unlink unix_path if @unix_paths.include? unix_path
|
||||
File.unlink unix_path if unix_paths.include? unix_path
|
||||
end
|
||||
end
|
||||
|
||||
def close_unix_paths
|
||||
@unix_paths.each { |up| File.unlink(up) if File.exist? up }
|
||||
end
|
||||
|
||||
def redirects_for_restart
|
||||
redirects = {:close_others => true}
|
||||
@listeners.each_with_index do |(l, io), i|
|
||||
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
||||
redirects[io.to_i] = io.to_i
|
||||
end
|
||||
redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
|
||||
redirects[:close_others] = true
|
||||
redirects
|
||||
end
|
||||
|
||||
def redirects_for_restart_env
|
||||
listeners.each_with_object({}).with_index do |(listen, memo), i|
|
||||
memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def loopback_addresses
|
||||
Socket.ip_address_list.select do |addrinfo|
|
||||
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
||||
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
||||
end
|
||||
|
||||
def socket_activation_fd(int)
|
||||
int + 3 # 3 is the magic number you add to follow the SA protocol
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -80,7 +80,7 @@ module Puma
|
|||
@launcher.run
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
def unsupported(str)
|
||||
@events.error(str)
|
||||
raise UnsupportedOption
|
||||
|
@ -117,11 +117,6 @@ module Puma
|
|||
@control_options[:auth_token] = arg
|
||||
end
|
||||
|
||||
o.on "-d", "--daemon", "Daemonize the server into the background" do
|
||||
user_config.daemonize
|
||||
user_config.quiet
|
||||
end
|
||||
|
||||
o.on "--debug", "Log lowlevel debugging information" do
|
||||
user_config.debug
|
||||
end
|
||||
|
@ -187,10 +182,6 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
|
||||
user_config.tcp_mode!
|
||||
end
|
||||
|
||||
o.on "--early-hints", "Enable early hints support" do
|
||||
user_config.early_hints
|
||||
end
|
||||
|
|
|
@ -246,7 +246,12 @@ module Puma
|
|||
def finish(timeout)
|
||||
return true if @ready
|
||||
until try_to_finish
|
||||
unless IO.select([@to_io], nil, nil, timeout)
|
||||
can_read = begin
|
||||
IO.select([@to_io], nil, nil, timeout)
|
||||
rescue ThreadPool::ForceShutdown
|
||||
nil
|
||||
end
|
||||
unless can_read
|
||||
write_error(408) if in_data_phase
|
||||
raise ConnectionError
|
||||
end
|
||||
|
@ -265,7 +270,7 @@ module Puma
|
|||
return @peerip if @peerip
|
||||
|
||||
if @remote_addr_header
|
||||
hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
|
||||
hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
|
||||
@peerip = hdr
|
||||
return hdr
|
||||
end
|
||||
|
@ -273,6 +278,18 @@ module Puma
|
|||
@peerip ||= @io.peeraddr.last
|
||||
end
|
||||
|
||||
# Returns true if the persistent connection can be closed immediately
|
||||
# without waiting for the configured idle/shutdown timeout.
|
||||
def can_close?
|
||||
# Allow connection to close if it's received at least one full request
|
||||
# and hasn't received any data for a future request.
|
||||
#
|
||||
# From RFC 2616 section 8.1.4:
|
||||
# Servers SHOULD always respond to at least one request per connection,
|
||||
# if at all possible.
|
||||
@requests_served > 0 && @parsed_bytes == 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_body
|
||||
|
|
|
@ -137,7 +137,7 @@ module Puma
|
|||
|
||||
diff.times do
|
||||
idx = next_worker_index
|
||||
@launcher.config.run_hooks :before_worker_fork, idx
|
||||
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
||||
|
||||
pid = fork { worker(idx, master) }
|
||||
if !pid
|
||||
|
@ -149,7 +149,7 @@ module Puma
|
|||
debug "Spawned worker: #{pid}"
|
||||
@workers << Worker.new(idx, pid, @phase, @options)
|
||||
|
||||
@launcher.config.run_hooks :after_worker_fork, idx
|
||||
@launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
|
||||
end
|
||||
|
||||
if diff > 0
|
||||
|
@ -268,7 +268,7 @@ module Puma
|
|||
|
||||
# Invoke any worker boot hooks so they can get
|
||||
# things in shape before booting the app.
|
||||
@launcher.config.run_hooks :before_worker_boot, index
|
||||
@launcher.config.run_hooks :before_worker_boot, index, @launcher.events
|
||||
|
||||
server = start_server
|
||||
|
||||
|
@ -310,7 +310,7 @@ module Puma
|
|||
|
||||
# Invoke any worker shutdown hooks so they can prevent the worker
|
||||
# exiting until any background operations are completed
|
||||
@launcher.config.run_hooks :before_worker_shutdown, index
|
||||
@launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
|
||||
ensure
|
||||
@worker_write << "t#{Process.pid}\n" rescue nil
|
||||
@worker_write.close
|
||||
|
@ -469,12 +469,7 @@ module Puma
|
|||
#
|
||||
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
||||
|
||||
if daemon?
|
||||
log "* Daemonizing..."
|
||||
Process.daemon(true)
|
||||
else
|
||||
log "Use Ctrl-C to stop"
|
||||
end
|
||||
log "Use Ctrl-C to stop"
|
||||
|
||||
redirect_io
|
||||
|
||||
|
@ -486,7 +481,7 @@ module Puma
|
|||
|
||||
@master_read, @worker_write = read, @wakeup
|
||||
|
||||
@launcher.config.run_hooks :before_fork, nil
|
||||
@launcher.config.run_hooks :before_fork, nil, @launcher.events
|
||||
GC.compact if GC.respond_to?(:compact)
|
||||
|
||||
spawn_workers
|
||||
|
|
|
@ -258,14 +258,6 @@ module Puma
|
|||
def app
|
||||
found = options[:app] || load_rackup
|
||||
|
||||
if @options[:mode] == :tcp
|
||||
require 'puma/tcp_logger'
|
||||
|
||||
logger = @options[:logger]
|
||||
quiet = !@options[:log_requests]
|
||||
return TCPLogger.new(logger, found, quiet)
|
||||
end
|
||||
|
||||
if @options[:log_requests]
|
||||
require 'puma/commonlogger'
|
||||
logger = @options[:logger]
|
||||
|
@ -288,8 +280,15 @@ module Puma
|
|||
@plugins.create name
|
||||
end
|
||||
|
||||
def run_hooks(key, arg)
|
||||
@options.all_of(key).each { |b| b.call arg }
|
||||
def run_hooks(key, arg, events)
|
||||
@options.all_of(key).each do |b|
|
||||
begin
|
||||
b.call arg
|
||||
rescue => e
|
||||
events.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
|
||||
events.debug e.backtrace.join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.temp_path
|
||||
|
|
|
@ -175,7 +175,6 @@ module Puma
|
|||
PORT_443 = "443".freeze
|
||||
LOCALHOST = "localhost".freeze
|
||||
LOCALHOST_IP = "127.0.0.1".freeze
|
||||
LOCALHOST_ADDR = "127.0.0.1:0".freeze
|
||||
|
||||
SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
|
||||
HTTP_11 = "HTTP/1.1".freeze
|
||||
|
|
|
@ -146,8 +146,9 @@ module Puma
|
|||
require 'openssl'
|
||||
OpenSSL::SSL::SSLSocket.new(
|
||||
TCPSocket.new(uri.host, uri.port),
|
||||
OpenSSL::SSL::SSLContext.new
|
||||
).tap(&:connect)
|
||||
OpenSSL::SSL::SSLContext.new)
|
||||
.tap { |ssl| ssl.sync_close = true } # default is false
|
||||
.tap(&:connect)
|
||||
when "tcp"
|
||||
TCPSocket.new uri.host, uri.port
|
||||
when "unix"
|
||||
|
@ -191,7 +192,13 @@ module Puma
|
|||
message response.last if PRINTABLE_COMMANDS.include?(@command)
|
||||
end
|
||||
ensure
|
||||
server.close if server && !server.closed?
|
||||
if server
|
||||
if uri.scheme == "ssl"
|
||||
server.sysclose
|
||||
else
|
||||
server.close unless server.closed?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def send_signal
|
||||
|
@ -263,7 +270,7 @@ module Puma
|
|||
exit 1
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
def start
|
||||
require 'puma/cli'
|
||||
|
||||
|
|
|
@ -210,20 +210,6 @@ module Puma
|
|||
@options[:clean_thread_locals] = which
|
||||
end
|
||||
|
||||
# Daemonize the server into the background. It's highly recommended to
|
||||
# use this in combination with +pidfile+ and +stdout_redirect+.
|
||||
#
|
||||
# The default is "false".
|
||||
#
|
||||
# @example
|
||||
# daemonize
|
||||
#
|
||||
# @example
|
||||
# daemonize false
|
||||
def daemonize(which=true)
|
||||
@options[:daemon] = which
|
||||
end
|
||||
|
||||
# When shutting down, drain the accept socket of pending
|
||||
# connections and process them. This loops over the accept
|
||||
# socket until there are no more read events and then stops
|
||||
|
@ -325,12 +311,6 @@ module Puma
|
|||
@options[:rackup] = path.to_s
|
||||
end
|
||||
|
||||
# Run Puma in TCP mode
|
||||
#
|
||||
def tcp_mode!
|
||||
@options[:mode] = :tcp
|
||||
end
|
||||
|
||||
def early_hints(answer=true)
|
||||
@options[:early_hints] = answer
|
||||
end
|
||||
|
@ -536,11 +516,6 @@ module Puma
|
|||
@options[:directory] = dir.to_s
|
||||
end
|
||||
|
||||
# Run the app as a raw TCP app instead of an HTTP rack app.
|
||||
def tcp_mode
|
||||
@options[:mode] = :tcp
|
||||
end
|
||||
|
||||
# Preload the application before starting the workers; this conflicts with
|
||||
# phased restart feature. This is off by default.
|
||||
#
|
||||
|
|
|
@ -22,63 +22,5 @@ module Puma
|
|||
execlp(cmd, *argv)
|
||||
raise SystemCallError.new(FFI.errno)
|
||||
end
|
||||
|
||||
PermKey = 'PUMA_DAEMON_PERM'
|
||||
RestartKey = 'PUMA_DAEMON_RESTART'
|
||||
|
||||
# Called to tell things "Your now always in daemon mode,
|
||||
# don't try to reenter it."
|
||||
#
|
||||
def self.perm_daemonize
|
||||
ENV[PermKey] = "1"
|
||||
end
|
||||
|
||||
def self.daemon?
|
||||
ENV.key?(PermKey) || ENV.key?(RestartKey)
|
||||
end
|
||||
|
||||
def self.daemon_init
|
||||
return true if ENV.key?(PermKey)
|
||||
|
||||
return false unless ENV.key? RestartKey
|
||||
|
||||
master = ENV[RestartKey]
|
||||
|
||||
# In case the master disappears early
|
||||
begin
|
||||
Process.kill "SIGUSR2", master.to_i
|
||||
rescue SystemCallError => e
|
||||
end
|
||||
|
||||
ENV[RestartKey] = ""
|
||||
|
||||
setsid
|
||||
|
||||
null = File.open "/dev/null", "w+"
|
||||
STDIN.reopen null
|
||||
STDOUT.reopen null
|
||||
STDERR.reopen null
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def self.daemon_start(dir, argv)
|
||||
ENV[RestartKey] = Process.pid.to_s
|
||||
|
||||
if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
|
||||
ENV['JRUBY_OPTS'] = k
|
||||
end
|
||||
|
||||
cmd = argv.first
|
||||
argv = ([:string] * argv.size).zip(argv).flatten
|
||||
argv << :string
|
||||
argv << nil
|
||||
|
||||
chdir(dir)
|
||||
ret = fork
|
||||
return ret if ret != 0
|
||||
execlp(cmd, *argv)
|
||||
raise SystemCallError.new(FFI.errno)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -48,7 +48,8 @@ module Puma
|
|||
@config = conf
|
||||
|
||||
@binder = Binder.new(@events)
|
||||
@binder.import_from_env
|
||||
@binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
|
||||
@binder.create_activated_fds(ENV).each { |k| ENV.delete k }
|
||||
|
||||
@environment = conf.environment
|
||||
|
||||
|
@ -69,10 +70,6 @@ module Puma
|
|||
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
||||
end
|
||||
|
||||
if @options[:daemon] && Puma.windows?
|
||||
unsupported 'daemon mode not supported on Windows'
|
||||
end
|
||||
|
||||
Dir.chdir(@restart_dir)
|
||||
|
||||
prune_bundler if prune_bundler?
|
||||
|
@ -184,7 +181,7 @@ module Puma
|
|||
when :exit
|
||||
# nothing
|
||||
end
|
||||
@binder.close_unix_paths
|
||||
close_binder_listeners unless @status == :restart
|
||||
end
|
||||
|
||||
# Return all tcp ports the launcher may be using, TCP or SSL
|
||||
|
@ -202,6 +199,7 @@ module Puma
|
|||
end
|
||||
|
||||
def close_binder_listeners
|
||||
@runner.close_control_listeners
|
||||
@binder.close_listeners
|
||||
end
|
||||
|
||||
|
@ -236,7 +234,7 @@ module Puma
|
|||
end
|
||||
|
||||
def restart!
|
||||
@config.run_hooks :on_restart, self
|
||||
@config.run_hooks :on_restart, self, @events
|
||||
|
||||
if Puma.jruby?
|
||||
close_binder_listeners
|
||||
|
@ -252,6 +250,7 @@ module Puma
|
|||
else
|
||||
argv = restart_args
|
||||
Dir.chdir(@restart_dir)
|
||||
ENV.update(@binder.redirects_for_restart_env)
|
||||
argv += [@binder.redirects_for_restart]
|
||||
Kernel.exec(*argv)
|
||||
end
|
||||
|
@ -297,8 +296,8 @@ module Puma
|
|||
|
||||
log '* Pruning Bundler environment'
|
||||
home = ENV['GEM_HOME']
|
||||
bundle_gemfile = ENV['BUNDLE_GEMFILE']
|
||||
Bundler.with_clean_env do
|
||||
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
||||
with_unbundled_env do
|
||||
ENV['GEM_HOME'] = home
|
||||
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
||||
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
||||
|
@ -454,10 +453,12 @@ module Puma
|
|||
end
|
||||
|
||||
begin
|
||||
Signal.trap "SIGINFO" do
|
||||
thread_status do |name, backtrace|
|
||||
@events.log name
|
||||
@events.log backtrace.map { |bt| " #{bt}" }
|
||||
unless Puma.jruby? # INFO in use by JVM already
|
||||
Signal.trap "SIGINFO" do
|
||||
thread_status do |name, backtrace|
|
||||
@events.log name
|
||||
@events.log backtrace.map { |bt| " #{bt}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue Exception
|
||||
|
@ -472,5 +473,14 @@ module Puma
|
|||
raise "#{feature} is not supported on your version of RubyGems. " \
|
||||
"You must have RubyGems #{min_version}+ to use this feature."
|
||||
end
|
||||
|
||||
def with_unbundled_env
|
||||
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
||||
if bundler_ver < Gem::Version.new('2.1.0')
|
||||
Bundler.with_clean_env { yield }
|
||||
else
|
||||
Bundler.with_unbundled_env { yield }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -125,11 +125,14 @@ module Puma
|
|||
|
||||
def read_and_drop(timeout = 1)
|
||||
return :timeout unless IO.select([@socket], nil, nil, timeout)
|
||||
return :eof unless read_nonblock(1024)
|
||||
:drop
|
||||
rescue Errno::EAGAIN
|
||||
# do nothing
|
||||
:eagain
|
||||
case @socket.read_nonblock(1024, exception: false)
|
||||
when nil
|
||||
:eof
|
||||
when :wait_readable
|
||||
:eagain
|
||||
else
|
||||
:drop
|
||||
end
|
||||
end
|
||||
|
||||
def should_drop_bytes?
|
||||
|
@ -141,9 +144,7 @@ module Puma
|
|||
# Read any drop any partially initialized sockets and any received bytes during shutdown.
|
||||
# Don't let this socket hold this loop forever.
|
||||
# If it can't send more packets within 1s, then give up.
|
||||
while should_drop_bytes?
|
||||
return if [:timeout, :eof].include?(read_and_drop(1))
|
||||
end
|
||||
return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
|
||||
rescue IOError, SystemCallError
|
||||
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
||||
# nothing
|
||||
|
|
|
@ -15,7 +15,7 @@ module Puma
|
|||
# Mimics IO#read with no data.
|
||||
#
|
||||
def read(count = nil, _buffer = nil)
|
||||
(count && count > 0) ? nil : ""
|
||||
count && count > 0 ? nil : ""
|
||||
end
|
||||
|
||||
def rewind
|
||||
|
|
|
@ -67,10 +67,6 @@ module Puma::Rack
|
|||
options[:environment] = e
|
||||
}
|
||||
|
||||
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
|
||||
options[:daemonize] = d ? true : false
|
||||
}
|
||||
|
||||
opts.on("-P", "--pid FILE", "file to store PID") { |f|
|
||||
options[:pid] = ::File.expand_path(f)
|
||||
}
|
||||
|
|
|
@ -189,7 +189,12 @@ module Puma
|
|||
if submon.value == @ready
|
||||
false
|
||||
else
|
||||
submon.value.close
|
||||
if submon.value.can_close?
|
||||
submon.value.close
|
||||
else
|
||||
# Pass remaining open client connections to the thread pool.
|
||||
@app_pool << submon.value
|
||||
end
|
||||
begin
|
||||
selector.deregister submon.value
|
||||
rescue IOError
|
||||
|
|
|
@ -18,10 +18,6 @@ module Puma
|
|||
@started_at = Time.now
|
||||
end
|
||||
|
||||
def daemon?
|
||||
@options[:daemon]
|
||||
end
|
||||
|
||||
def development?
|
||||
@options[:environment] == "development"
|
||||
end
|
||||
|
@ -68,6 +64,10 @@ module Puma
|
|||
@control = control
|
||||
end
|
||||
|
||||
def close_control_listeners
|
||||
@control.binder.close_listeners if @control
|
||||
end
|
||||
|
||||
def ruby_engine
|
||||
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
||||
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
||||
|
@ -88,10 +88,6 @@ module Puma
|
|||
log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
|
||||
log "* Min threads: #{min_t}, max threads: #{max_t}"
|
||||
log "* Environment: #{ENV['RACK_ENV']}"
|
||||
|
||||
if @options[:mode] == :tcp
|
||||
log "* Mode: Lopez Express (tcp)"
|
||||
end
|
||||
end
|
||||
|
||||
def redirected_io?
|
||||
|
@ -130,7 +126,6 @@ module Puma
|
|||
exit 1
|
||||
end
|
||||
|
||||
# Load the app before we daemonize.
|
||||
begin
|
||||
@app = @launcher.config.app
|
||||
rescue Exception => e
|
||||
|
@ -154,10 +149,6 @@ module Puma
|
|||
server.max_threads = max_t
|
||||
server.inherit_binder @launcher.binder
|
||||
|
||||
if @options[:mode] == :tcp
|
||||
server.tcp_mode!
|
||||
end
|
||||
|
||||
if @options[:early_hints]
|
||||
server.early_hints = true
|
||||
end
|
||||
|
|
|
@ -99,10 +99,6 @@ module Puma
|
|||
@binder = bind
|
||||
end
|
||||
|
||||
def tcp_mode!
|
||||
@mode = :tcp
|
||||
end
|
||||
|
||||
# On Linux, use TCP_CORK to better control how the TCP stack
|
||||
# packetizes our stream. This improves both latency and throughput.
|
||||
#
|
||||
|
@ -176,107 +172,6 @@ module Puma
|
|||
@thread_pool and @thread_pool.pool_capacity
|
||||
end
|
||||
|
||||
# Lopez Mode == raw tcp apps
|
||||
|
||||
def run_lopez_mode(background=true)
|
||||
@thread_pool = ThreadPool.new(@min_threads,
|
||||
@max_threads,
|
||||
Hash) do |client, tl|
|
||||
|
||||
io = client.to_io
|
||||
addr = io.peeraddr.last
|
||||
|
||||
if addr.empty?
|
||||
# Set unix socket addrs to localhost
|
||||
addr = "127.0.0.1:0"
|
||||
else
|
||||
addr = "#{addr}:#{io.peeraddr[1]}"
|
||||
end
|
||||
|
||||
env = { 'thread' => tl, REMOTE_ADDR => addr }
|
||||
|
||||
begin
|
||||
@app.call env, client.to_io
|
||||
rescue Object => e
|
||||
STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
|
||||
STDERR.puts e.backtrace
|
||||
end
|
||||
|
||||
client.close unless env['detach']
|
||||
end
|
||||
|
||||
@events.fire :state, :running
|
||||
|
||||
if background
|
||||
@thread = Thread.new do
|
||||
Puma.set_thread_name "server"
|
||||
handle_servers_lopez_mode
|
||||
end
|
||||
return @thread
|
||||
else
|
||||
handle_servers_lopez_mode
|
||||
end
|
||||
end
|
||||
|
||||
def handle_servers_lopez_mode
|
||||
begin
|
||||
check = @check
|
||||
sockets = [check] + @binder.ios
|
||||
pool = @thread_pool
|
||||
|
||||
while @status == :run
|
||||
begin
|
||||
ios = IO.select sockets
|
||||
ios.first.each do |sock|
|
||||
if sock == check
|
||||
break if handle_check
|
||||
else
|
||||
begin
|
||||
if io = sock.accept_nonblock
|
||||
client = Client.new io, nil
|
||||
pool << client
|
||||
end
|
||||
rescue SystemCallError
|
||||
# nothing
|
||||
rescue Errno::ECONNABORTED
|
||||
# client closed the socket even before accept
|
||||
begin
|
||||
io.close
|
||||
rescue
|
||||
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue Object => e
|
||||
@events.unknown_error self, e, "Listen loop"
|
||||
end
|
||||
end
|
||||
|
||||
@events.fire :state, @status
|
||||
|
||||
graceful_shutdown if @status == :stop || @status == :restart
|
||||
|
||||
rescue Exception => e
|
||||
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
||||
STDERR.puts e.backtrace
|
||||
ensure
|
||||
begin
|
||||
@check.close
|
||||
rescue
|
||||
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
||||
end
|
||||
|
||||
# Prevent can't modify frozen IOError (RuntimeError)
|
||||
begin
|
||||
@notify.close
|
||||
rescue IOError
|
||||
# no biggy
|
||||
end
|
||||
end
|
||||
|
||||
@events.fire :state, :done
|
||||
end
|
||||
# Runs the server.
|
||||
#
|
||||
# If +background+ is true (the default) then a thread is spun
|
||||
|
@ -290,12 +185,6 @@ module Puma
|
|||
|
||||
@status = :run
|
||||
|
||||
if @mode == :tcp
|
||||
return run_lopez_mode(background)
|
||||
end
|
||||
|
||||
queue_requests = @queue_requests
|
||||
|
||||
@thread_pool = ThreadPool.new(@min_threads,
|
||||
@max_threads,
|
||||
::Puma::IOBuffer) do |client, buffer|
|
||||
|
@ -306,7 +195,7 @@ module Puma
|
|||
process_now = false
|
||||
|
||||
begin
|
||||
if queue_requests
|
||||
if @queue_requests
|
||||
process_now = client.eagerly_finish
|
||||
else
|
||||
client.finish(@first_data_timeout)
|
||||
|
@ -339,7 +228,7 @@ module Puma
|
|||
|
||||
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
||||
|
||||
if queue_requests
|
||||
if @queue_requests
|
||||
@reactor = Reactor.new self, @thread_pool
|
||||
@reactor.run_in_thread
|
||||
end
|
||||
|
@ -423,16 +312,17 @@ module Puma
|
|||
|
||||
@events.fire :state, @status
|
||||
|
||||
graceful_shutdown if @status == :stop || @status == :restart
|
||||
if queue_requests
|
||||
@queue_requests = false
|
||||
@reactor.clear!
|
||||
@reactor.shutdown
|
||||
end
|
||||
graceful_shutdown if @status == :stop || @status == :restart
|
||||
rescue Exception => e
|
||||
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
||||
STDERR.puts e.backtrace
|
||||
ensure
|
||||
@check.close
|
||||
@check.close unless @check.closed? # Ruby 2.2 issue
|
||||
@notify.close
|
||||
end
|
||||
|
||||
|
@ -480,7 +370,6 @@ module Puma
|
|||
close_socket = false
|
||||
return
|
||||
when true
|
||||
return unless @queue_requests
|
||||
buffer.reset
|
||||
|
||||
ThreadPool.clean_thread_locals if clean_thread_locals
|
||||
|
@ -498,6 +387,7 @@ module Puma
|
|||
end
|
||||
|
||||
unless client.reset(check_for_more_data)
|
||||
return unless @queue_requests
|
||||
close_socket = false
|
||||
client.set_timeout @persistent_timeout
|
||||
@reactor.add client
|
||||
|
@ -699,17 +589,14 @@ module Puma
|
|||
return :async
|
||||
end
|
||||
rescue ThreadPool::ForceShutdown => e
|
||||
@events.log "Detected force shutdown of a thread, returning 503"
|
||||
@events.unknown_error self, e, "Rack app"
|
||||
|
||||
status = 503
|
||||
headers = {}
|
||||
res_body = ["Request was internally terminated early\n"]
|
||||
@events.unknown_error self, e, "Rack app", env
|
||||
@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
|
||||
|
||||
status, headers, res_body = lowlevel_error(e, env)
|
||||
status, headers, res_body = lowlevel_error(e, env, 500)
|
||||
end
|
||||
|
||||
content_length = nil
|
||||
|
@ -724,10 +611,10 @@ module Puma
|
|||
line_ending = LINE_END
|
||||
colon = COLON
|
||||
|
||||
http_11 = if env[HTTP_VERSION] == HTTP_11
|
||||
http_11 = env[HTTP_VERSION] == HTTP_11
|
||||
if http_11
|
||||
allow_chunked = true
|
||||
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
||||
include_keepalive_header = false
|
||||
|
||||
# An optimization. The most common response is 200, so we can
|
||||
# reply with the proper 200 status without having to compute
|
||||
|
@ -741,11 +628,9 @@ module Puma
|
|||
|
||||
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
||||
end
|
||||
true
|
||||
else
|
||||
allow_chunked = false
|
||||
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
||||
include_keepalive_header = keep_alive
|
||||
|
||||
# Same optimization as above for HTTP/1.1
|
||||
#
|
||||
|
@ -757,9 +642,12 @@ module Puma
|
|||
|
||||
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
# regardless of what the client wants, we always close the connection
|
||||
# if running without request queueing
|
||||
keep_alive &&= @queue_requests
|
||||
|
||||
response_hijack = nil
|
||||
|
||||
headers.each do |k, vs|
|
||||
|
@ -786,10 +674,15 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
if include_keepalive_header
|
||||
lines << CONNECTION_KEEP_ALIVE
|
||||
elsif http_11 && !keep_alive
|
||||
lines << CONNECTION_CLOSE
|
||||
# HTTP/1.1 & 1.0 assume different defaults:
|
||||
# - HTTP 1.0 assumes the connection will be closed if not specified
|
||||
# - HTTP 1.1 assumes the connection will be kept alive if not specified.
|
||||
# Only set the header if we're doing something which is not the default
|
||||
# for this protocol version
|
||||
if http_11
|
||||
lines << CONNECTION_CLOSE if !keep_alive
|
||||
else
|
||||
lines << CONNECTION_KEEP_ALIVE if keep_alive
|
||||
end
|
||||
|
||||
if no_body
|
||||
|
@ -916,19 +809,21 @@ module Puma
|
|||
|
||||
# A fallback rack response if +@app+ raises as exception.
|
||||
#
|
||||
def lowlevel_error(e, env)
|
||||
def lowlevel_error(e, env, status=500)
|
||||
if handler = @options[:lowlevel_error_handler]
|
||||
if handler.arity == 1
|
||||
return handler.call(e)
|
||||
else
|
||||
elsif handler.arity == 2
|
||||
return handler.call(e, env)
|
||||
else
|
||||
return handler.call(e, env, status)
|
||||
end
|
||||
end
|
||||
|
||||
if @leak_stack_on_error
|
||||
[500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
|
||||
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
|
||||
else
|
||||
[500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
|
||||
[status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -42,64 +42,10 @@ module Puma
|
|||
@server.stop(true) if @server
|
||||
end
|
||||
|
||||
def jruby_daemon?
|
||||
daemon? and Puma.jruby?
|
||||
end
|
||||
|
||||
def jruby_daemon_start
|
||||
require 'puma/jruby_restart'
|
||||
JRubyRestart.daemon_start(@restart_dir, @launcher.restart_args)
|
||||
end
|
||||
|
||||
def run
|
||||
already_daemon = false
|
||||
|
||||
if jruby_daemon?
|
||||
require 'puma/jruby_restart'
|
||||
|
||||
if JRubyRestart.daemon?
|
||||
# load and bind before redirecting IO so errors show up on stdout/stderr
|
||||
load_and_bind
|
||||
redirect_io
|
||||
end
|
||||
|
||||
already_daemon = JRubyRestart.daemon_init
|
||||
end
|
||||
|
||||
output_header "single"
|
||||
|
||||
if jruby_daemon?
|
||||
if already_daemon
|
||||
JRubyRestart.perm_daemonize
|
||||
else
|
||||
pid = nil
|
||||
|
||||
Signal.trap "SIGUSR2" do
|
||||
log "* Started new process #{pid} as daemon..."
|
||||
|
||||
# Must use exit! so we don't unwind and run the ensures
|
||||
# that will be run by the new child (such as deleting the
|
||||
# pidfile)
|
||||
exit!(true)
|
||||
end
|
||||
|
||||
Signal.trap "SIGCHLD" do
|
||||
log "! Error starting new process as daemon, exiting"
|
||||
exit 1
|
||||
end
|
||||
|
||||
jruby_daemon_start
|
||||
sleep
|
||||
end
|
||||
else
|
||||
if daemon?
|
||||
log "* Daemonizing..."
|
||||
Process.daemon(true)
|
||||
redirect_io
|
||||
end
|
||||
|
||||
load_and_bind
|
||||
end
|
||||
load_and_bind
|
||||
|
||||
Plugins.fire_background
|
||||
|
||||
|
@ -109,10 +55,9 @@ module Puma
|
|||
|
||||
@server = server = start_server
|
||||
|
||||
unless daemon?
|
||||
log "Use Ctrl-C to stop"
|
||||
redirect_io
|
||||
end
|
||||
|
||||
log "Use Ctrl-C to stop"
|
||||
redirect_io
|
||||
|
||||
@launcher.events.fire_on_booted!
|
||||
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Puma
|
||||
class TCPLogger
|
||||
def initialize(logger, app, quiet=false)
|
||||
@logger = logger
|
||||
@app = app
|
||||
@quiet = quiet
|
||||
end
|
||||
|
||||
FORMAT = "%s - %s"
|
||||
|
||||
def log(who, str)
|
||||
now = Time.now.strftime("%d/%b/%Y %H:%M:%S")
|
||||
|
||||
log_str = "#{now} - #{who} - #{str}"
|
||||
|
||||
case @logger
|
||||
when IO
|
||||
@logger.puts log_str
|
||||
when Events
|
||||
@logger.log log_str
|
||||
end
|
||||
end
|
||||
|
||||
def call(env, socket)
|
||||
who = env[Const::REMOTE_ADDR]
|
||||
log who, "connected" unless @quiet
|
||||
|
||||
env['log'] = lambda { |str| log(who, str) }
|
||||
|
||||
begin
|
||||
@app.call env, socket
|
||||
rescue Object => e
|
||||
log who, "exception: #{e.message} (#{e.class})"
|
||||
else
|
||||
log who, "disconnected" unless @quiet
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -59,7 +59,7 @@ module Rack
|
|||
conf
|
||||
end
|
||||
|
||||
def self.run(app, options = {})
|
||||
def self.run(app, **options)
|
||||
conf = self.config(app, options)
|
||||
|
||||
events = options.delete(:Silent) ? ::Puma::Events.strings : ::Puma::Events.stdio
|
||||
|
|
1
test/bundle_preservation_test/version1/Gemfile
Normal file
1
test/bundle_preservation_test/version1/Gemfile
Normal file
|
@ -0,0 +1 @@
|
|||
gem 'puma', path: '../../..'
|
1
test/bundle_preservation_test/version1/config.ru
Normal file
1
test/bundle_preservation_test/version1/config.ru
Normal file
|
@ -0,0 +1 @@
|
|||
run lambda { |env| [200, {'Content-Type'=>'text/plain'}, [ENV['BUNDLE_GEMFILE'].inspect]] }
|
1
test/bundle_preservation_test/version1/config/puma.rb
Normal file
1
test/bundle_preservation_test/version1/config/puma.rb
Normal file
|
@ -0,0 +1 @@
|
|||
directory File.expand_path("../../current", __dir__)
|
1
test/bundle_preservation_test/version2/Gemfile
Normal file
1
test/bundle_preservation_test/version2/Gemfile
Normal file
|
@ -0,0 +1 @@
|
|||
gem 'puma', path: '../../..'
|
1
test/bundle_preservation_test/version2/config.ru
Normal file
1
test/bundle_preservation_test/version2/config.ru
Normal file
|
@ -0,0 +1 @@
|
|||
run lambda { |env| [200, {'Content-Type'=>'text/plain'}, [ENV['BUNDLE_GEMFILE'].inspect]] }
|
1
test/bundle_preservation_test/version2/config/puma.rb
Normal file
1
test/bundle_preservation_test/version2/config/puma.rb
Normal file
|
@ -0,0 +1 @@
|
|||
directory File.expand_path("../../current", __dir__)
|
|
@ -11,26 +11,23 @@ if %w(2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1).include? RUBY_VERSION
|
|||
end
|
||||
end
|
||||
|
||||
require "net/http"
|
||||
require "timeout"
|
||||
require "minitest/autorun"
|
||||
require "minitest/pride"
|
||||
require "minitest/proveit"
|
||||
require_relative "helpers/apps"
|
||||
|
||||
$LOAD_PATH << File.expand_path("../../lib", __FILE__)
|
||||
Thread.abort_on_exception = true
|
||||
|
||||
$debugging_info = ''.dup
|
||||
$debugging_hold = false # needed for TestCLI#test_control_clustered
|
||||
|
||||
require "puma"
|
||||
require "puma/events"
|
||||
require "puma/detect"
|
||||
|
||||
# Either takes a string to do a get request against, or a tuple of [URI, HTTP] where
|
||||
# HTTP is some kind of Net::HTTP request object (POST, HEAD, etc.)
|
||||
def hit(uris)
|
||||
require "net/http"
|
||||
uris.map do |u|
|
||||
response =
|
||||
if u.kind_of? String
|
||||
|
@ -46,25 +43,21 @@ def hit(uris)
|
|||
end
|
||||
|
||||
module UniquePort
|
||||
@port = 3211
|
||||
@mutex = Mutex.new
|
||||
|
||||
def self.call
|
||||
@mutex.synchronize {
|
||||
@port += 1
|
||||
@port = 3307 if @port == 3306 # MySQL on Actions
|
||||
@port
|
||||
}
|
||||
TCPServer.open('127.0.0.1', 0) do |server|
|
||||
server.connect_address.ip_port
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require "timeout"
|
||||
module TimeoutEveryTestCase
|
||||
# our own subclass so we never confused different timeouts
|
||||
class TestTookTooLong < Timeout::Error
|
||||
end
|
||||
|
||||
def run(*)
|
||||
::Timeout.timeout(Puma.jruby? ? 120 : 60, TestTookTooLong) { super }
|
||||
::Timeout.timeout(RUBY_ENGINE == 'ruby' ? 60 : 120, TestTookTooLong) { super }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -80,7 +73,7 @@ module TestSkips
|
|||
# usage: skip NO_FORK_MSG unless HAS_FORK
|
||||
# windows >= 2.6 fork is not defined, < 2.6 fork raises NotImplementedError
|
||||
HAS_FORK = ::Process.respond_to? :fork
|
||||
NO_FORK_MSG = "Kernel.fork isn't available on the #{RUBY_PLATFORM} platform"
|
||||
NO_FORK_MSG = "Kernel.fork isn't available on #{RUBY_ENGINE} on #{RUBY_PLATFORM}"
|
||||
|
||||
# socket is required by puma
|
||||
# usage: skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
||||
|
@ -101,11 +94,11 @@ module TestSkips
|
|||
# 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 :darwin then "Skipped on darwin#{suffix}" if RUBY_PLATFORM[/darwin/]
|
||||
when :jruby then "Skipped on JRuby#{suffix}" if Puma.jruby?
|
||||
when :truffleruby then "Skipped on TruffleRuby#{suffix}" if RUBY_ENGINE == "truffleruby"
|
||||
when :windows then "Skipped on Windows#{suffix}" if Puma.windows?
|
||||
when :ci then "Skipped on ENV['CI']#{suffix}" if ENV["CI"]
|
||||
when :no_bundler then "Skipped w/o Bundler#{suffix}" if !defined?(Bundler)
|
||||
|
|
|
@ -19,16 +19,21 @@ class TestIntegration < Minitest::Test
|
|||
end
|
||||
|
||||
def teardown
|
||||
if defined?(@server) && @server
|
||||
if defined?(@server) && @server && @pid
|
||||
stop_server @pid, signal: :INT
|
||||
end
|
||||
|
||||
@ios_to_close.each do |io|
|
||||
io.close if io.is_a?(IO) && !io.closed?
|
||||
io = nil
|
||||
if @ios_to_close
|
||||
@ios_to_close.each do |io|
|
||||
io.close if io.is_a?(IO) && !io.closed?
|
||||
io = nil
|
||||
end
|
||||
end
|
||||
|
||||
if @bind_path
|
||||
refute File.exist?(@bind_path), "Bind path must be removed after stop"
|
||||
File.unlink(@bind_path) rescue nil
|
||||
end
|
||||
refute File.exist?(@bind_path), "Bind path must be removed after stop"
|
||||
File.unlink(@bind_path) rescue nil
|
||||
|
||||
# wait until the end for OS buffering?
|
||||
if defined?(@server) && @server
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
results = %w[t1 t2 t3].map do |test|
|
||||
require "puma"
|
||||
require "puma/detect"
|
||||
|
||||
TESTS_TO_RUN = if Process.respond_to?(:fork)
|
||||
%w[t1 t2 t3]
|
||||
else
|
||||
%w[t1 t2]
|
||||
end
|
||||
|
||||
results = TESTS_TO_RUN.map do |test|
|
||||
system("ruby -rrubygems test/shell/#{test}.rb ") # > /dev/null 2>&1
|
||||
end
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
system "ruby -rrubygems -Ilib bin/puma -p 10102 -C test/shell/t1_conf.rb test/rackup/hello.ru &"
|
||||
sleep 5
|
||||
system "curl http://localhost:10102/"
|
||||
|
||||
system "kill `cat t1-pid`"
|
||||
sleep 1 until system "curl http://localhost:10102/"
|
||||
|
||||
Process.kill :TERM, Integer(File.read("t1-pid"))
|
||||
|
||||
sleep 1
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
system "ruby -rrubygems -Ilib bin/pumactl -F test/shell/t2_conf.rb start"
|
||||
sleep 5
|
||||
system "curl http://localhost:10103/"
|
||||
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`
|
||||
|
||||
|
|
|
@ -3,4 +3,3 @@ stdout_redirect "t2-stdout"
|
|||
pidfile "t2-pid"
|
||||
bind "tcp://0.0.0.0:10103"
|
||||
rackup File.expand_path('../rackup/hello.ru', File.dirname(__FILE__))
|
||||
daemonize
|
||||
|
|
|
@ -3,13 +3,13 @@ sleep 5
|
|||
|
||||
worker_pid_was_present = File.file? "t3-worker-2-pid"
|
||||
|
||||
system "kill `cat t3-worker-2-pid`" # kill off a worker
|
||||
Process.kill :TERM, Integer(File.read("t3-worker-2-pid")) # kill off a worker
|
||||
|
||||
sleep 2
|
||||
|
||||
worker_index_within_number_of_workers = !File.file?("t3-worker-3-pid")
|
||||
|
||||
system "kill `cat t3-pid`"
|
||||
Process.kill :TERM, Integer(File.read("t3-pid"))
|
||||
|
||||
File.unlink "t3-pid" if File.file? "t3-pid"
|
||||
File.unlink "t3-worker-0-pid" if File.file? "t3-worker-0-pid"
|
||||
|
|
|
@ -5,6 +5,7 @@ require_relative "helpers/ssl"
|
|||
|
||||
require "puma/binder"
|
||||
require "puma/puma_http11"
|
||||
require "puma/events"
|
||||
|
||||
class TestBinderBase < Minitest::Test
|
||||
include SSLHelper
|
||||
|
@ -22,16 +23,42 @@ class TestBinderBase < Minitest::Test
|
|||
end
|
||||
|
||||
class TestBinder < TestBinderBase
|
||||
def test_localhost_addresses_dont_alter_listeners_for_tcp_addresses
|
||||
@binder.parse ["tcp://localhost:10001"], @events
|
||||
parallelize_me!
|
||||
|
||||
assert_equal [], @binder.instance_variable_get(:@listeners)
|
||||
def test_localhost_addresses_dont_alter_listeners_for_tcp_addresses
|
||||
@binder.parse ["tcp://localhost:0"], @events
|
||||
|
||||
assert_empty @binder.listeners
|
||||
end
|
||||
|
||||
def test_home_alters_listeners_for_tcp_addresses
|
||||
port = UniquePort.call
|
||||
@binder.parse ["tcp://127.0.0.1:#{port}"], @events
|
||||
|
||||
assert_equal "tcp://127.0.0.1:#{port}", @binder.listeners[0][0]
|
||||
assert_kind_of TCPServer, @binder.listeners[0][1]
|
||||
end
|
||||
|
||||
def test_connected_ports
|
||||
ports = (1..3).map { |_| UniquePort.call }
|
||||
|
||||
@binder.parse(ports.map { |p| "tcp://localhost:#{p}" }, @events)
|
||||
|
||||
assert_equal ports, @binder.connected_ports
|
||||
end
|
||||
|
||||
def test_localhost_addresses_dont_alter_listeners_for_ssl_addresses
|
||||
@binder.parse ["ssl://localhost:10002?#{ssl_query}"], @events
|
||||
@binder.parse ["ssl://localhost:0?#{ssl_query}"], @events
|
||||
|
||||
assert_equal [], @binder.instance_variable_get(:@listeners)
|
||||
assert_empty @binder.listeners
|
||||
end
|
||||
|
||||
def test_home_alters_listeners_for_ssl_addresses
|
||||
port = UniquePort.call
|
||||
@binder.parse ["ssl://127.0.0.1:#{port}?#{ssl_query}"], @events
|
||||
|
||||
assert_equal "ssl://127.0.0.1:#{port}?#{ssl_query}", @binder.listeners[0][0]
|
||||
assert_kind_of TCPServer, @binder.listeners[0][1]
|
||||
end
|
||||
|
||||
def test_correct_zero_port
|
||||
|
@ -43,27 +70,33 @@ class TestBinder < TestBinderBase
|
|||
refute_equal 0, port
|
||||
end
|
||||
|
||||
def test_correct_zero_port_ssl
|
||||
skip("Implement later")
|
||||
ssl_regex = %r!ssl://127.0.0.1:(\d+)!
|
||||
|
||||
@binder.parse ["ssl://localhost:0?#{ssl_query}"], @events
|
||||
|
||||
port = ssl_regex.match(@events.stdout.string)[1].to_i
|
||||
|
||||
refute_equal 0, port
|
||||
end
|
||||
|
||||
def test_logs_all_localhost_bindings
|
||||
@binder.parse ["tcp://localhost:0"], @events
|
||||
|
||||
assert_match %r!tcp://127.0.0.1:(\d+)!, @events.stdout.string
|
||||
if @binder.loopback_addresses.include?("::1")
|
||||
if Socket.ip_address_list.any? {|i| i.ipv6_loopback? }
|
||||
assert_match %r!tcp://\[::1\]:(\d+)!, @events.stdout.string
|
||||
end
|
||||
end
|
||||
|
||||
def test_correct_zero_port_ssl
|
||||
skip("Implement in 4.3")
|
||||
def test_logs_all_localhost_bindings_ssl
|
||||
skip("Incorrectly logs localhost, not 127.0.0.1")
|
||||
@binder.parse ["ssl://localhost:0?#{ssl_query}"], @events
|
||||
|
||||
stdout = @events.stdout.string
|
||||
m = %r!tcp://127.0.0.1:(\d+)!.match(stdout)
|
||||
port = m[1].to_i
|
||||
|
||||
refute_equal 0, port
|
||||
assert_match %r!ssl://127.0.0.1:(\d+)!, stdout
|
||||
if @binder.loopback_addresses.include? '::1'
|
||||
assert_match %r!ssl://\[::1\]:(\d+)!, stdout
|
||||
assert_match %r!ssl://127.0.0.1:(\d+)!, @events.stdout.string
|
||||
if Socket.ip_address_list.any? {|i| i.ipv6_loopback? }
|
||||
assert_match %r!ssl://\[::1\]:(\d+)!, @events.stdout.string
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -72,6 +105,7 @@ class TestBinder < TestBinderBase
|
|||
end
|
||||
|
||||
def test_allows_both_unix_and_tcp
|
||||
skip_on :jruby # Undiagnosed thread race. TODO fix
|
||||
assert_parsing_logs_uri [:unix, :tcp]
|
||||
end
|
||||
|
||||
|
@ -88,9 +122,9 @@ class TestBinder < TestBinderBase
|
|||
|
||||
assert_match %r!unix://#{unix_path}!, @events.stdout.string
|
||||
|
||||
refute_includes @binder.instance_variable_get(:@unix_paths), unix_path
|
||||
refute_includes @binder.unix_paths, unix_path
|
||||
|
||||
@binder.close_unix_paths
|
||||
@binder.close_listeners
|
||||
|
||||
assert File.exist?(unix_path)
|
||||
|
||||
|
@ -131,8 +165,147 @@ class TestBinder < TestBinderBase
|
|||
refute ssl_context_for_binder.no_tlsv1_1
|
||||
end
|
||||
|
||||
def test_env_contains_protoenv
|
||||
@binder.parse ["ssl://localhost:0?#{ssl_query}"], @events
|
||||
|
||||
env_hash = @binder.envs[@binder.ios.first]
|
||||
|
||||
@binder.proto_env.each do |k,v|
|
||||
assert_equal env_hash[k], v
|
||||
end
|
||||
end
|
||||
|
||||
def test_env_contains_stderr
|
||||
@binder.parse ["ssl://localhost:0?#{ssl_query}"], @events
|
||||
|
||||
env_hash = @binder.envs[@binder.ios.first]
|
||||
|
||||
assert_equal @events.stderr, env_hash["rack.errors"]
|
||||
end
|
||||
|
||||
def test_close_calls_close_on_ios
|
||||
@mocked_ios = [Minitest::Mock.new, Minitest::Mock.new]
|
||||
@mocked_ios.each { |m| m.expect(:close, true) }
|
||||
@binder.ios = @mocked_ios
|
||||
|
||||
@binder.close
|
||||
|
||||
assert @mocked_ios.map(&:verify).all?
|
||||
end
|
||||
|
||||
def test_redirects_for_restart_creates_a_hash
|
||||
@binder.parse ["tcp://127.0.0.1:0"], @events
|
||||
|
||||
result = @binder.redirects_for_restart
|
||||
ios = @binder.listeners.map { |_l, io| io.to_i }
|
||||
|
||||
ios.each { |int| assert_equal int, result[int] }
|
||||
assert result[:close_others]
|
||||
end
|
||||
|
||||
def test_redirects_for_restart_env
|
||||
@binder.parse ["tcp://127.0.0.1:0"], @events
|
||||
|
||||
result = @binder.redirects_for_restart_env
|
||||
|
||||
@binder.listeners.each_with_index do |l, i|
|
||||
assert_equal "#{l[1].to_i}:#{l[0]}", result["PUMA_INHERIT_#{i}"]
|
||||
end
|
||||
end
|
||||
|
||||
def test_close_listeners_closes_ios
|
||||
@binder.parse ["tcp://127.0.0.1:#{UniquePort.call}"], @events
|
||||
|
||||
refute @binder.listeners.any? { |u, l| l.closed? }
|
||||
|
||||
@binder.close_listeners
|
||||
|
||||
assert @binder.listeners.all? { |u, l| l.closed? }
|
||||
end
|
||||
|
||||
def test_close_listeners_closes_ios_unless_closed?
|
||||
@binder.parse ["tcp://127.0.0.1:0"], @events
|
||||
|
||||
bomb = @binder.listeners.first[1]
|
||||
bomb.close
|
||||
def bomb.close; raise "Boom!"; end # the bomb has been planted
|
||||
|
||||
assert @binder.listeners.any? { |u, l| l.closed? }
|
||||
|
||||
@binder.close_listeners
|
||||
|
||||
assert @binder.listeners.all? { |u, l| l.closed? }
|
||||
end
|
||||
|
||||
def test_listeners_file_unlink_if_unix_listener
|
||||
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
||||
|
||||
@binder.parse ["unix://test/#{name}_server.sock"], @events
|
||||
|
||||
assert File.socket?("test/#{name}_server.sock")
|
||||
|
||||
@binder.close_listeners
|
||||
|
||||
refute File.socket?("test/#{name}_server.sock")
|
||||
end
|
||||
|
||||
def test_import_from_env_listen_inherit
|
||||
@binder.parse ["tcp://127.0.0.1:0"], @events
|
||||
removals = @binder.create_inherited_fds(@binder.redirects_for_restart_env)
|
||||
|
||||
@binder.listeners.each do |url, io|
|
||||
assert_equal io.to_i, @binder.inherited_fds[url]
|
||||
end
|
||||
assert_includes removals, "PUMA_INHERIT_0"
|
||||
end
|
||||
|
||||
# Socket activation tests. We have to skip all of these on non-UNIX platforms
|
||||
# because the check that we do in the code only works if you support UNIX sockets.
|
||||
# This is OK, because systemd obviously only works on Linux.
|
||||
def test_socket_activation_tcp
|
||||
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
||||
url = "127.0.0.1"
|
||||
port = UniquePort.call
|
||||
sock = Addrinfo.tcp(url, port).listen
|
||||
assert_activates_sockets(url: url, port: port, sock: sock)
|
||||
end
|
||||
|
||||
def test_socket_activation_tcp_ipv6
|
||||
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
||||
url = "::"
|
||||
port = UniquePort.call
|
||||
sock = Addrinfo.tcp(url, port).listen
|
||||
assert_activates_sockets(url: url, port: port, sock: sock)
|
||||
end
|
||||
|
||||
def test_socket_activation_unix
|
||||
skip_on :jruby # Failing with what I think is a JRuby bug
|
||||
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
||||
path = "test/unixserver.state"
|
||||
sock = Addrinfo.unix(path).listen
|
||||
assert_activates_sockets(path: path, sock: sock)
|
||||
ensure
|
||||
File.unlink(path) rescue nil # JRuby race?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_activates_sockets(path: nil, port: nil, url: nil, sock: nil)
|
||||
hash = { "LISTEN_FDS" => 1, "LISTEN_PID" => $$ }
|
||||
@events.instance_variable_set(:@debug, true)
|
||||
|
||||
@binder.instance_variable_set(:@sock_fd, sock.fileno)
|
||||
def @binder.socket_activation_fd(int); @sock_fd; end
|
||||
@result = @binder.create_activated_fds(hash)
|
||||
|
||||
url = "[::]" if url == "::"
|
||||
ary = path ? [:unix, path] : [:tcp, url, port]
|
||||
|
||||
assert_kind_of TCPServer, @binder.activated_sockets[ary]
|
||||
assert_match "Registered #{ary.join(":")} for activation from LISTEN_FDS", @events.stdout.string
|
||||
assert_equal ["LISTEN_FDS", "LISTEN_PID"], @result
|
||||
end
|
||||
|
||||
def assert_parsing_logs_uri(order = [:unix, :tcp])
|
||||
skip UNIX_SKT_MSG if order.include?(:unix) && !UNIX_SKT_EXIST
|
||||
|
||||
|
@ -147,10 +320,11 @@ class TestBinder < TestBinderBase
|
|||
@binder.parse tested_paths, @events
|
||||
stdout = @events.stdout.string
|
||||
|
||||
assert stdout.include?(prepared_paths[order[0]]), "\n#{stdout}\n"
|
||||
assert stdout.include?(prepared_paths[order[1]]), "\n#{stdout}\n"
|
||||
order.each do |prot|
|
||||
assert_match prepared_paths[prot], stdout
|
||||
end
|
||||
ensure
|
||||
@binder.close_unix_paths if order.include?(:unix) && UNIX_SKT_EXIST
|
||||
@binder.close_listeners if order.include?(:unix) && UNIX_SKT_EXIST
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -64,6 +64,8 @@ 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}"
|
||||
|
@ -94,8 +96,8 @@ class TestCLI < Minitest::Test
|
|||
assert_equal([:started_at, :backlog, :running, :pool_capacity, :max_threads, :requests_count], Puma.stats.keys)
|
||||
|
||||
ensure
|
||||
cli.launcher.stop
|
||||
t.join
|
||||
cli.launcher.stop if cli
|
||||
t.join if t
|
||||
end
|
||||
|
||||
def test_control_clustered
|
||||
|
|
|
@ -4,6 +4,7 @@ require_relative "helper"
|
|||
require_relative "helpers/config_file"
|
||||
|
||||
require "puma/configuration"
|
||||
require 'puma/events'
|
||||
|
||||
class TestConfigFile < TestConfigFileBase
|
||||
parallelize_me!
|
||||
|
@ -66,7 +67,11 @@ class TestConfigFile < TestConfigFileBase
|
|||
end
|
||||
conf.load
|
||||
|
||||
conf.app
|
||||
# suppress deprecation warning of Rack (>= 2.2.0)
|
||||
# > Parsing options from the first comment line is deprecated!\n
|
||||
assert_output(nil, nil) do
|
||||
conf.app
|
||||
end
|
||||
|
||||
assert_equal ["tcp://127.0.0.1:9292"], conf.options[:binds]
|
||||
end
|
||||
|
@ -249,6 +254,61 @@ class TestConfigFile < TestConfigFileBase
|
|||
conf.options[:raise_exception_on_sigterm] = true
|
||||
assert_equal conf.options[:raise_exception_on_sigterm], true
|
||||
end
|
||||
|
||||
def test_run_hooks_on_restart_hook
|
||||
assert_run_hooks :on_restart
|
||||
end
|
||||
|
||||
def test_run_hooks_before_worker_fork
|
||||
assert_run_hooks :before_worker_fork, configured_with: :on_worker_fork
|
||||
end
|
||||
|
||||
def test_run_hooks_after_worker_fork
|
||||
assert_run_hooks :after_worker_fork
|
||||
end
|
||||
|
||||
def test_run_hooks_before_worker_boot
|
||||
assert_run_hooks :before_worker_boot, configured_with: :on_worker_boot
|
||||
end
|
||||
|
||||
def test_run_hooks_before_worker_shutdown
|
||||
assert_run_hooks :before_worker_shutdown, configured_with: :on_worker_shutdown
|
||||
end
|
||||
|
||||
def test_run_hooks_before_fork
|
||||
assert_run_hooks :before_fork
|
||||
end
|
||||
|
||||
def test_run_hooks_and_exception
|
||||
conf = Puma::Configuration.new do |c|
|
||||
c.on_restart do |a|
|
||||
raise RuntimeError, 'Error from hook'
|
||||
end
|
||||
end
|
||||
conf.load
|
||||
events = Puma::Events.strings
|
||||
|
||||
conf.run_hooks :on_restart, 'ARG', events
|
||||
expected = /WARNING hook on_restart failed with exception \(RuntimeError\) Error from hook/
|
||||
assert_match expected, events.stdout.string
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_run_hooks(hook_name, options = {})
|
||||
configured_with = options[:configured_with] || hook_name
|
||||
|
||||
messages = []
|
||||
conf = Puma::Configuration.new do |c|
|
||||
c.send(configured_with) do |a|
|
||||
messages << "#{hook_name} is called with #{a}"
|
||||
end
|
||||
end
|
||||
conf.load
|
||||
|
||||
conf.run_hooks hook_name, 'ARG', Puma::Events.strings
|
||||
assert_equal messages, ["#{hook_name} is called with ARG"]
|
||||
end
|
||||
end
|
||||
|
||||
# Thread unsafe modification of ENV
|
||||
|
|
|
@ -144,15 +144,15 @@ class Http11ParserTest < Minitest::Test
|
|||
parser = Puma::HttpParser.new
|
||||
req = {}
|
||||
|
||||
# Support URI path length to a max of 2048
|
||||
path = "/" + rand_data(1000, 100)
|
||||
# Support URI path length to a max of 8196
|
||||
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 > 2048
|
||||
path = "/" + rand_data(3000, 100)
|
||||
# Raise exception if URI path length > 8196
|
||||
path = "/" + rand_data(9000, 100)
|
||||
http = "GET #{path} HTTP/1.1\r\n\r\n"
|
||||
assert_raises Puma::HttpParserError do
|
||||
parser.execute(req, http, 0)
|
||||
|
|
|
@ -14,10 +14,13 @@ class TestIntegrationPumactl < TestIntegration
|
|||
def teardown
|
||||
super
|
||||
|
||||
refute File.exist?(@control_path), "Control path must be removed after stop"
|
||||
ensure
|
||||
[@state_path, @control_path].each { |p| File.unlink(p) rescue nil }
|
||||
end
|
||||
|
||||
def test_stop_tcp
|
||||
skip_on :jruby, :truffleruby # Undiagnose thread race. TODO fix
|
||||
@control_tcp_port = UniquePort.call
|
||||
cli_server "-q test/rackup/sleep.ru --control-url tcp://#{HOST}:#{@control_tcp_port} --control-token #{TOKEN} -S #{@state_path}"
|
||||
|
||||
|
@ -30,10 +33,18 @@ class TestIntegrationPumactl < TestIntegration
|
|||
end
|
||||
|
||||
def test_stop_unix
|
||||
ctl_unix
|
||||
end
|
||||
|
||||
def test_halt_unix
|
||||
ctl_unix 'halt'
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
cli_pumactl "stop", unix: true
|
||||
cli_pumactl signal, unix: true
|
||||
|
||||
_, status = Process.wait2(@pid)
|
||||
assert_equal 0, status
|
||||
|
|
|
@ -49,7 +49,7 @@ class TestIntegrationSingle < TestIntegration
|
|||
|
||||
cli_server 'test/rackup/sleep.ru'
|
||||
|
||||
_stdin, curl_stdout, _stderr, curl_wait_thread = Open3.popen3("curl http://#{HOST}:#{@tcp_port}/sleep10")
|
||||
_stdin, curl_stdout, _stderr, curl_wait_thread = Open3.popen3({ 'LC_ALL' => 'C' }, "curl http://#{HOST}:#{@tcp_port}/sleep10")
|
||||
sleep 1 # ensure curl send a request
|
||||
|
||||
Process.kill :TERM, @pid
|
||||
|
|
|
@ -7,6 +7,12 @@ class TestPreserveBundlerEnv < TestIntegration
|
|||
super
|
||||
end
|
||||
|
||||
def teardown
|
||||
return if skipped?
|
||||
FileUtils.rm current_release_symlink, force: true
|
||||
super
|
||||
end
|
||||
|
||||
# It does not wipe out BUNDLE_GEMFILE et al
|
||||
def test_usr2_restart_preserves_bundler_environment
|
||||
skip_unless_signal_exist? :USR2
|
||||
|
@ -32,4 +38,53 @@ class TestPreserveBundlerEnv < TestIntegration
|
|||
new_reply = read_body(connection)
|
||||
assert_match("Gemfile.bundle_env_preservation_test", new_reply)
|
||||
end
|
||||
|
||||
def test_phased_restart_preserves_unspecified_bundle_gemfile
|
||||
skip_unless_signal_exist? :USR1
|
||||
|
||||
@tcp_port = UniquePort.call
|
||||
env = {
|
||||
"BUNDLE_GEMFILE" => nil,
|
||||
"BUNDLER_ORIG_BUNDLE_GEMFILE" => nil
|
||||
}
|
||||
set_release_symlink File.expand_path("bundle_preservation_test/version1", __dir__)
|
||||
cmd = "bundle exec puma -q -w 1 --prune-bundler -b tcp://#{HOST}:#{@tcp_port}"
|
||||
Dir.chdir(current_release_symlink) do
|
||||
@server = IO.popen(env, cmd.split, "r")
|
||||
end
|
||||
wait_for_server_to_boot
|
||||
@pid = @server.pid
|
||||
connection = connect
|
||||
|
||||
# Bundler itself sets ENV['BUNDLE_GEMFILE'] to the Gemfile it finds if ENV['BUNDLE_GEMFILE'] was unspecified
|
||||
initial_reply = read_body(connection)
|
||||
expected_gemfile = File.expand_path("bundle_preservation_test/version1/Gemfile", __dir__).inspect
|
||||
assert_equal(expected_gemfile, initial_reply)
|
||||
|
||||
set_release_symlink File.expand_path("bundle_preservation_test/version2", __dir__)
|
||||
start_phased_restart
|
||||
|
||||
connection = connect
|
||||
connection.write "GET / HTTP/1.1\r\n\r\n"
|
||||
new_reply = read_body(connection)
|
||||
expected_gemfile = File.expand_path("bundle_preservation_test/version2/Gemfile", __dir__).inspect
|
||||
assert_equal(expected_gemfile, new_reply)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_release_symlink
|
||||
File.expand_path "bundle_preservation_test/current", __dir__
|
||||
end
|
||||
|
||||
def set_release_symlink(target_dir)
|
||||
FileUtils.rm current_release_symlink, force: true
|
||||
FileUtils.symlink target_dir, current_release_symlink, force: true
|
||||
end
|
||||
|
||||
def start_phased_restart
|
||||
Process.kill :USR1, @pid
|
||||
|
||||
true while @server.gets !~ /booted, phase: 1/
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
require_relative "helper"
|
||||
require "puma/events"
|
||||
require "net/http"
|
||||
|
||||
class TestPumaServer < Minitest::Test
|
||||
parallelize_me!
|
||||
|
@ -261,6 +263,36 @@ EOF
|
|||
assert_match(/HTTP\/1.0 500 Internal Server Error/, data)
|
||||
end
|
||||
|
||||
def test_force_shutdown_custom_error_message
|
||||
handler = lambda {|err, env, status| [500, {"Content-Type" => "application/json"}, ["{}\n"]]}
|
||||
@server = Puma::Server.new @app, @events, {:lowlevel_error_handler => handler, :force_shutdown_after => 2}
|
||||
|
||||
server_run app: ->(env) do
|
||||
@server.stop
|
||||
sleep 5
|
||||
end
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
|
||||
|
||||
assert_match(/HTTP\/1.0 500 Internal Server Error/, data)
|
||||
assert_match(/Content-Type: application\/json/, data)
|
||||
assert_match(/{}\n$/, data)
|
||||
end
|
||||
|
||||
def test_force_shutdown_error_default
|
||||
@server = Puma::Server.new @app, @events, {:force_shutdown_after => 2}
|
||||
|
||||
server_run app: ->(env) do
|
||||
@server.stop
|
||||
sleep 5
|
||||
end
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
|
||||
|
||||
assert_match(/HTTP\/1.0 503 Service Unavailable/, data)
|
||||
assert_match(/Puma caught this error.+Puma::ThreadPool::ForceShutdown/, data)
|
||||
end
|
||||
|
||||
def test_prints_custom_error
|
||||
re = lambda { |err| [302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']] }
|
||||
@server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re}
|
||||
|
@ -287,6 +319,21 @@ EOF
|
|||
assert_match(/HTTP\/1.0 302 Found/, data)
|
||||
end
|
||||
|
||||
def test_leh_has_status
|
||||
re = lambda { |err, env, status|
|
||||
raise "Cannot find status" unless status
|
||||
[302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']]
|
||||
}
|
||||
|
||||
@server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re}
|
||||
|
||||
server_run app: ->(env) { raise "don't leak me bro" }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
|
||||
|
||||
assert_match(/HTTP\/1.0 302 Found/, data)
|
||||
end
|
||||
|
||||
def test_custom_http_codes_10
|
||||
server_run app: ->(env) { [449, {}, [""]] }
|
||||
|
||||
|
@ -835,4 +882,106 @@ EOF
|
|||
assert_does_not_allow_http_injection(app)
|
||||
end
|
||||
end
|
||||
|
||||
# Perform a server shutdown while requests are pending (one in app-server response, one still sending client request).
|
||||
def shutdown_requests(app_delay: 2, request_delay: 1, post: false, response:, **options)
|
||||
@server = Puma::Server.new @app, @events, options
|
||||
server_run app: ->(_) {
|
||||
sleep app_delay
|
||||
[204, {}, []]
|
||||
}
|
||||
|
||||
s1 = send_http "GET / HTTP/1.1\r\n\r\n"
|
||||
s2 = send_http post ?
|
||||
"POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nhi!" :
|
||||
"GET / HTTP/1.1\r\n"
|
||||
sleep 0.1
|
||||
|
||||
@server.stop
|
||||
sleep request_delay
|
||||
|
||||
s2 << "\r\n"
|
||||
|
||||
assert_match /204/, s1.gets
|
||||
|
||||
assert IO.select([s2], nil, nil, app_delay), 'timeout waiting for response'
|
||||
s2_result = begin
|
||||
s2.gets
|
||||
rescue Errno::ECONNABORTED, Errno::ECONNRESET
|
||||
# Some platforms raise errors instead of returning a response/EOF when a TCP connection is aborted.
|
||||
post ? '408' : nil
|
||||
end
|
||||
|
||||
if response
|
||||
assert_match response, s2_result
|
||||
else
|
||||
assert_nil s2_result
|
||||
end
|
||||
end
|
||||
|
||||
# Shutdown should allow pending requests to complete.
|
||||
def test_shutdown_requests
|
||||
shutdown_requests response: /204/
|
||||
shutdown_requests response: /204/, queue_requests: false
|
||||
end
|
||||
|
||||
# Requests stuck longer than `first_data_timeout` should have connection closed (408 w/pending POST body).
|
||||
def test_shutdown_data_timeout
|
||||
shutdown_requests request_delay: 3, first_data_timeout: 2, response: nil
|
||||
shutdown_requests request_delay: 3, first_data_timeout: 2, response: nil, queue_requests: false
|
||||
shutdown_requests request_delay: 3, first_data_timeout: 2, response: /408/, post: true
|
||||
end
|
||||
|
||||
# Requests still pending after `force_shutdown_after` should have connection closed (408 w/pending POST body).
|
||||
def test_force_shutdown
|
||||
shutdown_requests request_delay: 4, response: nil, force_shutdown_after: 1
|
||||
shutdown_requests request_delay: 4, response: nil, force_shutdown_after: 1, queue_requests: false
|
||||
shutdown_requests request_delay: 4, response: /408/, force_shutdown_after: 1, post: true
|
||||
end
|
||||
|
||||
# App-responses still pending during `force_shutdown_after` should return 503
|
||||
# (uncaught Puma::ThreadPool::ForceShutdown exception).
|
||||
def test_force_shutdown_app
|
||||
shutdown_requests app_delay: 3, response: /503/, force_shutdown_after: 1
|
||||
shutdown_requests app_delay: 3, response: /503/, force_shutdown_after: 1, queue_requests: false
|
||||
end
|
||||
|
||||
def test_http11_connection_header_queue
|
||||
server_run app: ->(_) { [200, {}, [""]] }
|
||||
|
||||
sock = send_http "GET / HTTP/1.1\r\n\r\n"
|
||||
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], header(sock)
|
||||
|
||||
sock << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
|
||||
assert_equal ["HTTP/1.1 200 OK", "Connection: close", "Content-Length: 0"], header(sock)
|
||||
|
||||
sock.close
|
||||
end
|
||||
|
||||
def test_http10_connection_header_queue
|
||||
server_run app: ->(_) { [200, {}, [""]] }
|
||||
|
||||
sock = send_http "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
|
||||
assert_equal ["HTTP/1.0 200 OK", "Connection: Keep-Alive", "Content-Length: 0"], header(sock)
|
||||
|
||||
sock << "GET / HTTP/1.0\r\n\r\n"
|
||||
assert_equal ["HTTP/1.0 200 OK", "Content-Length: 0"], header(sock)
|
||||
sock.close
|
||||
end
|
||||
|
||||
def test_http11_connection_header_no_queue
|
||||
@server = Puma::Server.new @app, @events, queue_requests: false
|
||||
server_run app: ->(_) { [200, {}, [""]] }
|
||||
sock = send_http "GET / HTTP/1.1\r\n\r\n"
|
||||
assert_equal ["HTTP/1.1 200 OK", "Connection: close", "Content-Length: 0"], header(sock)
|
||||
sock.close
|
||||
end
|
||||
|
||||
def test_http10_connection_header_no_queue
|
||||
@server = Puma::Server.new @app, @events, queue_requests: false
|
||||
server_run app: ->(_) { [200, {}, [""]] }
|
||||
sock = send_http "GET / HTTP/1.0\r\n\r\n"
|
||||
assert_equal ["HTTP/1.0 200 OK", "Content-Length: 0"], header(sock)
|
||||
sock.close
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require_relative "helper"
|
||||
require "puma/minissl"
|
||||
require "puma/puma_http11"
|
||||
require "puma/events"
|
||||
require "net/http"
|
||||
|
||||
#———————————————————————————————————————————————————————————————————————————————
|
||||
# NOTE: ALL TESTS BYPASSED IF DISABLE_SSL IS TRUE
|
||||
|
@ -21,7 +23,7 @@ DISABLE_SSL = begin
|
|||
Puma::MiniSSL.check
|
||||
# net/http (loaded in helper) does not necessarily load OpenSSL
|
||||
require "openssl" unless Object.const_defined? :OpenSSL
|
||||
puts "", RUBY_DESCRIPTION,
|
||||
puts "", RUBY_DESCRIPTION, "RUBYOPT: #{ENV['RUBYOPT']}",
|
||||
" Puma::MiniSSL OpenSSL",
|
||||
"OPENSSL_LIBRARY_VERSION: #{Puma::MiniSSL::OPENSSL_LIBRARY_VERSION.ljust 32}#{OpenSSL::OPENSSL_LIBRARY_VERSION}",
|
||||
" OPENSSL_VERSION: #{Puma::MiniSSL::OPENSSL_VERSION.ljust 32}#{OpenSSL::OPENSSL_VERSION}", ""
|
||||
|
@ -164,7 +166,7 @@ class TestPumaServerSSL < Minitest::Test
|
|||
skip("TLSv1 protocol is unavailable") if Puma::MiniSSL::OPENSSL_NO_TLS1
|
||||
start_server { |ctx| ctx.no_tlsv1 = true }
|
||||
|
||||
if @http.respond_to? :max_version=
|
||||
if OpenSSL::SSL::SSLContext.private_instance_methods(false).include?(:set_minmax_proto_version)
|
||||
@http.max_version = :TLS1
|
||||
else
|
||||
@http.ssl_version = :TLSv1
|
||||
|
@ -184,7 +186,7 @@ class TestPumaServerSSL < Minitest::Test
|
|||
def test_tls_v1_1_rejection
|
||||
start_server { |ctx| ctx.no_tlsv1_1 = true }
|
||||
|
||||
if @http.respond_to? :max_version=
|
||||
if OpenSSL::SSL::SSLContext.private_instance_methods(false).include?(:set_minmax_proto_version)
|
||||
@http.max_version = :TLS1_1
|
||||
else
|
||||
@http.ssl_version = :TLSv1_1
|
||||
|
|
|
@ -22,13 +22,6 @@ class TestPumaControlCli < TestConfigFileBase
|
|||
@ready.close
|
||||
end
|
||||
|
||||
def find_open_port
|
||||
server = TCPServer.new("127.0.0.1", 0)
|
||||
server.addr[1]
|
||||
ensure
|
||||
server.close
|
||||
end
|
||||
|
||||
def with_config_file(path_to_config, port)
|
||||
path = Pathname.new(path_to_config)
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
|
@ -120,7 +113,7 @@ class TestPumaControlCli < TestConfigFileBase
|
|||
|
||||
def test_control_url_and_status
|
||||
host = "127.0.0.1"
|
||||
port = find_open_port
|
||||
port = UniquePort.call
|
||||
url = "tcp://#{host}:#{port}/"
|
||||
|
||||
opts = [
|
||||
|
@ -149,8 +142,9 @@ class TestPumaControlCli < TestConfigFileBase
|
|||
end
|
||||
|
||||
def test_control_ssl
|
||||
skip_on :jruby # Hanging on JRuby, TODO fix
|
||||
host = "127.0.0.1"
|
||||
port = find_open_port
|
||||
port = UniquePort.call
|
||||
url = "ssl://#{host}:#{port}?#{ssl_query}"
|
||||
|
||||
opts = [
|
||||
|
|
|
@ -26,16 +26,14 @@ class TestPathHandler < Minitest::Test
|
|||
|
||||
@launcher = nil
|
||||
thread = Thread.new do
|
||||
Rack::Handler::Puma.run(app, options) do |s, p|
|
||||
Rack::Handler::Puma.run(app, **options) do |s, p|
|
||||
@launcher = s
|
||||
end
|
||||
end
|
||||
|
||||
# Wait for launcher to boot
|
||||
Timeout.timeout(10) do
|
||||
until @launcher
|
||||
sleep 1
|
||||
end
|
||||
sleep 1 until @launcher
|
||||
end
|
||||
sleep 1
|
||||
|
||||
|
@ -227,6 +225,13 @@ class TestUserSuppliedOptionsIsNotPresent < Minitest::Test
|
|||
end
|
||||
end
|
||||
|
||||
def test_default_log_request_when_no_config_file
|
||||
conf = Rack::Handler::Puma.config(->{}, @options)
|
||||
conf.load
|
||||
|
||||
assert_equal false, conf.options[:log_requests]
|
||||
end
|
||||
|
||||
def test_file_log_requests_wins_over_default_config
|
||||
file_log_requests_config = true
|
||||
|
||||
|
@ -240,9 +245,7 @@ class TestUserSuppliedOptionsIsNotPresent < Minitest::Test
|
|||
assert_equal file_log_requests_config, conf.options[:log_requests]
|
||||
end
|
||||
|
||||
|
||||
def test_user_log_requests_wins_over_file_config
|
||||
file_log_requests_config = true
|
||||
user_log_requests_config = false
|
||||
|
||||
@options[:log_requests] = user_log_requests_config
|
||||
|
|
107
test/test_redirect_io.rb
Normal file
107
test/test_redirect_io.rb
Normal file
|
@ -0,0 +1,107 @@
|
|||
require_relative "helper"
|
||||
require_relative "helpers/integration"
|
||||
|
||||
class TestRedirectIO < TestIntegration
|
||||
parallelize_me!
|
||||
|
||||
def setup
|
||||
super
|
||||
|
||||
# Keep the Tempfile instances alive to avoid being GC'd
|
||||
@out_file = Tempfile.new('puma-out')
|
||||
@err_file = Tempfile.new('puma-err')
|
||||
@out_file_path = @out_file.path
|
||||
@err_file_path = @err_file.path
|
||||
end
|
||||
|
||||
def teardown
|
||||
super
|
||||
|
||||
paths = [@out_file_path, @err_file_path, @old_out_file_path, @old_err_file_path].compact
|
||||
File.unlink(*paths)
|
||||
@out_file = nil
|
||||
@err_file = nil
|
||||
end
|
||||
|
||||
def test_sighup_redirects_io_single
|
||||
skip_on :jruby # Server isn't coming up in CI, TODO Fix
|
||||
skip_unless_signal_exist? :HUP
|
||||
|
||||
cli_args = [
|
||||
'--redirect-stdout', @out_file_path,
|
||||
'--redirect-stderr', @err_file_path,
|
||||
'test/rackup/hello.ru'
|
||||
]
|
||||
cli_server cli_args.join ' '
|
||||
|
||||
wait_until_file_has_content @out_file_path
|
||||
assert_match 'puma startup', File.read(@out_file_path)
|
||||
|
||||
wait_until_file_has_content @err_file_path
|
||||
assert_match 'puma startup', File.read(@err_file_path)
|
||||
|
||||
log_rotate_output_files
|
||||
|
||||
Process.kill :HUP, @server.pid
|
||||
|
||||
wait_until_file_has_content @out_file_path
|
||||
assert_match 'puma startup', File.read(@out_file_path)
|
||||
|
||||
wait_until_file_has_content @err_file_path
|
||||
assert_match 'puma startup', File.read(@err_file_path)
|
||||
end
|
||||
|
||||
def test_sighup_redirects_io_cluster
|
||||
skip NO_FORK_MSG unless HAS_FORK
|
||||
skip_unless_signal_exist? :HUP
|
||||
|
||||
cli_args = [
|
||||
'-w', '1',
|
||||
'--redirect-stdout', @out_file_path,
|
||||
'--redirect-stderr', @err_file_path,
|
||||
'test/rackup/hello.ru'
|
||||
]
|
||||
cli_server cli_args.join ' '
|
||||
|
||||
wait_until_file_has_content @out_file_path
|
||||
assert_match 'puma startup', File.read(@out_file_path)
|
||||
|
||||
wait_until_file_has_content @err_file_path
|
||||
assert_match 'puma startup', File.read(@err_file_path)
|
||||
|
||||
log_rotate_output_files
|
||||
|
||||
Process.kill :HUP, @server.pid
|
||||
|
||||
wait_until_file_has_content @out_file_path
|
||||
assert_match 'puma startup', File.read(@out_file_path)
|
||||
|
||||
wait_until_file_has_content @err_file_path
|
||||
assert_match 'puma startup', File.read(@err_file_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def log_rotate_output_files
|
||||
# rename both files to .old
|
||||
@old_out_file_path = "#{@out_file_path}.old"
|
||||
@old_err_file_path = "#{@err_file_path}.old"
|
||||
File.rename @out_file_path, @old_out_file_path
|
||||
File.rename @err_file_path, @old_err_file_path
|
||||
|
||||
File.new(@out_file_path, File::CREAT).close
|
||||
File.new(@err_file_path, File::CREAT).close
|
||||
end
|
||||
|
||||
def wait_until_file_has_content(path)
|
||||
File.open(path) do |file|
|
||||
begin
|
||||
file.read_nonblock 1
|
||||
file.seek 0
|
||||
rescue EOFError
|
||||
sleep 0.1
|
||||
retry
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
require_relative "helper"
|
||||
|
||||
require "puma/tcp_logger"
|
||||
|
||||
class TestTCPLogger < Minitest::Test
|
||||
|
||||
def setup
|
||||
@events = Puma::Events.new STDOUT, STDERR
|
||||
@server = Puma::Server.new nil, @events
|
||||
|
||||
@server.app = proc { |env, socket|}
|
||||
@server.tcp_mode!
|
||||
|
||||
@socket = nil
|
||||
end
|
||||
|
||||
def test_events
|
||||
# in lib/puma/launcher.rb:85
|
||||
# Puma::Events is default tcp_logger for cluster mode
|
||||
logger = Puma::Events.new(STDOUT, STDERR)
|
||||
out, err = capture_subprocess_io do
|
||||
Puma::TCPLogger.new(logger, @server.app).call({}, @socket)
|
||||
end
|
||||
assert_match(/connected/, out)
|
||||
assert_equal('', err)
|
||||
end
|
||||
|
||||
def test_io
|
||||
# in lib/puma/configuration.rb:184
|
||||
# STDOUT is default tcp_logger for single mode
|
||||
logger = STDOUT
|
||||
out, err = capture_subprocess_io do
|
||||
Puma::TCPLogger.new(logger, @server.app).call({}, @socket)
|
||||
end
|
||||
assert_match(/connected/, out)
|
||||
assert_equal('', err)
|
||||
end
|
||||
end
|
|
@ -1,34 +0,0 @@
|
|||
require_relative "helper"
|
||||
|
||||
class TestTCPRack < Minitest::Test
|
||||
|
||||
def setup
|
||||
@port = UniquePort.call
|
||||
@host = "127.0.0.1"
|
||||
|
||||
@events = Puma::Events.new STDOUT, STDERR
|
||||
@server = Puma::Server.new nil, @events
|
||||
end
|
||||
|
||||
def teardown
|
||||
@server.stop(true)
|
||||
end
|
||||
|
||||
def test_passes_the_socket
|
||||
@server.tcp_mode!
|
||||
|
||||
body = "We sell hats for a discount!\n"
|
||||
|
||||
@server.app = proc do |env, socket|
|
||||
socket << body
|
||||
socket.close
|
||||
end
|
||||
|
||||
@server.add_tcp_listener @host, @port
|
||||
@server.run
|
||||
|
||||
sock = TCPSocket.new @host, @port
|
||||
|
||||
assert_equal body, sock.read
|
||||
end
|
||||
end
|
|
@ -16,7 +16,7 @@ class TestThreadPool < Minitest::Test
|
|||
end
|
||||
|
||||
def pause
|
||||
sleep 0.2
|
||||
sleep 1
|
||||
end
|
||||
|
||||
def test_append_spawns
|
||||
|
@ -64,6 +64,7 @@ class TestThreadPool < Minitest::Test
|
|||
end
|
||||
|
||||
def test_trim
|
||||
skip_on :jruby, :truffleruby # Undiagnose thread race. TODO fix
|
||||
pool = new_pool(0, 1) do |work|
|
||||
@work_mutex.synchronize do
|
||||
@work_done.signal
|
||||
|
@ -78,12 +79,15 @@ class TestThreadPool < Minitest::Test
|
|||
end
|
||||
|
||||
pool.trim
|
||||
pool.instance_variable_get(:@workers).first.join
|
||||
# wait/join required here for MRI, JRuby races the access here
|
||||
worker = pool.instance_variable_get(:@workers).first
|
||||
worker.join if worker
|
||||
|
||||
assert_equal 0, pool.spawned
|
||||
end
|
||||
|
||||
def test_trim_leaves_min
|
||||
skip_on :jruby, :truffleruby # Undiagnose thread race. TODO fix
|
||||
pool = new_pool(1, 2) do |work|
|
||||
@work_mutex.synchronize do
|
||||
@work_done.signal
|
||||
|
|
|
@ -6,12 +6,12 @@ class TestPumaUnixSocket < Minitest::Test
|
|||
|
||||
App = lambda { |env| [200, {}, ["Works"]] }
|
||||
|
||||
Path = "test/puma.sock"
|
||||
PATH = "test/puma.sock"
|
||||
|
||||
def setup
|
||||
return unless UNIX_SKT_EXIST
|
||||
@server = Puma::Server.new App
|
||||
@server.add_unix_listener Path
|
||||
@server.add_unix_listener PATH
|
||||
@server.run
|
||||
end
|
||||
|
||||
|
@ -22,7 +22,7 @@ class TestPumaUnixSocket < Minitest::Test
|
|||
|
||||
def test_server
|
||||
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
|
||||
sock = UNIXSocket.new Path
|
||||
sock = UNIXSocket.new PATH
|
||||
|
||||
sock << "GET / HTTP/1.0\r\nHost: blah.com\r\n\r\n"
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
# Puma as a service
|
||||
|
||||
## Upstart
|
||||
|
||||
See `/tools/jungle/upstart` for Ubuntu's upstart scripts.
|
||||
|
||||
## Systemd
|
||||
|
||||
See [/docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md).
|
||||
|
||||
## Init.d
|
||||
|
||||
Deprecatation Warning : `init.d` was replaced by `systemd` since Debian 8 and Ubuntu 16.04, you should look into [/docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md) unless you are on an older OS.
|
||||
|
||||
See `/tools/jungle/init.d` for tools to use with init.d and start-stop-daemon.
|
||||
|
||||
## rc.d
|
||||
|
||||
See `/tools/jungle/rc.d` for FreeBSD's rc.d scripts
|
|
@ -1,61 +0,0 @@
|
|||
# Puma daemon service
|
||||
|
||||
Deprecatation Warning : `init.d` was replaced by `systemd` since Debian 8 and Ubuntu 16.04, you should look into [/docs/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md) unless you are on an older OS.
|
||||
|
||||
Init script to manage multiple Puma servers on the same box using start-stop-daemon.
|
||||
|
||||
## Installation
|
||||
|
||||
# Copy the init script to services directory
|
||||
sudo cp puma /etc/init.d
|
||||
sudo chmod +x /etc/init.d/puma
|
||||
|
||||
# Make it start at boot time.
|
||||
sudo update-rc.d -f puma defaults
|
||||
|
||||
# Copy the Puma runner to an accessible location
|
||||
sudo cp run-puma /usr/local/bin
|
||||
sudo chmod +x /usr/local/bin/run-puma
|
||||
|
||||
# Create an empty configuration file
|
||||
sudo touch /etc/puma.conf
|
||||
|
||||
## Managing the jungle
|
||||
|
||||
Puma apps are held in /etc/puma.conf by default. It's mainly a CSV file and every line represents one app. Here's the syntax:
|
||||
|
||||
app-path,user,config-file-path,log-file-path,environment-variables
|
||||
|
||||
You can add an instance by editing the file or running the following command:
|
||||
|
||||
sudo /etc/init.d/puma add /path/to/app user /path/to/app/config/puma.rb /path/to/app/log/puma.log
|
||||
|
||||
The config and log paths, as well as the environment variables, are optional parameters and default to:
|
||||
|
||||
* config: /path/to/app/*config/puma.rb*
|
||||
* log: /path/to/app/*log/puma.log*
|
||||
* environment: (empty)
|
||||
|
||||
Multiple environment variables need to be separated by a semicolon, e.g.
|
||||
|
||||
FOO=1;BAR=2
|
||||
|
||||
To remove an app, simply delete the line from the config file or run:
|
||||
|
||||
sudo /etc/init.d/puma remove /path/to/app
|
||||
|
||||
The command will make sure the Puma instance stops before removing it from the jungle.
|
||||
|
||||
## Assumptions
|
||||
|
||||
* The script expects a temporary folder named /path/to/app/*tmp/puma* to exist. Create it if it's not there by default.
|
||||
The pid and state files should live there and must be called: *tmp/puma/pid* and *tmp/puma/state*.
|
||||
You can change those if you want but you'll have to adapt the script for it to work.
|
||||
|
||||
* Here's what a minimal app's config file should have:
|
||||
|
||||
```
|
||||
pidfile "/path/to/app/tmp/puma/pid"
|
||||
state_path "/path/to/app/tmp/puma/state"
|
||||
activate_control_app
|
||||
```
|
|
@ -1,421 +0,0 @@
|
|||
#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: puma
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Puma web server
|
||||
# Description: A ruby web server built for concurrency http://puma.io
|
||||
# initscript to be placed in /etc/init.d.
|
||||
### END INIT INFO
|
||||
|
||||
# Author: Darío Javier Cravero <dario@exordo.com>
|
||||
#
|
||||
# Do NOT "set -e"
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/usr/local/bin:/usr/local/sbin/:/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="Puma rack web server"
|
||||
NAME=puma
|
||||
DAEMON=$NAME
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
CONFIG=/etc/puma.conf
|
||||
JUNGLE=`cat $CONFIG`
|
||||
RUNPUMA=/usr/local/bin/run-puma
|
||||
USE_LOCAL_BUNDLE=0
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
#
|
||||
# Function that starts the jungle
|
||||
#
|
||||
do_start() {
|
||||
log_daemon_msg "=> Running the jungle..."
|
||||
for i in $JUNGLE; do
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
do_start_one $dir
|
||||
done
|
||||
}
|
||||
|
||||
do_start_one() {
|
||||
PIDFILE=$1/tmp/puma/pid
|
||||
if [ -e $PIDFILE ]; then
|
||||
PID=`cat $PIDFILE`
|
||||
# If the puma is running, restart it, otherwise run it.
|
||||
if ps -p $PID > /dev/null; then
|
||||
do_restart_one $1
|
||||
else
|
||||
do_start_one_do $1
|
||||
fi
|
||||
else
|
||||
do_start_one_do $1
|
||||
fi
|
||||
}
|
||||
|
||||
do_start_one_do() {
|
||||
i=`grep $1 $CONFIG`
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
user=`echo $i | cut -d , -f 2`
|
||||
config_file=`echo $i | cut -d , -f 3`
|
||||
if [ "$config_file" = "" ]; then
|
||||
config_file="$dir/config/puma.rb"
|
||||
fi
|
||||
log_file=`echo $i | cut -d , -f 4`
|
||||
if [ "$log_file" = "" ]; then
|
||||
log_file="$dir/log/puma.log"
|
||||
fi
|
||||
environment=`echo $i | cut -d , -f 5`
|
||||
|
||||
log_daemon_msg "--> Woke up puma $dir"
|
||||
log_daemon_msg "user $user"
|
||||
log_daemon_msg "log to $log_file"
|
||||
|
||||
if [ ! -z "$environment" ]; then
|
||||
for e in $(echo "$environment" | tr ';' '\n'); do
|
||||
log_daemon_msg "environment $e"
|
||||
v=${e%%\=*} ; eval "$e" ; export $v
|
||||
done
|
||||
fi
|
||||
|
||||
start-stop-daemon --verbose --start --chdir $dir --chuid $user --background --exec $RUNPUMA -- $dir $config_file $log_file
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the jungle
|
||||
#
|
||||
do_stop() {
|
||||
log_daemon_msg "=> Putting all the beasts to bed..."
|
||||
for i in $JUNGLE; do
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
do_stop_one $dir
|
||||
done
|
||||
}
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop_one() {
|
||||
log_daemon_msg "--> Stopping $1"
|
||||
PIDFILE=$1/tmp/puma/pid
|
||||
STATEFILE=$1/tmp/puma/state
|
||||
if [ -e $PIDFILE ]; then
|
||||
PID=`cat $PIDFILE`
|
||||
if ps -p $PID > /dev/null; then
|
||||
log_daemon_msg "---> About to kill PID `cat $PIDFILE`"
|
||||
if [ "$USE_LOCAL_BUNDLE" -eq 1 ]; then
|
||||
cd $1 && bundle exec pumactl --state $STATEFILE stop
|
||||
else
|
||||
pumactl --state $STATEFILE stop
|
||||
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..."
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Function that restarts the jungle
|
||||
#
|
||||
do_restart() {
|
||||
for i in $JUNGLE; do
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
do_restart_one $dir
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGUSR2 to the daemon/service
|
||||
#
|
||||
do_restart_one() {
|
||||
PIDFILE=$1/tmp/puma/pid
|
||||
|
||||
if [ -e $PIDFILE ]; then
|
||||
log_daemon_msg "--> About to restart puma $1"
|
||||
kill -s USR2 `cat $PIDFILE`
|
||||
# TODO Check if process exist
|
||||
else
|
||||
log_daemon_msg "--> Your puma was never playing... Let's get it out there first"
|
||||
do_start_one $1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Function that phased restarts the jungle
|
||||
#
|
||||
do_phased_restart() {
|
||||
for i in $JUNGLE; do
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
do_phased_restart_one $dir
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGUSR1 to the daemon/service
|
||||
#
|
||||
do_phased_restart_one() {
|
||||
PIDFILE=$1/tmp/puma/pid
|
||||
|
||||
if [ -e $PIDFILE ]; then
|
||||
log_daemon_msg "--> About to restart puma $1"
|
||||
kill -s USR1 `cat $PIDFILE`
|
||||
# TODO Check if process exist
|
||||
else
|
||||
log_daemon_msg "--> Your puma was never playing... Let's get it out there first"
|
||||
do_start_one $1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Function that statuss the jungle
|
||||
#
|
||||
do_status() {
|
||||
for i in $JUNGLE; do
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
do_status_one $dir
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGUSR2 to the daemon/service
|
||||
#
|
||||
do_status_one() {
|
||||
PIDFILE=$1/tmp/puma/pid
|
||||
i=`grep $1 $CONFIG`
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
|
||||
if [ -e $PIDFILE ]; then
|
||||
log_daemon_msg "--> About to status puma $1"
|
||||
if [ "$USE_LOCAL_BUNDLE" -eq 1 ]; then
|
||||
cd $1 && bundle exec pumactl --state $dir/tmp/puma/state stats
|
||||
else
|
||||
pumactl --state $dir/tmp/puma/state stats
|
||||
fi
|
||||
# kill -s USR2 `cat $PIDFILE`
|
||||
# TODO Check if process exist
|
||||
else
|
||||
log_daemon_msg "--> $1 isn't there :(..."
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
do_add() {
|
||||
str=""
|
||||
# App's directory
|
||||
if [ -d "$1" ]; then
|
||||
if [ "`grep -c "^$1" $CONFIG`" -eq 0 ]; then
|
||||
str=$1
|
||||
else
|
||||
echo "The app is already being managed. Remove it if you want to update its config."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "The directory $1 doesn't exist."
|
||||
exit 1
|
||||
fi
|
||||
# User to run it as
|
||||
if [ "`grep -c "^$2:" /etc/passwd`" -eq 0 ]; then
|
||||
echo "The user $2 doesn't exist."
|
||||
exit 1
|
||||
else
|
||||
str="$str,$2"
|
||||
fi
|
||||
# Config file
|
||||
if [ "$3" != "" ]; then
|
||||
if [ -e $3 ]; then
|
||||
str="$str,$3"
|
||||
else
|
||||
echo "The config file $3 doesn't exist."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
# Log file
|
||||
if [ "$4" != "" ]; then
|
||||
str="$str,$4"
|
||||
fi
|
||||
|
||||
# Environment variables
|
||||
if [ "$5" != "" ]; then
|
||||
str="$str,$5"
|
||||
fi
|
||||
|
||||
# Add it to the jungle
|
||||
echo $str >> $CONFIG
|
||||
log_daemon_msg "Added a Puma to the jungle: $str. You still have to start it though."
|
||||
}
|
||||
|
||||
do_remove() {
|
||||
if [ "`grep -c "^$1" $CONFIG`" -eq 0 ]; then
|
||||
echo "There's no app $1 to remove."
|
||||
else
|
||||
# Stop it first.
|
||||
do_stop_one $1
|
||||
# Remove it from the config.
|
||||
sed -i "\\:^$1:d" $CONFIG
|
||||
log_daemon_msg "Removed a Puma from the jungle: $1."
|
||||
fi
|
||||
}
|
||||
|
||||
config_bundler() {
|
||||
HOME="$(eval echo ~$(id -un))"
|
||||
|
||||
if [ -d "$1/.rbenv/bin" ]; then
|
||||
PATH="$1/.rbenv/bin:$1/.rbenv/shims:$1"
|
||||
eval "$(rbenv init -)"
|
||||
USE_LOCAL_BUNDLE=1
|
||||
return 0
|
||||
|
||||
elif [ -d "/usr/local/rbenv/bin" ]; then
|
||||
PATH="/usr/local/rbenv/bin:/usr/local/rbenv/shims:$PATH"
|
||||
eval "$(rbenv init -)"
|
||||
USE_LOCAL_BUNDLE=1
|
||||
return 0
|
||||
|
||||
elif [ -d "$HOME/.rbenv/bin" ]; then
|
||||
PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH"
|
||||
eval "$(rbenv init -)"
|
||||
USE_LOCAL_BUNDLE=1
|
||||
return 0
|
||||
|
||||
# TODO: test rvm
|
||||
# elif [ -f /etc/profile.d/rvm.sh ]; then
|
||||
# source /etc/profile.d/rvm.sh
|
||||
# elif [ -f /usr/local/rvm/scripts/rvm ]; then
|
||||
# source /etc/profile.d/rvm.sh
|
||||
# elif [ -f "$HOME/.rvm/scripts/rvm" ]; then
|
||||
# source "$HOME/.rvm/scripts/rvm"
|
||||
# TODO: don't know what to do with chruby
|
||||
# elif [ -f /usr/local/share/chruby/chruby.sh ]; then
|
||||
# source /usr/local/share/chruby/chruby.sh
|
||||
# if [ -f /usr/local/share/chruby/auto.sh ]; then
|
||||
# source /usr/local/share/chruby/auto.sh
|
||||
# fi
|
||||
# if you aren't using auto, set your version here
|
||||
# chruby 2.0.0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
config_bundler
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||
if [ "$#" -eq 1 ]; then
|
||||
do_start
|
||||
else
|
||||
i=`grep $2 $CONFIG`
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
do_start_one $dir
|
||||
fi
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
if [ "$#" -eq 1 ]; then
|
||||
do_stop
|
||||
else
|
||||
i=`grep $2 $CONFIG`
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
do_stop_one $dir
|
||||
fi
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
# TODO Implement.
|
||||
log_daemon_msg "Status $DESC" "$NAME"
|
||||
if [ "$#" -eq 1 ]; then
|
||||
do_status
|
||||
else
|
||||
i=`grep $2 $CONFIG`
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
do_status_one $dir
|
||||
fi
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
restart)
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
if [ "$#" -eq 1 ]; then
|
||||
do_restart
|
||||
else
|
||||
i=`grep $2 $CONFIG`
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
do_restart_one $dir
|
||||
fi
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
phased-restart)
|
||||
log_daemon_msg "Restarting (phased) $DESC" "$NAME"
|
||||
if [ "$#" -eq 1 ]; then
|
||||
do_phased_restart
|
||||
else
|
||||
i=`grep $2 $CONFIG`
|
||||
dir=`echo $i | cut -d , -f 1`
|
||||
do_phased_restart_one $dir
|
||||
fi
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
add)
|
||||
if [ "$#" -lt 3 ]; then
|
||||
echo "Please, specify the app's directory and the user that will run it at least."
|
||||
echo " Usage: $SCRIPTNAME add /path/to/app user /path/to/app/config/puma.rb /path/to/app/config/log/puma.log"
|
||||
echo " config and log are optionals."
|
||||
exit 1
|
||||
else
|
||||
do_add $2 $3 $4 $5
|
||||
fi
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
remove)
|
||||
if [ "$#" -lt 2 ]; then
|
||||
echo "Please, specify the app's directory to remove."
|
||||
exit 1
|
||||
else
|
||||
do_remove $2
|
||||
fi
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage:" >&2
|
||||
echo " Run the jungle: $SCRIPTNAME {start|stop|status|restart|phased-restart}" >&2
|
||||
echo " Add a Puma: $SCRIPTNAME add /path/to/app user /path/to/app/config/puma.rb /path/to/app/config/log/puma.log"
|
||||
echo " config and log are optionals."
|
||||
echo " Remove a Puma: $SCRIPTNAME remove /path/to/app"
|
||||
echo " On a Puma: $SCRIPTNAME {start|stop|status|restart|phased-restart} PUMA-NAME" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
:
|
|
@ -1,18 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# on system boot, and root have no rbenv installed,
|
||||
# after start-stop-daemon switched to current user, we have to init rbenv
|
||||
if [ -d "$HOME/.rbenv/bin" ]; then
|
||||
PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH"
|
||||
eval "$(rbenv init -)"
|
||||
elif [ -d "/usr/local/rbenv/bin" ]; then
|
||||
PATH="/usr/local/rbenv/bin:/usr/local/rbenv/shims:$PATH"
|
||||
eval "$(rbenv init -)"
|
||||
elif [ -f /usr/local/rvm/scripts/rvm ]; then
|
||||
source /etc/profile.d/rvm.sh
|
||||
elif [ -f "$HOME/.rvm/scripts/rvm" ]; then
|
||||
source "$HOME/.rvm/scripts/rvm"
|
||||
fi
|
||||
|
||||
app=$1; config=$2; log=$3;
|
||||
cd $app && exec bundle exec puma -C $config >> $log 2>&1
|
Loading…
Add table
Reference in a new issue