pax_global_header00006660000000000000000000000064136262647430014527gustar00rootroot0000000000000052 comment=f809e6b7aa3083afb8da5eb54bdd45fc391d1ba1 puma-3.12.4/000077500000000000000000000000001362626474300125605ustar00rootroot00000000000000puma-3.12.4/.gitattributes000066400000000000000000000001041362626474300154460ustar00rootroot00000000000000# Auto detect text files and perform LF normalization * text eol=lf puma-3.12.4/.github/000077500000000000000000000000001362626474300141205ustar00rootroot00000000000000puma-3.12.4/.github/issue_template.md000066400000000000000000000003571362626474300174720ustar00rootroot00000000000000### Steps to reproduce 1) ... 2) ... 3) ... ### Expected behavior Tell us what should happen ... ### Actual behavior Tell us what happens instead ... ### System configuration **Ruby version**: **Rails version**: **Puma version**: puma-3.12.4/.gitignore000066400000000000000000000004701362626474300145510ustar00rootroot00000000000000scratch/ *.bundle *.log *.o *.so *.jar *.rbc doc log pkg tmp t/ .rbx/ Gemfile.lock .idea/ /test/test_puma.state /test/test_server.sock /test/test_control.sock # windows local build artifacts /win_gem_test/shared/ /win_gem_test/packages/ /win_gem_test/test_logs/ /Rakefile_wintest *.gem /lib/puma/puma_http11.rb puma-3.12.4/.rubocop.yml000066400000000000000000000015131362626474300150320ustar00rootroot00000000000000AllCops: DisabledByDefault: true TargetRubyVersion: 2.2 DisplayCopNames: true StyleGuideCopsOnly: false Exclude: - 'tmp/**/*' - 'vendor/**/*' - 'Rakefile' Layout/SpaceAfterColon: Enabled: true Layout/SpaceAroundKeyword: Enabled: true Layout/SpaceBeforeBlockBraces: EnforcedStyleForEmptyBraces: no_space Enabled: true Layout/SpaceBeforeFirstArg: Enabled: true Layout/SpaceInsideParens: Enabled: true Layout/Tab: Enabled: true Layout/TrailingBlankLines: Enabled: true Layout/TrailingWhitespace: Enabled: true Lint/Debugger: Enabled: true Naming/MethodName: Enabled: true EnforcedStyle: snake_case Exclude: - 'test/**/**' Naming/VariableName: Enabled: true Style/MethodDefParentheses: Enabled: true Style/TrailingCommaInArguments: Enabled: true Performance: Enabled: true puma-3.12.4/.rubocop_todo.yml000066400000000000000000000024321362626474300160600ustar00rootroot00000000000000inherit_from: "./.rubocop.yml" # 29 offenses Layout/SpaceAroundOperators: Enabled: true # 21 offenses Layout/SpaceInsideBlockBraces: Enabled: true # 16 offenses Layout/SpaceAroundEqualsInParameterDefault: Enabled: true EnforcedStyle: no_space # 15 offenses Layout/SpaceInsideHashLiteralBraces: Enabled: true EnforcedStyle: no_space # 8 offenses Layout/EmptyLines: Enabled: true # 4 offenses Layout/EmptyLinesAroundClassBody: Enabled: true Exclude: - 'test/**/*' # 6 offenses Layout/EmptyLinesAroundMethodBody: Enabled: true # 5 offenses Layout/EmptyLinesAroundModuleBody: Enabled: true # 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 Metrics/LineLength: Max: 120 AllowHeredoc: true AllowURI: true URISchemes: - http - 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: truepuma-3.12.4/.travis.yml000066400000000000000000000015021362626474300146670ustar00rootroot00000000000000dist: trusty sudo: false group: beta language: ruby cache: bundler before_install: # rubygems 2.7.8 and greater include bundler - | rv="$(ruby -e 'STDOUT.write RUBY_VERSION')"; if [ "$rv" \< "2.3" ]; then gem update --system 2.7.8 --no-document elif [ "$rv" \< "2.6" ]; then gem update --system --no-document --conservative fi - ruby -v && gem --version && bundle version rvm: - 2.2.10 - 2.3.8 - 2.4.5 - 2.5.3 - ruby-head - jruby-9.2.0.0 matrix: fast_finish: true include: - rvm: ruby-head env: RUBYOPT="--jit" - rvm: 2.3.8 os: osx - rvm: 2.5.3 os: osx - rvm: jruby-head - rvm: rbx-3 allow_failures: - rvm: ruby-head - rvm: ruby-head env: RUBYOPT="--jit" - rvm: jruby-head - rvm: rbx-3 env: global: - TESTOPTS="-v" puma-3.12.4/Gemfile000066400000000000000000000005571362626474300140620ustar00rootroot00000000000000source "https://rubygems.org" gemspec gem "rdoc" gem "rake-compiler" gem "rack", "< 3.0" gem "minitest", "~> 5.11" gem "minitest-retry" gem "jruby-openssl", :platform => "jruby" gem "rubocop", "~> 0.58.0" if %w(2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1).include? RUBY_VERSION gem "stopgap_13632", "~> 1.0", :platforms => ["mri", "mingw", "x64_mingw"] end gem 'm' puma-3.12.4/History.md000066400000000000000000001343331362626474300145520ustar00rootroot00000000000000## Master * x features * x bugfixes ## 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 * Security * Fix: Prevent HTTP Response splitting via CR/LF in header values. CVE-2020-5247. ## 4.3.1 and 3.12.2 / 2019-12-05 * Security * Fix: a poorly-behaved client could use keepalive requests to monopolize Puma's reactor and create a denial of service attack. CVE-2019-16770. ## 3.12.1 / 2019-03-19 * 1 features * Internal strings are frozen (#1649) * 3 bugfixes * Fix chunked ending check (#1607) * Rack handler should use provided default host (#1700) * Better support for detecting runtimes that support `fork` (#1630) ## 3.12.0 / 2018-07-13 * 5 features: * You can now specify which SSL ciphers the server should support, default is unchanged (#1478) * The setting for Puma's `max_threads` is now in `Puma.stats` (#1604) * Pool capacity is now in `Puma.stats` (#1579) * Installs restricted to Ruby 2.2+ (#1506) * `--control` is now deprecated in favor of `--control-url` (#1487) * 2 bugfixes: * Workers will no longer accept more web requests than they have capacity to process. This prevents an issue where one worker would accept lots of requests while starving other workers (#1563) * In a test env puma now emits the stack on an exception (#1557) ## 3.11.4 / 2018-04-12 * 2 features: * Manage puma as a service using rc.d (#1529) * Server stats are now available from a top level method (#1532) * 5 bugfixes: * Fix parsing CLI options (#1482) * Order of stderr and stdout is made before redirecting to a log file (#1511) * Init.d fix of `ps -p` to check if pid exists (#1545) * Early hints bugfix (#1550) * Purge interrupt queue when closing socket fails (#1553) ## 3.11.3 / 2018-03-05 * 3 bugfixes: * Add closed? to MiniSSL::Socket for use in reactor (#1510) * Handle EOFError at the toplevel of the server threads (#1524) (#1507) * Deal with zero sized bodies when using SSL (#1483) ## 3.11.2 / 2018-01-19 * 1 bugfix: * Deal with read\_nonblock returning nil early ## 3.11.1 / 2018-01-18 * 1 bugfix: * Handle read\_nonblock returning nil when the socket close (#1502) ## 3.11.0 / 2017-11-20 * 2 features: * HTTP 103 Early Hints (#1403) * 421/451 status codes now have correct status messages attached (#1435) * 9 bugfixes: * Environment config files (/config/puma/.rb) load correctly (#1340) * Specify windows dependencies correctly (#1434, #1436) * puma/events required in test helper (#1418) * Correct control CLI's option help text (#1416) * Remove a warning for unused variable in mini_ssl (#1409) * Correct pumactl docs argument ordering (#1427) * Fix an uninitialized variable warning in server.rb (#1430) * Fix docs typo/error in Launcher init (#1429) * Deal with leading spaces in RUBYOPT (#1455) * 2 other: * Add docs about internals (#1425, #1452) * Tons of test fixes from @MSP-Greg (#1439, #1442, #1464) ## 3.10.0 / 2017-08-17 * 3 features: * The status server has a new /gc and /gc-status command. (#1384) * The persistent and first data timeouts are now configurable (#1111) * Implemented RFC 2324 (#1392) * 12 bugfixes: * Not really a Puma bug, but @NickolasVashchenko created a gem to workaround a Ruby bug that some users of Puma may be experiencing. See README for more. (#1347) * Fix hangups with SSL and persistent connections. (#1334) * Fix Rails double-binding to a port (#1383) * Fix incorrect thread names (#1368) * Fix issues with /etc/hosts and JRuby where localhost addresses were not correct. (#1318) * Fix compatibility with RUBYOPT="--enable-frozen-string-literal" (#1376) * Fixed some compiler warnings (#1388) * We actually run the integration tests in CI now (#1390) * No longer shipping unnecessary directories in the gemfile (#1391) * If RUBYOPT is nil, we no longer blow up on restart. (#1385) * Correct response to SIGINT (#1377) * Proper exit code returned when we receive a TERM signal (#1337) * 3 refactors: * Various test improvements from @grosser * Rubocop (#1325) * Hoe has been removed (#1395) * 1 known issue: * Socket activation doesn't work in JRuby. Their fault, not ours. (#1367) ## 3.9.1 / 2017-06-03 * 2 bugfixes: * Fixed compatibility with older Bundler versions (#1314) * Some internal test/development cleanup (#1311, #1313) ## 3.9.0 / 2017-06-01 * 2 features: * The ENV is now reset to its original values when Puma restarts via USR1/USR2 (#1260) (MRI only, no JRuby support) * Puma will no longer accept more clients than the maximum number of threads. (#1278) * 9 bugfixes: * Reduce information leakage by preventing HTTP parse errors from writing environment hashes to STDERR (#1306) * Fix SSL/WebSocket compatibility (#1274) * HTTP headers with empty values are no longer omitted from responses. (#1261) * Fix a Rack env key which was set to nil. (#1259) * peercert has been implemented for JRuby (#1248) * Fix port settings when using rails s (#1277, #1290) * Fix compat w/LibreSSL (#1285) * Fix restarting Puma w/symlinks and a new Gemfile (#1282) * Replace Dir.exists? with Dir.exist? (#1294) * 1 known issue: * A bug in MRI 2.2+ can result in IOError: stream closed. See #1206. This issue has existed since at least Puma 3.6, and probably further back. * 1 refactor: * Lots of test fixups from @grosser. ## 3.8.2 / 2017-03-14 * 1 bugfix: * Deal with getsockopt with TCP\_INFO failing for sockets that say they're TCP but aren't really. (#1241) ## 3.8.1 / 2017-03-10 * 1 bugfix: * Remove method call to method that no longer exists (#1239) ## 3.8.0 / 2017-03-09 * 2 bugfixes: * Port from rack handler does not take precedence over config file in Rails 5.1.0.beta2+ and 5.0.1.rc3+ (#1234) * The `tmp/restart.txt` plugin no longer restricts the user from running more than one server from the same folder at a time (#1226) * 1 feature: * Closed clients are aborted to save capacity (#1227) * 1 refactor: * Bundler is no longer a dependency from tests (#1213) ## 3.7.1 / 2017-02-20 * 2 bugfixes: * Fix typo which blew up MiniSSL (#1182) * Stop overriding command-line options with the config file (#1203) ## 3.7.0 / 2017-01-04 * 6 minor features: * Allow rack handler to accept ssl host. (#1129) * Refactor TTOU processing. TTOU now handles multiple signals at once. (#1165) * Pickup any remaining chunk data as the next request. * Prevent short term thread churn - increased auto trim default to 30 seconds. * Raise error when `stdout` or `stderr` is not writable. (#1175) * Add Rack 2.0 support to gemspec. (#1068) * 5 refactors: * Compare host and server name only once per call. (#1091) * Minor refactor on Thread pool (#1088) * Removed a ton of unused constants, variables and files. * Use MRI macros when allocating heap memory * Use hooks for on\_booted event. (#1160) * 14 bugfixes: * Add eof? method to NullIO? (#1169) * Fix Puma startup in provided init.d script (#1061) * Fix default SSL mode back to none. (#1036) * Fixed the issue of @listeners getting nil io (#1120) * Make `get_dh1024` compatible with OpenSSL v1.1.0 (#1178) * More gracefully deal with SSL sessions. Fixes #1002 * Move puma.rb to just autoloads. Fixes #1063 * MiniSSL: Provide write as <<. Fixes #1089 * Prune bundler should inherit fds (#1114) * Replace use of Process.getpgid which does not behave as intended on all platforms (#1110) * Transfer encoding header should be downcased before comparison (#1135) * Use same write log logic for hijacked requests. (#1081) * Fix `uninitialized constant Puma::StateFile` (#1138) * Fix access priorities of each level in LeveledOptions (#1118) * 3 others: * Lots of tests added/fixed/improved. Switched to Minitest from Test::Unit. Big thanks to @frodsan. * Lots of documentation added/improved. * Add license indicators to the HTTP extension. (#1075) ## 3.6.2 / 2016-11-22 * 1 bug fix: * Revert #1118/Fix access priorities of each level in LeveledOptions. This had an unintentional side effect of changing the importance of command line options, such as -p. ## 3.6.1 / 2016-11-21 * 8 bug fixes: * Fix Puma start in init.d script. * Fix default SSL mode back to none. Fixes #1036 * Fixed the issue of @listeners getting nil io, fix rails restart (#1120) * More gracefully deal with SSL sessions. Fixes #1002 * Prevent short term thread churn. * Provide write as <<. Fixes #1089 * Fix access priorities of each level in LeveledOptions - fixes TTIN. * Stub description files updated for init.d. * 2 new project committers: * Nate Berkopec (@nateberkopec) * Richard Schneeman (@schneems) ## 3.6.0 / 2016-07-24 * 12 bug fixes: * Add ability to detect a shutting down server. Fixes #932 * Add support for Expect: 100-continue. Fixes #519 * Check SSLContext better. Fixes #828 * Clarify behavior of '-t num'. Fixes #984 * Don't default to VERIFY_PEER. Fixes #1028 * Don't use ENV['PWD'] on windows. Fixes #1023 * Enlarge the scope of catching app exceptions. Fixes #1027 * Execute background hooks after daemonizing. Fixes #925 * Handle HUP as a stop unless there is IO redirection. Fixes #911 * Implement chunked request handling. Fixes #620 * Just rescue exception to return a 500. Fixes #1027 * Redirect IO in the jruby daemon mode. Fixes #778 ## 3.5.2 / 2016-07-20 * 1 bug fix: * Don't let persistent_timeout be nil * 1 PR merged: * Merge pull request #1021 from benzrf/patch-1 ## 3.5.1 / 2016-07-20 * 1 bug fix: * Be sure to only listen on host:port combos once. Fixes #1022 ## 3.5.0 / 2016-07-18 * 1 minor features: * Allow persistent_timeout to be configured via the dsl. * 9 bug fixes: * Allow a bare % in a query string. Fixes #958 * Explicitly listen on all localhost addresses. Fixes #782 * Fix `TCPLogger` log error in tcp cluster mode. * Fix puma/puma#968 Cannot bind SSL port due to missing verify_mode option * Fix puma/puma#968 Default verify_mode to peer * Log any exceptions in ThreadPool. Fixes #1010 * Silence connection errors in the reactor. Fixes #959 * Tiny fixes in hook documentation for #840 * It should not log requests if we want it to be quiet * 5 doc fixes: * Add How to stop Puma on Heroku using plugins to the example directory * Provide both hot and phased restart in jungle script * Update reference to the instances management script * Update default number of threads * Fix typo in example config * 14 PRs merged: * Merge pull request #1007 from willnet/patch-1 * Merge pull request #1014 from jeznet/patch-1 * Merge pull request #1015 from bf4/patch-1 * Merge pull request #1017 from jorihardman/configurable_persistent_timeout * Merge pull request #954 from jf/master * Merge pull request #955 from jf/add-request-info-to-standard-error-rescue * Merge pull request #956 from maxkwallace/master * Merge pull request #960 from kmayer/kmayer-plugins-heroku-restart * Merge pull request #969 from frankwong15/master * Merge pull request #970 from willnet/delete-blank-document * Merge pull request #974 from rocketjob/feature/name_threads * Merge pull request #977 from snow/master * Merge pull request #981 from zach-chai/patch-1 * Merge pull request #993 from scorix/master ## 3.4.0 / 2016-04-07 * 2 minor features: * Add ability to force threads to stop on shutdown. Fixes #938 * Detect and commit seppuku when fork(2) fails. Fixes #529 * 3 unknowns: * Ignore errors trying to update the backport tables. Fixes #788 * Invoke the lowlevel_error in more places to allow for exception tracking. Fixes #894 * Update the query string when an absolute URI is used. Fixes #937 * 5 doc fixes: * Add Process Monitors section to top-level README * Better document the hooks. Fixes #840 * docs/system.md sample config refinements and elaborations * Fix typos at couple of places. * Cleanup warnings * 3 PRs merged: * Merge pull request #945 from dekellum/systemd-docs-refined * Merge pull request #946 from vipulnsward/rm-pid * Merge pull request #947 from vipulnsward/housekeeping-typos ## 3.3.0 / 2016-04-05 * 2 minor features: * Allow overriding options of Configuration object * Rename to inherit_ssl_listener like inherit_tcp|unix * 2 doc fixes: * Add docs/systemd.md (with socket activation sub-section) * Document UNIX signals with cluster on README.md * 3 PRs merged: * Merge pull request #936 from prathamesh-sonpatki/allow-overriding-config-options * Merge pull request #940 from kyledrake/signalsdoc * Merge pull request #942 from dekellum/socket-activate-improve ## 3.2.0 / 2016-03-20 * 1 deprecation removal: * Delete capistrano.rb * 3 bug fixes: * Detect gems.rb as well as Gemfile * Simplify and fix logic for directory to use when restarting for all phases * Speed up phased-restart start * 2 PRs merged: * Merge pull request #927 from jlecour/gemfile_variants * Merge pull request #931 from joneslee85/patch-10 ## 3.1.1 / 2016-03-17 * 4 bug fixes: * Disable USR1 usage on JRuby * Fixes #922 - Correctly define file encoding as UTF-8 * Set a more explicit SERVER_SOFTWARE Rack variable * Show RUBY_ENGINE_VERSION if available. Fixes #923 * 3 PRs merged: * Merge pull request #912 from tricknotes/fix-allow-failures-in-travis-yml * Merge pull request #921 from swrobel/patch-1 * Merge pull request #924 from tbrisker/patch-1 ## 3.1.0 / 2016-03-05 * 1 minor feature: * Add 'import' directive to config file. Fixes #916 * 5 bug fixes: * Add 'fetch' to options. Fixes #913 * Fix jruby daemonization. Fixes #918 * Recreate the proper args manually. Fixes #910 * Require 'time' to get iso8601. Fixes #914 ## 3.0.2 / 2016-02-26 * 5 bug fixes: * Fix 'undefined local variable or method `pid` for #' when execute pumactl with `--pid` option. * Fix 'undefined method `windows?` for Puma:Module' when execute pumactl. * Harden tmp_restart against errors related to the restart file * Make `plugin :tmp_restart` behavior correct in Windows. * fix uninitialized constant Puma::ControlCLI::StateFile * 3 PRs merged: * Merge pull request #901 from mitto/fix-pumactl-uninitialized-constant-statefile * Merge pull request #902 from corrupt952/fix_undefined_method_and_variable_when_execute_pumactl * Merge pull request #905 from Eric-Guo/master ## 3.0.1 / 2016-02-25 * 1 bug fix: * Removed the experimental support for async.callback as it broke websockets entirely. Seems no server has both hijack and async.callback and thus faye is totally confused what to do and doesn't work. ## 3.0.0 / 2016-02-25 * 2 major changes: * Ruby pre-2.0 is no longer supported. We'll do our best to not add features that break those rubies but will no longer be testing with them. * Don't log requests by default. Fixes #852 * 2 major features: * Plugin support! Plugins can interact with configuration as well as provide augment server functionality! * Experimental env['async.callback'] support * 4 minor features: * Listen to unix socket with provided backlog if any * Improves the clustered stats to report worker stats * Pass the env to the lowlevel_error handler. Fixes #854 * Treat path-like hosts as unix sockets. Fixes #824 * 5 bug fixes: * Clean thread locals when using keepalive. Fixes #823 * Cleanup compiler warnings. Fixes #815 * Expose closed? for use by the reactor. Fixes #835 * Move signal handlers to separate method to prevent space leak. Fixes #798 * Signal not full on worker exit #876 * 5 doc fixes: * Update README.md with various grammar fixes * Use newest version of Minitest * Add directory configuration docs, fix typo [ci skip] * Remove old COPYING notice. Fixes #849 * 10 merged PRs: * Merge pull request #871 from deepj/travis * Merge pull request #874 from wallclockbuilder/master * Merge pull request #883 from dadah89/igor/trim_only_worker * Merge pull request #884 from uistudio/async-callback * Merge pull request #888 from mlarraz/tick_minitest * Merge pull request #890 from todd/directory_docs * Merge pull request #891 from ctaintor/improve_clustered_status * Merge pull request #893 from spastorino/add_missing_require * Merge pull request #897 from zendesk/master * Merge pull request #899 from kch/kch-readme-fixes ## 2.16.0 / 2016-01-27 * 7 minor features: * Add 'set_remote_address' config option * Allow to run puma in silent mode * Expose cli options in DSL * Support passing JRuby keystore info in ssl_bind DSL * Allow umask for unix:/// style control urls * Expose `old_worker_count` in stats url * Support TLS client auth (verify_mode) in jruby * 7 bug fixes: * Don't persist before_fork hook in state file * Reload bundler before pulling in rack. Fixes #859 * Remove NEWRELIC_DISPATCHER env variable * Cleanup C code * Use Timeout.timeout instead of Object.timeout * Make phased restarts faster * Ignore the case of certain headers, because HTTP * 1 doc changes: * Test against the latest Ruby 2.1, 2.2, 2.3, head and JRuby 9.0.4.0 on Travis * 12 merged PRs * Merge pull request #822 from kwugirl/remove_NEWRELIC_DISPATCHER * Merge pull request #833 from joemiller/jruby-client-tls-auth * Merge pull request #837 from YuriSolovyov/ssl-keystore-jruby * Merge pull request #839 from mezuka/master * Merge pull request #845 from deepj/timeout-deprecation * Merge pull request #846 from sriedel/strip_before_fork * Merge pull request #850 from deepj/travis * Merge pull request #853 from Jeffrey6052/patch-1 * Merge pull request #857 from zendesk/faster_phased_restarts * Merge pull request #858 from mlarraz/fix_some_warnings * Merge pull request #860 from zendesk/expose_old_worker_count * Merge pull request #861 from zendesk/allow_control_url_umask ## 2.15.3 / 2015-11-07 * 1 bug fix: * Fix JRuby parser ## 2.15.2 / 2015-11-06 * 2 bug fixes: * ext/puma_http11: handle duplicate headers as per RFC * Only set ctx.ca iff there is a params['ca'] to set with. * 2 PRs merged: * Merge pull request #818 from unleashed/support-duplicate-headers * Merge pull request #819 from VictorLowther/fix-ca-and-verify_null-exception ## 2.15.1 / 2015-11-06 * 1 bug fix: * Allow older openssl versions ## 2.15.0 / 2015-11-06 * 6 minor features: * Allow setting ca without setting a verify mode * Make jungle for init.d support rbenv * Use SSL_CTX_use_certificate_chain_file for full chain * cluster: add worker_boot_timeout option * configuration: allow empty tags to mean no tag desired * puma/cli: support specifying STD{OUT,ERR} redirections and append mode * 5 bug fixes: * Disable SSL Compression * Fix bug setting worker_directory when using a symlink directory * Fix error message in DSL that was slightly inaccurate * Pumactl: set correct process name. Fixes #563 * thread_pool: fix race condition when shutting down workers * 10 doc fixes: * Add before_fork explanation in Readme.md * Correct spelling in DEPLOYMENT.md * Correct spelling in docs/nginx.md * Fix spelling errors. * Fix typo in deployment description * Fix typos (it's -> its) in events.rb and server.rb * fixing for typo mentioned in #803 * Spelling correction for README * thread_pool: fix typos in comment * More explicit docs for worker_timeout * 18 PRs merged: * Merge pull request #768 from nathansamson/patch-1 * Merge pull request #773 from rossta/spelling_corrections * Merge pull request #774 from snow/master * Merge pull request #781 from sunsations/fix-typo * Merge pull request #791 from unleashed/allow_empty_tags * Merge pull request #793 from robdimarco/fix-working-directory-symlink-bug * Merge pull request #794 from peterkeen/patch-1 * Merge pull request #795 from unleashed/redirects-from-cmdline * Merge pull request #796 from cschneid/fix_dsl_message * Merge pull request #799 from annafw/master * Merge pull request #800 from liamseanbrady/fix_typo * Merge pull request #801 from scottjg/ssl-chain-file * Merge pull request #802 from scottjg/ssl-crimes * Merge pull request #804 from burningTyger/patch-2 * Merge pull request #809 from unleashed/threadpool-fix-race-in-shutdown * Merge pull request #810 from vlmonk/fix-pumactl-restart-bug * Merge pull request #814 from schneems/schneems/worker_timeout-docs * Merge pull request #817 from unleashed/worker-boot-timeout ## 2.14.0 / 2015-09-18 * 1 minor feature: * Make building with SSL support optional * 1 bug fix: * Use Rack::Builder if available. Fixes #735 ## 2.13.4 / 2015-08-16 * 1 bug fix: * Use the environment possible set by the config early and from the config file later (if set). ## 2.13.3 / 2015-08-15 Seriously, I need to revamp config with tests. * 1 bug fix: * Fix preserving options before cleaning for state. Fixes #769 ## 2.13.2 / 2015-08-15 The "clearly I don't have enough tests for the config" release. * 1 bug fix: * Fix another place binds wasn't initialized. Fixes #767 ## 2.13.1 / 2015-08-15 * 2 bug fixes: * Fix binds being masked in config files. Fixes #765 * Use options from the config file properly in pumactl. Fixes #764 ## 2.13.0 / 2015-08-14 * 1 minor feature: * Add before_fork hooks option. * 3 bug fixes: * Check for OPENSSL_NO_ECDH before using ECDH * Eliminate logging overhead from JRuby SSL * Prefer cli options over config file ones. Fixes #669 * 1 deprecation: * Add deprecation warning to capistrano.rb. Fixes #673 * 4 PRs merged: * Merge pull request #668 from kcollignon/patch-1 * Merge pull request #754 from nathansamson/before_boot * Merge pull request #759 from BenV/fix-centos6-build * Merge pull request #761 from looker/no-log ## 2.12.3 / 2015-08-03 * 8 minor bugs fixed: * Fix Capistrano 'uninitialized constant Puma' error. * Fix some ancient and incorrect error handling code * Fix uninitialized constant error * Remove toplevel rack interspection, require rack on load instead * Skip empty parts when chunking * Switch from inject to each in config_ru_binds iteration * Wrap SSLv3 spec in version guard. * ruby 1.8.7 compatibility patches * 4 PRs merged: * Merge pull request #742 from deivid-rodriguez/fix_missing_require * Merge pull request #743 from matthewd/skip-empty-chunks * Merge pull request #749 from huacnlee/fix-cap-uninitialized-puma-error * Merge pull request #751 from costi/compat_1_8_7 * 1 test fix: * Add 1.8.7, rbx-1 (allow failures) to Travis. ## 2.12.2 / 2015-07-17 * 2 bug fix: * Pull over and use Rack::URLMap. Fixes #741 * Stub out peercert on JRuby for now. Fixes #739 ## 2.12.1 / 2015-07-16 * 2 bug fixes: * Use a constant format. Fixes #737 * Use strerror for Windows sake. Fixes #733 * 1 doc change: * typo fix: occured -> occurred * 1 PR merged: * Merge pull request #736 from paulanunda/paulanunda/typo-fix ## 2.12.0 / 2015-07-14 * 13 bug fixes: * Add thread reaping to thread pool * Do not automatically use chunked responses when hijacked * Do not suppress Content-Length on partial hijack * Don't allow any exceptions to terminate a thread * Handle ENOTCONN client disconnects when setting REMOTE_ADDR * Handle very early exit of cluster mode. Fixes #722 * Install rack when running tests on travis to use rack/lint * Make puma -v and -h return success exit code * Make pumactl load config/puma.rb by default * Pass options from pumactl properly when pruning. Fixes #694 * Remove rack dependency. Fixes #705 * Remove the default Content-Type: text/plain * Add Client Side Certificate Auth * 8 doc/test changes: * Added example sourcing of environment vars * Added tests for bind configuration on rackup file * Fix example config text * Update DEPLOYMENT.md * Update Readme with example of custom error handler * ci: Improve Travis settings * ci: Start running tests against JRuby 9k on Travis * ci: Convert to container infrastructure for travisci * 2 ops changes: * Check for system-wide rbenv * capistrano: Add additional env when start rails * 16 PRs merged: * Merge pull request #686 from jjb/patch-2 * Merge pull request #693 from rob-murray/update-example-config * Merge pull request #697 from spk/tests-bind-on-rackup-file * Merge pull request #699 from deees/fix/require_rack_builder * Merge pull request #701 from deepj/master * Merge pull request #702 from Jimdo/thread-reaping * Merge pull request #703 from deepj/travis * Merge pull request #704 from grega/master * Merge pull request #709 from lian/master * Merge pull request #711 from julik/master * Merge pull request #712 from yakara-ltd/pumactl-default-config * Merge pull request #715 from RobotJiang/master * Merge pull request #725 from rwz/master * Merge pull request #726 from strenuus/handle-client-disconnect * Merge pull request #729 from allaire/patch-1 * Merge pull request #730 from iamjarvo/container-infrastructure ## 2.11.3 / 2015-05-18 * 5 bug fixes: * Be sure to unlink tempfiles after a request. Fixes #690 * Coerce the key to a string before checking. (thar be symbols). Fixes #684 * Fix hang on bad SSL handshake * Remove `enable_SSLv3` support from JRuby * 1 PR merged: * Merge pull request #698 from looker/hang-handshake ## 2.11.2 / 2015-04-11 * 2 minor features: * Add `on_worker_fork` hook, which allows to mimic Unicorn's behavior * Add shutdown_debug config option * 4 bug fixes: * Fix the Config constants not being available in the DSL. Fixes #683 * Ignore multiple port declarations * Proper 'Connection' header handling compatible with HTTP 1.[01] protocols * Use "Puma" instead of "puma" to reporting to New Relic * 1 doc fixes: * Add Gitter badge. * 6 PRs merged: * Merge pull request #657 from schneems/schneems/puma-once-port * Merge pull request #658 from Tomohiro/newrelic-dispatcher-default-update * Merge pull request #662 from basecrm/connection-compatibility * Merge pull request #664 from fxposter/on-worker-fork * Merge pull request #667 from JuanitoFatas/doc/gemspec * Merge pull request #672 from chulkilee/refactor ## 2.11.1 / 2015-02-11 * 2 bug fixes: * Avoid crash in strange restart conditions * Inject the GEM_HOME that bundler into puma-wild's env. Fixes #653 * 2 PRs merged: * Merge pull request #644 from bpaquet/master * Merge pull request #646 from mkonecny/master ## 2.11.0 / 2015-01-20 * 9 bug fixes: * Add mode as an additional bind option to unix sockets. Fixes #630 * Advertise HTTPS properly after a hot restart * Don't write lowlevel_error_handler to state * Fix phased restart with stuck requests * Handle spaces in the path properly. Fixes #622 * Set a default REMOTE_ADDR to avoid using peeraddr on unix sockets. Fixes #583 * Skip device number checking on jruby. Fixes #586 * Update extconf.rb to compile correctly on OS X * redirect io right after daemonizing so startup errors are shown. Fixes #359 * 6 minor features: * Add a configuration option that prevents puma from queueing requests. * Add reload_worker_directory * Add the ability to pass environment variables to the init script (for Jungle). * Add the proctitle tag to the worker. Fixes #633 * Infer a proctitle tag based on the directory * Update lowlevel error message to be more meaningful. * 10 PRs merged: * Merge pull request #478 from rubencaro/master * Merge pull request #610 from kwilczynski/master * Merge pull request #611 from jasonl/better-lowlevel-message * Merge pull request #616 from jc00ke/master * Merge pull request #623 from raldred/patch-1 * Merge pull request #628 from rdpoor/master * Merge pull request #634 from deepj/master * Merge pull request #637 from raskhadafi/patch-1 * Merge pull request #639 from ebeigarts/fix-phased-restarts * Merge pull request #640 from codehotter/issue-612-dependent-requests-deadlock ## 2.10.2 / 2014-11-26 * 1 bug fix: * Conditionalize thread local cleaning, fixes perf degradation fix The code to clean out all Thread locals adds pretty significant overhead to a each request, so it has to be turned on explicitly if a user needs it. ## 2.10.1 / 2014-11-24 * 1 bug fix: * Load the app after daemonizing because the app might start threads. This change means errors loading the app are now reported only in the redirected stdout/stderr. If you're app has problems starting up, start it without daemon mode initially to test. ## 2.10.0 / 2014-11-23 * 3 minor features: * Added on_worker_shutdown hook mechanism * Allow binding to ipv6 addresses for ssl URIs * Warn about any threads started during app preload * 5 bug fixes: * Clean out a threads local data before doing work * Disable SSLv3. Fixes #591 * First change the directory to use the correct Gemfile. * Only use config.ru binds if specified. Fixes #606 * Strongish cipher suite with FS support for some browsers * 2 doc changes: * Change umask examples to more permissive values * fix typo in README.md * 9 Merged PRs: * Merge pull request #560 from raskhadafi/prune_bundler-bug * Merge pull request #566 from sheltond/master * Merge pull request #593 from andruby/patch-1 * Merge pull request #594 from hassox/thread-cleanliness * Merge pull request #596 from burningTyger/patch-1 * Merge pull request #601 from sorentwo/friendly-umask * Merge pull request #602 from 1334/patch-1 * Merge pull request #608 from Gu1/master * Merge remote-tracking branch 'origin/pr/538' ## 2.9.2 / 2014-10-25 * 8 bug fixes: * Fix puma-wild handling a restart properly. Fixes #550 * JRuby SSL POODLE update * Keep deprecated features warnings * Log the current time when Puma shuts down. * Fix cross-platform extension library detection * Use the correct Windows names for OpenSSL. * Better error logging during startup * Fixing sexist error messages * 6 PRs merged: * Merge pull request #549 from bsnape/log-shutdown-time * Merge pull request #553 from lowjoel/master * Merge pull request #568 from mariuz/patch-1 * Merge pull request #578 from danielbuechele/patch-1 * Merge pull request #581 from alexch/slightly-better-logging * Merge pull request #590 from looker/jruby_disable_sslv3 ## 2.9.1 / 2014-09-05 * 4 bug fixes: * Cleanup the SSL related structures properly, fixes memory leak * Fix thread spawning edge case. * Force a worker check after a worker boots, don't wait 5sec. Fixes #574 * Implement SIGHUP for logs reopening * 2 PRs merged: * Merge pull request #561 from theoldreader/sighup * Merge pull request #570 from havenwood/spawn-thread-edge-case ## 2.9.0 / 2014-07-12 * 1 minor feature: * Add SSL support for JRuby * 3 bug fixes: * Typo BUNDLER_GEMFILE -> BUNDLE_GEMFILE * Use fast_write because we can't trust syswrite * pumactl - do not modify original ARGV * 4 doc fixes: * BSD-3-Clause over BSD to avoid confusion * Deploy doc: clarification of the GIL * Fix typo in DEPLOYMENT.md * Update README.md * 6 PRs merged: * Merge pull request #520 from misfo/patch-2 * Merge pull request #530 from looker/jruby-ssl * Merge pull request #537 from vlmonk/patch-1 * Merge pull request #540 from allaire/patch-1 * Merge pull request #544 from chulkilee/bsd-3-clause * Merge pull request #551 from jcxplorer/patch-1 ## 2.8.2 / 2014-04-12 * 4 bug fixes: * During upgrade, change directory in main process instead of workers. * Close the client properly on error * Capistrano: fallback from phased restart to start when not started * Allow tag option in conf file * 4 doc fixes: * Fix Puma daemon service README typo * `preload_app!` instead of `preload_app` * add preload_app and prune_bundler to example config * allow changing of worker_timeout in config file * 11 PRs merged: * Merge pull request #487 from ckuttruff/master * Merge pull request #492 from ckuttruff/master * Merge pull request #493 from alepore/config_tag * Merge pull request #503 from mariuz/patch-1 * Merge pull request #505 from sammcj/patch-1 * Merge pull request #506 from FlavourSys/config_worker_timeout * Merge pull request #510 from momer/rescue-block-handle-servers-fix * Merge pull request #511 from macool/patch-1 * Merge pull request #514 from edogawaconan/refactor_env * Merge pull request #517 from misfo/patch-1 * Merge pull request #518 from LongMan/master ## 2.8.1 / 2014-03-06 * 1 bug fixes: * Run puma-wild with proper deps for prune_bundler * 2 doc changes: * Described the configuration file finding behavior added in 2.8.0 and how to disable it. * Start the deployment doc * 6 PRs merged: * Merge pull request #471 from arthurnn/fix_test * Merge pull request #485 from joneslee85/patch-9 * Merge pull request #486 from joshwlewis/patch-1 * Merge pull request #490 from tobinibot/patch-1 * Merge pull request #491 from brianknight10/clarify-no-config ## 2.8.0 / 2014-02-28 * 8 minor features: * Add ability to autoload a config file. Fixes #438 * Add ability to detect and terminate hung workers. Fixes #333 * Add booted_workers to stats response * Add config to customize the default error message * Add prune_bundler option * Add worker indexes, expose them via on_worker_boot. Fixes #440 * Add pretty process name * Show the ruby version in use * 7 bug fixes: * Added 408 status on timeout. * Be more hostile with sockets that write block. Fixes #449 * Expect at_exit to exclusively remove the pidfile. Fixes #444 * Expose latency and listen backlog via bind query. Fixes #370 * JRuby raises IOError if the socket is there. Fixes #377 * Process requests fairly. Fixes #406 * Rescue SystemCallError as well. Fixes #425 * 4 doc changes: * Add 2.1.0 to the matrix * Add Code Climate badge to README * Create signals.md * Set the license to BSD. Fixes #432 * 14 PRs merged: * Merge pull request #428 from alexeyfrank/capistrano_default_hooks * Merge pull request #429 from namusyaka/revert-const_defined * Merge pull request #431 from mrb/master * Merge pull request #433 from alepore/process-name * Merge pull request #437 from ibrahima/master * Merge pull request #446 from sudara/master * Merge pull request #451 from pwiebe/status_408 * Merge pull request #453 from joevandyk/patch-1 * Merge pull request #470 from arthurnn/fix_458 * Merge pull request #472 from rubencaro/master * Merge pull request #480 from jjb/docs-on-running-test-suite * Merge pull request #481 from schneems/master * Merge pull request #482 from prathamesh-sonpatki/signals-doc-cleanup * Merge pull request #483 from YotpoLtd/master ## 2.7.1 / 2013-12-05 * 1 bug fix: * Keep STDOUT/STDERR the right mode. Fixes #422 ## 2.7.0 / 2013-12-03 * 1 minor feature: * Adding TTIN and TTOU to increment/decrement workers * N bug fixes: * Always use our Process.daemon because it's not busted * Add capistrano restart failback to start. * Change position of `cd` so that rvm gemset is loaded * Clarify some platform specifics * Do not close the pipe sockets when retrying * Fix String#byteslice for Ruby 1.9.1, 1.9.2 * Fix compatibility with 1.8.7. * Handle IOError closed stream in IO.select * Increase the max URI path length to 2048 chars from 1024 chars * Upstart jungle use config/puma.rb instead ## 2.6.0 / 2013-09-13 * 2 minor features: * Add support for event hooks ** Add a hook for state transitions * Add phased restart to capistrano recipe. * 4 bug fixes: * Convince workers to stop by SIGKILL after timeout * Define RSTRING_NOT_MODIFIED for Rubinius performance * Handle BrokenPipe, StandardError and IOError in fat_wrote and break out * Return success status to the invoking environment ## 2.5.1 / 2013-08-13 * 2 bug fixes: * Keep jruby daemon mode from retrying on a hot restart * Extract version from const.rb in gemspec ## 2.5.0 / 2013-08-08 * 2 minor features: * Allow configuring pumactl with config.rb * make `pumactl restart` start puma if not running * 6 bug fixes: * Autodetect ruby managers and home directory in upstart script * Convert header values to string before sending. * Correctly report phased-restart availability * Fix pidfile creation/deletion race on jruby daemonization * Use integers when comparing thread counts * Fix typo in using lopez express (raw tcp) mode * 6 misc changes: * Fix typo in phased-restart response * Uncomment setuid/setgid by default in upstart * Use Puma::Const::PUMA_VERSION in gemspec * Update upstart comments to reflect new commandline * Remove obsolete pumactl instructions; refer to pumactl for details * Make Bundler used puma.gemspec version agnostic ## 2.4.1 / 2013-08-07 * 1 experimental feature: * Support raw tcp servers (aka Lopez Express mode) ## 2.4.0 / 2013-07-22 * 5 minor features: * Add PUMA_JRUBY_DAEMON_OPTS to get around agent starting twice * Add ability to drain accept socket on shutdown * Add port to DSL * Adds support for using puma config file in capistrano deploys. * Make phased_restart fallback to restart if not available * 10 bug fixes: * Be sure to only delete the pid in the master. Fixes #334 * Call out -C/--config flags * Change parser symbol names to avoid clash. Fixes #179 * Convert thread pool sizes to integers * Detect when the jruby daemon child doesn't start properly * Fix typo in CLI help * Improve the logging output when hijack is used. Fixes #332 * Remove unnecessary thread pool size conversions * Setup :worker_boot as an Array. Fixes #317 * Use 127.0.0.1 as REMOTE_ADDR of unix client. Fixes #309 ## 2.3.2 / 2013-07-08 * 1 bug fix: * Move starting control server to after daemonization. ## 2.3.1 / 2013-07-06 * 2 bug fixes: * Include the right files in the Manifest. * Disable inheriting connections on restart on windows. Fixes #166 * 1 doc change: * Better document some platform constraints ## 2.3.0 / 2013-07-05 * 1 major bug fix: * Stabilize control server, add support in cluster mode * 5 minor bug fixes: * Add ability to cleanup stale unix sockets * Check status data better. Fixes #292 * Convert raw IO errors to ConnectionError. Fixes #274 * Fix sending Content-Type and Content-Length for no body status. Fixes #304 * Pass state path through to `pumactl start`. Fixes #287 * 2 internal changes: * Refactored modes into seperate classes that CLI uses * Changed CLI to take an Events object instead of stdout/stderr (API change) ## 2.2.2 / 2013-07-02 * 1 bug fix: * Fix restart_command in the config ## 2.2.1 / 2013-07-02 * 1 minor feature: * Introduce preload flag * 1 bug fix: * Pass custom restart command in JRuby ## 2.2.0 / 2013-07-01 * 1 major feature: * Add ability to preload rack app * 2 minor bugfixes: * Don't leak info when not in development. Fixes #256 * Load the app, then bind the ports ## 2.1.1 / 2013-06-20 * 2 minor bug fixes: * Fix daemonization on jruby * Load the application before daemonizing. Fixes #285 ## 2.1.0 / 2013-06-18 * 3 minor features: * Allow listening socket to be configured via Capistrano variable * Output results from 'stat's command when using pumactl * Support systemd socket activation * 15 bug fixes: * Deal with pipes closing while stopping. Fixes #270 * Error out early if there is no app configured * Handle ConnectionError rather than the lowlevel exceptions * tune with `-C` config file and `on_worker_boot` * use `-w` * Fixed some typos in upstart scripts * Make sure to use bytesize instead of size (MiniSSL write) * Fix an error in puma-manager.conf * fix: stop leaking sockets on restart (affects ruby 1.9.3 or before) * Ignore errors on the cross-thread pipe. Fixes #246 * Ignore errors while uncorking the socket (it might already be closed) * Ignore the body on a HEAD request. Fixes #278 * Handle all engine data when possible. Fixes #251. * Handle all read exceptions properly. Fixes #252 * Handle errors from the server better * 3 doc changes: * Add note about on_worker_boot hook * Add some documentation for Clustered mode * Added quotes to /etc/puma.conf ## 2.0.1 / 2013-04-30 * 1 bug fix: * Fix not starting on JRuby properly ## 2.0.0 / 2013-04-29 RailsConf 2013 edition! * 2 doc changes: * Start with rackup -s Puma, NOT rackup -s puma. * Minor doc fixes in the README.md, Capistrano section * 2 bug fixes: * Fix reading RACK_ENV properly. Fixes #234 * Make cap recipe handle tmp/sockets; fixes #228 * 3 minor changes: * Fix capistrano recipe * Fix stdout/stderr logs to sync outputs * allow binding to IPv6 addresses ## 2.0.0.b7 / 2013-03-18 * 5 minor enhancements: * Add -q option for :start * Add -V, --version * Add default Rack handler helper * Upstart support * Set worker directory from configuration file * 12 bug fixes: * Close the binder in the right place. Fixes #192 * Handle early term in workers. Fixes #206 * Make sure that the default port is 80 when the request doesn't include HTTP_X_FORWARDED_PROTO. * Prevent Errno::EBADF errors on restart when running ruby 2.0 * Record the proper @master_pid * Respect the header HTTP_X_FORWARDED_PROTO when the host doesn't include a port number. * Retry EAGAIN/EWOULDBLOCK during syswrite * Run exec properly to restart. Fixes #154 * Set Rack run_once to false * Syncronize all access to @timeouts. Fixes #208 * Write out the state post-daemonize. Fixes #189 * Prevent crash when all workers are gone ## 2.0.0.b6 / 2013-02-06 * 2 minor enhancements: * Add hook for running when a worker boots * Advertise the Configuration object for apps to use. * 1 bug fix: * Change directory in working during upgrade. Fixes #185 ## 2.0.0.b5 / 2013-02-05 * 2 major features: * Add phased worker upgrade * Add support for the rack hijack protocol * 2 minor features: * Add -R to specify the restart command * Add config file option to specify the restart command * 5 bug fixes: * Cleanup pipes properly. Fixes #182 * Daemonize earlier so that we don't lose app threads. Fixes #183 * Drain the notification pipe. Fixes #176, thanks @cryo28 * Move write_pid to after we daemonize. Fixes #180 * Redirect IO properly and emit message for checkpointing ## 2.0.0.b4 / 2012-12-12 * 4 bug fixes: * Properly check #syswrite's value for variable sized buffers. Fixes #170 * Shutdown status server properly * Handle char vs byte and mixing syswrite with write properly * made MiniSSL validate key/cert file existence ## 2.0.0.b3 / 2012-11-22 * 1 bug fix: * Package right files in gem ## 2.0.0.b2 / 2012-11-18 * 5 minor feature: * Now Puma is bundled with an capistrano recipe. Just require 'puma/capistrano' in you deploy.rb * Only inject CommonLogger in development mode * Add -p option to pumactl * Add ability to use pumactl to start a server * Add options to daemonize puma * 7 bug fixes: * Reset the IOBuffer properly. Fixes #148 * Shutdown gracefully on JRuby with Ctrl-C * Various methods to get newrelic to start. Fixes #128 * fixing syntax error at capistrano recipe * Force ECONNRESET when read returns nil * Be sure to empty the drain the todo before shutting down. Fixes #155 * allow for alternate locations for status app ## 2.0.0.b1 / 2012-09-11 * 1 major feature: * Optional worker process mode (-w) to allow for process scaling in addition to thread scaling * 1 bug fix: * Introduce Puma::MiniSSL to be able to properly control doing nonblocking SSL NOTE: SSL support in JRuby is not supported at present. Support will be added back in a future date when a java Puma::MiniSSL is added. ## 1.6.3 / 2012-09-04 * 1 bug fix: * Close sockets waiting in the reactor when a hot restart is performed so that browsers reconnect on the next request ## 1.6.2 / 2012-08-27 * 1 bug fix: * Rescue StandardError instead of IOError to handle SystemCallErrors as well as other application exceptions inside the reactor. ## 1.6.1 / 2012-07-23 * 1 packaging bug fixed: * Include missing files ## 1.6.0 / 2012-07-23 * 1 major bug fix: * Prevent slow clients from starving the server by introducing a dedicated IO reactor thread. Credit for reporting goes to @meh. ## 1.5.0 / 2012-07-19 * 7 contributors to this release: * Christian Mayer * Darío Javier Cravero * Dirkjan Bussink * Gianluca Padovani * Santiago Pastorino * Thibault Jouan * tomykaira * 6 bug fixes: * Define RSTRING_NOT_MODIFIED for Rubinius * Convert status to integer. Fixes #123 * Delete pidfile when stopping the server * Allow compilation with -Werror=format-security option * Fix wrong HTTP version for a HTTP/1.0 request * Use String#bytesize instead of String#length * 3 minor features: * Added support for setting RACK_ENV via the CLI, config file, and rack app * Allow Server#run to run sync. Fixes #111 * Puma can now run on windows ## 1.4.0 / 2012-06-04 * 1 bug fix: * SCRIPT_NAME should be passed from env to allow mounting apps * 1 experimental feature: * Add puma.socket key for direct socket access ## 1.3.1 / 2012-05-15 * 2 bug fixes: * use #bytesize instead of #length for Content-Length header * Use StringIO properly. Fixes #98 ## 1.3.0 / 2012-05-08 * 2 minor features: * Return valid Rack responses (passes Lint) from status server * Add -I option to specify $LOAD_PATH directories * 4 bug fixes: * Don't join the server thread inside the signal handle. Fixes #94 * Make NullIO#read mimic IO#read * Only stop the status server if it's started. Fixes #84 * Set RACK_ENV early in cli also. Fixes #78 * 1 new contributor: * Jesse Cooke ## 1.2.2 / 2012-04-28 * 4 bug fixes: * Report a lowlevel error to stderr * Set a fallback SERVER_NAME and SERVER_PORT * Keep the encoding of the body correct. Fixes #79 * show error.to_s along with backtrace for low-level error ## 1.2.1 / 2012-04-11 1 bug fix: * Fix rack.url_scheme for SSL servers. Fixes #65 ## 1.2.0 / 2012-04-11 1 major feature: * When possible, the internal restart does a "hot restart" meaning the server sockets remains open, so no connections are lost. 1 minor feature: * More helpful fallback error message 6 bug fixes: * Pass the proper args to unknown_error. Fixes #54, #58 * Stop the control server before restarting. Fixes #61 * Fix reporting https only on a true SSL connection * Set the default content type to 'text/plain'. Fixes #63 * Use REUSEADDR. Fixes #60 * Shutdown gracefully on SIGTERM. Fixes #53 2 new contributors: * Seamus Abshere * Steve Richert ## 1.1.1 / 2012-03-30 1 bugfix: * Include puma/compat.rb in the gem (oops!) ## 1.1.0 / 2012-03-30 1 bugfix: * Make sure that the unix socket has the perms 0777 by default 1 minor feature: * Add umask param to the unix:// bind to set the umask ## 1.0.0 / 2012-03-29 * Released! puma-3.12.4/LICENSE000066400000000000000000000027751362626474300136000ustar00rootroot00000000000000Some code copyright (c) 2005, Zed Shaw Copyright (c) 2011, Evan Phoenix All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Evan Phoenix nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. puma-3.12.4/README.md000066400000000000000000000237241362626474300140470ustar00rootroot00000000000000

# Puma: A Ruby Web Server Built For Concurrency [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/puma/puma?utm\_source=badge&utm\_medium=badge&utm\_campaign=pr-badge) [![Build Status](https://secure.travis-ci.org/puma/puma.svg)](http://travis-ci.org/puma/puma) [![AppVeyor](https://img.shields.io/appveyor/ci/nateberkopec/puma.svg)](https://ci.appveyor.com/project/nateberkopec/puma) [![Dependency Status](https://gemnasium.com/puma/puma.svg)](https://gemnasium.com/puma/puma) [![Code Climate](https://codeclimate.com/github/puma/puma.svg)](https://codeclimate.com/github/puma/puma) Puma is a **simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications** in development and production. ## Built For Speed & Concurrency Under the hood, Puma processes requests using a C-optimized Ragel extension (inherited from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable way. Puma then serves the request in a thread from an internal thread pool. Since each request is served in a separate thread, truly concurrent Ruby implementations (JRuby, Rubinius) will use all available CPU cores. Puma was designed to be the go-to server for [Rubinius](http://rubini.us), but also works well with JRuby and MRI. On MRI, there is a Global VM Lock (GVL) that ensures only one thread can run Ruby code at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing blocking IO to be run concurrently. ## Quick Start ``` $ gem install puma $ puma ``` ## Frameworks ### Rails Puma is the default server for Rails, and should already be included in your Gemfile. Then start your server with the `rails` command: ``` $ rails s ``` Many configuration options are not available when using `rails s`. It is recommended that you use Puma's executable instead: ``` $ bundle exec puma ``` ### Sinatra You can run your Sinatra application with Puma from the command line like this: ``` $ ruby app.rb -s Puma ``` Or you can configure your application to always use Puma: ```ruby require 'sinatra' configure { set :server, :puma } ``` ## Configuration Puma provides numerous options. Consult `puma -h` (or `puma --help`) for a full list of CLI options, or see [dsl.rb](https://github.com/puma/puma/blob/master/lib/puma/dsl.rb). ### Thread Pool Puma uses a thread pool. You can set the minimum and maximum number of threads that are available in the pool with the `-t` (or `--threads`) flag: ``` $ puma -t 8:32 ``` Puma will automatically scale the number of threads, from the minimum until it caps out at the maximum, based on how much traffic is present. The current default is `0:16`. Feel free to experiment, but be careful not to set the number of maximum threads to a large number, as you may exhaust resources on the system (or hit resource limits). Be aware that additionally Puma creates threads on its own for internal purposes (e.g. handling slow clients). So even if you specify -t 1:1, expect around 7 threads created in your application. ### Clustered mode Puma also offers "clustered mode". Clustered mode `fork`s workers from a master process. Each child process still has its own thread pool. You can tune the number of workers with the `-w` (or `--workers`) flag: ``` $ puma -t 8:32 -w 3 ``` Note that threads are still used in clustered mode, and the `-t` thread flag setting is per worker, so `-w 2 -t 16:16` will spawn 32 threads in total. In clustered mode, Puma may "preload" your application. This loads all the application code *prior* to forking. Preloading reduces total memory usage of your application via an operating system feature called [copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write) (Ruby 2.0+ only). Use the `--preload` flag from the command line: ``` $ puma -w 3 --preload ``` If you're using a configuration file, use the `preload_app!` method: ```ruby # config/puma.rb workers 3 preload_app! ``` Additionally, you can specify a block in your configuration file that will be run on boot of each worker: ```ruby # config/puma.rb on_worker_boot do # configuration here end ``` This code can be used to setup the process before booting the application, allowing you to do some Puma-specific things that you don't want to embed in your application. For instance, you could fire a log notification that a worker booted or send something to statsd. This can be called multiple times. If you're preloading your application and using ActiveRecord, it's recommended that you setup your connection pool here: ```ruby # config/puma.rb on_worker_boot do ActiveSupport.on_load(:active_record) do ActiveRecord::Base.establish_connection end end ``` On top of that, you can specify a block in your configuration file that will be run before workers are forked: ```ruby # config/puma.rb before_fork do # configuration here end ``` Preloading can’t be used with phased restart, since phased restart kills and restarts workers one-by-one, and preload_app copies the code of master into the workers. ### Binding TCP / Sockets In contrast to many other server configs which require multiple flags, Puma simply uses one URI parameter with the `-b` (or `--bind`) flag: ``` $ puma -b tcp://127.0.0.1:9292 ``` Want to use UNIX Sockets instead of TCP (which can provide a 5-10% performance boost)? ``` $ puma -b unix:///var/run/puma.sock ``` If you need to change the permissions of the UNIX socket, just add a umask parameter: ``` $ puma -b 'unix:///var/run/puma.sock?umask=0111' ``` Need a bit of security? Use SSL sockets: ``` $ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert' ``` #### Controlling SSL Cipher Suites Need to use or avoid specific SSL cipher suites? Use ssl_cipher_filter or ssl_cipher_list options. #####Ruby: ``` $ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&ssl_cipher_filter=!aNULL:AES+SHA' ``` #####JRuby: ``` $ puma -b 'ssl://127.0.0.1:9292?keystore=path_to_keystore&keystore-pass=keystore_password&ssl_cipher_list=TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA' ``` See https://www.openssl.org/docs/man1.0.2/apps/ciphers.html for cipher filter format and full list of cipher suites. ### Control/Status Server Puma has a built-in status/control app that can be used to query and control Puma itself. ``` $ puma --control-url tcp://127.0.0.1:9293 --control-token foo ``` Puma will start the control server on localhost port 9293. All requests to the control server will need to include `token=foo` as a query parameter. This allows for simple authentication. Check out [status.rb](https://github.com/puma/puma/blob/master/lib/puma/app/status.rb) to see what the app has available. You can also interact with the control server via `pumactl`. This command will restart Puma: ``` $ pumactl --control-url 'tcp://127.0.0.1:9293' --control-token foo restart ``` To see a list of `pumactl` options, use `pumactl --help`. ### Configuration File You can also provide a configuration file which Puma will use with the `-C` (or `--config`) flag: ``` $ puma -C /path/to/config ``` If no configuration file is specified, Puma will look for a configuration file at `config/puma.rb`. If an environment is specified, either via the `-e` and `--environment` flags, or through the `RACK_ENV` environment variable, the default file location will be `config/puma/environment_name.rb`. If you want to prevent Puma from looking for a configuration file in those locations, provide a dash as the argument to the `-C` (or `--config`) flag: ``` $ puma -C "-" ``` Take the following [sample configuration](https://github.com/puma/puma/blob/master/examples/config.rb) as inspiration or check out [dsl.rb](https://github.com/puma/puma/blob/master/lib/puma/dsl.rb) to see all available options. ## Restart Puma includes the ability to restart itself. When available (MRI, Rubinius, JRuby), Puma performs a "hot restart". This is the same functionality available in *Unicorn* and *NGINX* which keep the server sockets open between restarts. This makes sure that no pending requests are dropped while the restart is taking place. For more, see the [restart documentation](https://github.com/puma/puma/blob/master/docs/restart.md). ## Signals Puma responds to several signals. A detailed guide to using UNIX signals with Puma can be found in the [signals documentation](https://github.com/puma/puma/blob/master/docs/signals.md). ## Platform Constraints Some platforms do not support all Puma features. * **JRuby**, **Windows**: server sockets are not seamless on restart, they must be closed and reopened. These platforms have no way to pass descriptors into a new process that is exposed to Ruby. Also, cluster mode is not supported due to a lack of fork(2). * **Windows**: daemon mode is not supported due to a lack of fork(2). ## Known Bugs For MRI versions 2.2.7, 2.2.8, 2.2.9, 2.2.10 2.3.4 and 2.4.1, you may see ```stream closed in another thread (IOError)```. It may be caused by a [Ruby bug](https://bugs.ruby-lang.org/issues/13632). It can be fixed with the gem https://rubygems.org/gems/stopgap_13632: ```ruby if %w(2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1).include? RUBY_VERSION begin require 'stopgap_13632' rescue LoadError end end ``` ## Deployment Puma has support for Capistrano with an [external gem](https://github.com/seuros/capistrano-puma). It is common to use process monitors with Puma. Modern process monitors like systemd or upstart 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/systemd](https://github.com/puma/puma/blob/master/docs/systemd.md) ## Contributing To run the test suite: ```bash $ bundle install $ bundle exec rake ``` ## License Puma is copyright Evan Phoenix and contributors, licensed under the BSD 3-Clause license. See the included LICENSE file for details. puma-3.12.4/Rakefile000066400000000000000000000052641362626474300142340ustar00rootroot00000000000000require "bundler/setup" require "rake/testtask" require "rake/extensiontask" require "rake/javaextensiontask" require "rubocop/rake_task" require 'puma/detect' require 'rubygems/package_task' require 'bundler/gem_tasks' gemspec = Gem::Specification.load(Dir['*.gemspec'].first) Gem::PackageTask.new(gemspec).define # Add rubocop task RuboCop::RakeTask.new spec = Gem::Specification.load("puma.gemspec") # generate extension code using Ragel (C and Java) desc "Generate extension code (C and Java) using Ragel" task :ragel file 'ext/puma_http11/http11_parser.c' => ['ext/puma_http11/http11_parser.rl'] do |t| begin sh "ragel #{t.prerequisites.last} -C -G2 -I ext/puma_http11 -o #{t.name}" rescue fail "Could not build wrapper using Ragel (it failed or not installed?)" end end task :ragel => ['ext/puma_http11/http11_parser.c'] file 'ext/puma_http11/org/jruby/puma/Http11Parser.java' => ['ext/puma_http11/http11_parser.java.rl'] do |t| begin sh "ragel #{t.prerequisites.last} -J -G2 -I ext/puma_http11 -o #{t.name}" rescue fail "Could not build wrapper using Ragel (it failed or not installed?)" end end task :ragel => ['ext/puma_http11/org/jruby/puma/Http11Parser.java'] if !Puma.jruby? # compile extensions using rake-compiler # C (MRI, Rubinius) Rake::ExtensionTask.new("puma_http11", spec) do |ext| # place extension inside namespace ext.lib_dir = "lib/puma" CLEAN.include "lib/puma/{1.8,1.9}" CLEAN.include "lib/puma/puma_http11.rb" end else # Java (JRuby) Rake::JavaExtensionTask.new("puma_http11", spec) do |ext| ext.lib_dir = "lib/puma" end end # the following is a fat-binary stub that will be used when # require 'puma/puma_http11' and will use either 1.8 or 1.9 version depending # on RUBY_VERSION file "lib/puma/puma_http11.rb" do |t| File.open(t.name, "w") do |f| f.puts "RUBY_VERSION =~ /(\d+.\d+)/" f.puts 'require "puma/#{$1}/puma_http11"' end end Rake::TestTask.new(:test) # tests require extension be compiled, but depend on the platform if Puma.jruby? task :test => [:java] else task :test => [:compile] end task :test => [:ensure_no_puma_gem] task :ensure_no_puma_gem do Bundler.with_clean_env do out = `gem list puma`.strip if !$?.success? || out != "" abort "No other puma version should be installed to avoid false positives or loading it by accident but found #{out}" end end end namespace :test do desc "Run the integration tests" task :integration do sh "ruby test/shell/run.rb" end desc "Run all tests" if (Puma.jruby? && ENV['TRAVIS']) || Puma.windows? task :all => :test else task :all => [:test, "test:integration"] end end task :default => [:rubocop, "test:all"] puma-3.12.4/Release.md000066400000000000000000000007321362626474300144640ustar00rootroot00000000000000## Before Release - Make sure tests pass and your last local commit matches master. - Run tests with latest jruby - Update the version in `const.rb`. - Make sure there is a history entry in `History.md`. - On minor version updates i.e. from 3.10.x to 3.11.x update the "codename" in `const.rb`. # Release process Using "3.7.1" as a version example. 1. `bundle exec rake release` 2. Switch to latest JRuby version 3. `rake java gem` 4. `gem push pkg/puma-3.7.1-java.gem` puma-3.12.4/appveyor.yml000066400000000000000000000012161362626474300151500ustar00rootroot00000000000000install: # download shared script files - ps: >- if ( !(Test-Path -Path ./shared -PathType Container) ) { $uri = 'https://ci.appveyor.com/api/projects/MSP-Greg/av-gem-build-test/artifacts/shared.7z' $7z = 'C:\Program Files\7-Zip\7z.exe' $fn = "$env:TEMP\shared.7z" (New-Object System.Net.WebClient).DownloadFile($uri, $fn) &$7z x $fn -owin_gem_test 1> $null Remove-Item -LiteralPath $fn -Force Write-Host "Downloaded shared files" -ForegroundColor Yellow } build_script: - ps: .\win_gem_test\puma.ps1 $env:gem_bits environment: matrix: - gem_bits: 64 - gem_bits: 32 puma-3.12.4/benchmarks/000077500000000000000000000000001362626474300146755ustar00rootroot00000000000000puma-3.12.4/benchmarks/wrk/000077500000000000000000000000001362626474300155005ustar00rootroot00000000000000puma-3.12.4/benchmarks/wrk/hello.sh000077500000000000000000000003351362626474300171430ustar00rootroot00000000000000# You are encouraged to use @ioquatix's wrk fork, located here: https://github.com/ioquatix/wrk bundle exec bin/puma -t 4 test/rackup/hello.ru & PID1=$! sleep 5 wrk -c 4 -d 30 --latency http://localhost:9292 kill $PID1 puma-3.12.4/benchmarks/wrk/many_long_headers.sh000077500000000000000000000002101362626474300215060ustar00rootroot00000000000000bundle exec bin/puma -t 4 test/rackup/many_long_headers.ru & PID1=$! sleep 5 wrk -c 4 -d 30 --latency http://localhost:9292 kill $PID1 puma-3.12.4/benchmarks/wrk/realistic_response.sh000077500000000000000000000002111362626474300217260ustar00rootroot00000000000000bundle exec bin/puma -t 4 test/rackup/realistic_response.ru & PID1=$! sleep 5 wrk -c 4 -d 30 --latency http://localhost:9292 kill $PID1 puma-3.12.4/bin/000077500000000000000000000000001362626474300133305ustar00rootroot00000000000000puma-3.12.4/bin/puma000077500000000000000000000001611362626474300142160ustar00rootroot00000000000000#!/usr/bin/env ruby # # Copyright (c) 2011 Evan Phoenix # require 'puma/cli' cli = Puma::CLI.new ARGV cli.run puma-3.12.4/bin/puma-wild000066400000000000000000000006211362626474300151510ustar00rootroot00000000000000#!/usr/bin/env ruby # # Copyright (c) 2014 Evan Phoenix # require 'rubygems' gems = ARGV.shift inc = "" if gems == "-I" inc = ARGV.shift $LOAD_PATH.concat inc.split(":") gems = ARGV.shift end gems.split(",").each do |s| name, ver = s.split(":",2) gem name, ver end module Puma; end Puma.const_set("WILD_ARGS", ["-I", inc, gems]) require 'puma/cli' cli = Puma::CLI.new ARGV cli.run puma-3.12.4/bin/pumactl000077500000000000000000000002271362626474300147240ustar00rootroot00000000000000#!/usr/bin/env ruby require 'puma/control_cli' cli = Puma::ControlCLI.new ARGV.dup begin cli.run rescue => e STDERR.puts e.message exit 1 end puma-3.12.4/docs/000077500000000000000000000000001362626474300135105ustar00rootroot00000000000000puma-3.12.4/docs/architecture.md000066400000000000000000000040061362626474300165140ustar00rootroot00000000000000# Architecture ## Overview ![http://bit.ly/2iJuFky](images/puma-general-arch.png) Puma is a threaded web server, processing requests across a TCP or UNIX socket. Workers accept connections from the socket and a thread in the worker's thread pool processes the client's request. Clustered mode is shown/discussed here. Single mode is analogous to having a single worker process. ## Connection pipeline ![http://bit.ly/2zwzhEK](images/puma-connection-flow.png) * Upon startup, Puma listens on a TCP or UNIX socket. * The backlog of this socket is configured (with a default of 1024), determining how many established but unaccepted connections can exist concurrently. * This socket backlog is distinct from the "backlog" of work as reported by the control server stats. The latter is the number of connections in that worker's "todo" set waiting for a worker thread. * By default, a single, separate thread is used to receive HTTP requests across the socket. * When at least one worker thread is available for work, a connection is accepted and placed in this request buffer * This thread waits for entire HTTP requests to be received over the connection * Once received, the connection is pushed into the "todo" set * Worker threads pop work off the "todo" set for processing * The thread processes the request via the rack application (which generates the HTTP response) * The thread writes the response to the connection * Finally, the thread become available to process another connection in the "todo" set ### Disabling `queue_requests` ![http://bit.ly/2zxCJ1Z](images/puma-connection-flow-no-reactor.png) The `queue_requests` option is `true` by default, enabling the separate thread used to buffer requests as described above. If set to `false`, this buffer will not be used for connections while waiting for the request to arrive. In this mode, when a connection is accepted, it is added to the "todo" queue immediately, and a worker will synchronously do any waiting necessary to read the HTTP request from the socket. puma-3.12.4/docs/deployment.md000066400000000000000000000077301362626474300162210ustar00rootroot00000000000000# Deployment engineering for puma Puma is software that is expected to be run in a deployed environment eventually. You can certainly use it as your dev server only, but most people look to use it in their production deployments as well. To that end, this is meant to serve as a foundation of wisdom how to do that in a way that increases happiness and decreases downtime. ## Specifying puma Most people want to do this by putting `gem "puma"` into their Gemfile, so we'll go ahead and assume that. Go add it now... we'll wait. Welcome back! ## Single vs Cluster mode Puma was originally conceived as a thread-only webserver, but grew the ability to also use processes in version 2. Here are some rules of thumb: ### MRI * Use cluster mode and set the number of workers to 1.5x the number of cpu cores in the machine, minimum 2. * Set the number of threads to desired concurrent requests / number of workers. Puma defaults to 16 and that's a decent number. #### Migrating from Unicorn * If you're migrating from unicorn though, here are some settings to start with: * Set workers to half the number of unicorn workers you're using * Set threads to 2 * Enjoy 50% memory savings * As you grow more confident in the thread safety of your app, you can tune the workers down and the threads up. #### Worker utilization **How do you know if you're got enough (or too many workers)?** A good question. Due to MRI's GIL, only one thread can be executing Ruby code at a time. But since so many apps are waiting on IO from DBs, etc., they can utilize threads to make better use of the process. The rule of thumb is you never want processes that are pegged all the time. This means that there is more work to do that the process can get through. On the other hand, if you have processes that sit around doing nothing, then they're just eating up resources. Watching your CPU utilization over time and aim for about 70% on average. This means you've got capacity still but aren't starving threads. ## Daemonizing 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 makes it easy to figure out what is going on. Additionally, unlike `unicorn`, puma does not require daemonization to do zero-downtime restarts. I see people using daemonization because they start puma directly via capistrano task and thus want it to live on past the `cap deploy`. To this people I said: You need to be using a process monitor. Nothing is making sure puma stays up in this scenario! You're just waiting for something weird to happen, puma to die, and to get paged at 3am. Do yourself a favor, at least the process monitoring your OS comes with, be it `sysvinit`, `upstart`, or `systemd`. Or branch out and use `runit` or hell, even `monit`. ## Restarting You probably will want to deploy some new code at some point, and you'd like puma to start running that new code. Minimizing the amount of time the server is unavailable would be nice as well. Here's how to do it: 1. Don't use `preload!`. This dirties the master process and means it will have to shutdown all the workers and re-exec itself to get your new code. It is not compatible with phased-restart and `prune_bundler` as well. 1. Use `prune_bundler`. This makes it so that the cluster master will detach itself from a Bundler context on start. This allows the cluster workers to load your app and start a brand new Bundler context within the worker only. This means your master remains pristine and can live on between new releases of your code. 1. Use phased-restart (`SIGUSR1` or `pumactl phased-restart`). This tells the master to kill off one worker at a time and restart them in your new code. This minimizes downtime and staggers the restart nicely. **WARNING** This means that both your old code and your new code will be running concurrently. Most deployment solutions already cause that, but it's worth warning you about it again. Be careful with your migrations, etc! puma-3.12.4/docs/images/000077500000000000000000000000001362626474300147555ustar00rootroot00000000000000puma-3.12.4/docs/images/puma-connection-flow-no-reactor.png000066400000000000000000000372341362626474300236070ustar00rootroot00000000000000PNG  IHDRT4e:>dIDATxpTٕ.Lvf!̲`,̲K\` +{nB(#Oܻw/###==ʕ+-!O̝;7r۷o/fΜ?33<|… <]DwfggΎ;bK.u=)))};?ŋcʕ6lآEڷo[طo_ů|׮]+%GlB!?~ܶm[ĉ^ڵkw„ O%~/i I&h]30}{]t!6pBKp;&:of(׭[ѣGN~$۬D3O<ٰaCs^$~wٳoAByQ=/]DݻիM4h0rH΢ w{;vXytܼyY;xr+.5a9gcǎz71oӉ΅0sСCA3I̧D#mԐ!CӱnVYH|m#Fq"N\+?36 #"XHի1Gg~fngN8 )Xwu$~SN 'OF< 6rMׯ_gI.~Y֨ˬAP1SO8z( QXJ y.,AH~b&`.&?3S0d›ªO<%c}q2kB\aLL :c#xDP̉7n\f-P܏D SVTCoC!'a[3sL8OY /ѣGoBD?X Ô#O>r2MxL&֩SfrC)mxZ,O~Q|̺uHOٹss@hb>g)sswJZRa 36Fza/;!Z ڋ@b\jbf-xxœ!͙32΢rzE R !f_nf.O"@CRmC]vID0Ǐ>$l" b&!rhA¡g%O-Y!ͼPMb{$~B!O! !?!B'B'BHCmAT|I!;Tٳgk~FyK/k-Zy, /o}د)~-k9^Km1Y}>E~˶ۨͫ`7B}޽cc/@˜dW|XqYv҈_ZZZ׮]TrQ{z0 ~;v- 2TTS_G>2ٳg{nRR[ʕ+h"KOǎ9dUL 3%`~TQ_`elڏos<̘1É<իW߼y&Mܹ3&&Nϧ|AcD^r<Ǩ"!r_vـuZQӤ|5:|pŊ RNJ N4Nۿ?'dffҎZ,q,~B2fLz>j?>WgΜYZӧ[KxY~=^RRR_Qe?!$~XӪi%{n#jĞe69ϏJ*1^nZre$N#)QV1Y'QXXBÇgΜ[`9s<~xĉ𒐐@7H:) !]9xv 61wM|r`;TN5kZh:u~: U FO_d}Tq@.\pUo'Q+WyEF9?!$~]Iri7oXxqa^K/<ٛ1fi?@f3v*B͛7'љHŏ$$~5jPgOƛOI_ʕ+-ř,#GrxDOڵQJM05ڵ6'7kL6unB~%)t _8O|, c1u?y~Hw6n8o<}Ŋ8u"?_X9؄cqi7=`ٴOٞ ,6YI }vQDxL^|xƽ{*"":[v-|҉4Zy$|$~aeϞ=/m/RWƛ7]jגB?vY[,׹{niQT[jQP3Lz8N|eHis^$~^2fKIVgɼyÆ nΏi^h_hBWbHo?1$ƌӣG##$~gϞڴQؓ&yC* SIII㭐gڢd1ޢK M{6&VVسHo%~BH|ؼys8R$~~BńԣxPJ@;vl׮ܶmwm߾=yRyP ~c5`WZշo_٩S'rx҆#V^OC-xŏ^ɰu2H'Yb]YI% L֭۱cǨ Ď"J5aI G? I9?OH>?24ZVʎJ ֫W *-t +'N2q&4SS_vžB@񊟭]qPs]{By PN5I|O_VPAfϞ}QK,3gPͮK.v݂z&NH޽{Ǒ)m)Phk][7oKd;yS /%̼qFvHfbIŋ^WGע/>I|-~_m<ÄG?g#F}:?ptkZӷ{9˷IB įD!djs~)/XowN;x&k[8h>G^FhboB^4°GXOk=dul4wMk–/~ܜc7;v_@#GOY$cPKw&'>j ?X4/o T,/;4.;L5lO /BH*h oyw.? |p$~sq=8aٌna|/~g^ !O@lNޞM?4kr`:Љ96UJxB )E7_oi//ɑoW?oB4Sǿ]z42/(B'S=Yꐼ³4z x|6+h ?.A)9 /BH$~eF/yqYv#iC͡gŦ}Mrg١!=?*o٨‹*Yvʛ !KOOOKKtK٬)TS["^JxBצM8oɓ'GM‹q90?{ҥ/$~? B7c 䍴ד'G'믿^jU$^v3id^PaOP‹dڱoXɓ]s߾}Y@ ?.A)99?o !ÇP/4N>}:=N,;T~!TMB=QS(EO'+?!O?_YBH)%A=س399%! ?#yW{~B%HKUa O'Ož,;eJ?МOOh/ B'B"4称" OhO'P3,%&&ڥKS t{BkV,oРAiӆ.!r͊%~OМߒ%Kx^:]BK9K.O !6KDǫďVժUiL2/`X.lU! #~L()Oc<'W>?!|aد圿 *ddd6B`Xk>МQn] ~*%s'6 5BUH{L >)Z$OМرcDT"pOa|Xj9?gOs16EA(8'h`E$~B=z]w*JT/)XvJv*MJ!D?`f]Q`cqj 1<*ǗB, !hO!B' GK!$~Bs~EE!$~o˗?|J]VuΝ_|q޽:fԈK"WmANޑ#G*V n<~Ubڵ :oN@!$~Q&Z3f3#hq-MbNPs=\p.ʄ;#Am~uoy&O壟BoF 0/yDGMFu-Ρ+,^pZrJ3o̘1Ln|+O'IUf7l0:c)zy,]eUd 6qD?f8TL)LHHRʍ7DEY#:Ӝ|>BHXlBܜ"gh"Dܒ>}XO7nl7;wu֬Yt!#Fb…vi'=ԩS}r|Mbs$~B9[&H|#C׼pBB8qB:ί# 'B_9FK! * #B_zc !|($Oy~;. 8]S$@D B)J(B)t3lhxB9gA{zH]o (_XUUWU+4RÚ?!\__(o'/..,W.S VVV#q~JǘTкrP#bý6`1 +0;Y|5;<eD2;mFEIupn| A6Ÿ~UUUre!'''X2R۩EA_}El)( g}IA)L&DH󠲢#tLSkfDrBFT?PǟQ||ڬӾᥪ*!8{PloooO,~r36  (rvvf[ܚ"sWĖrhҶ>XfItP;{ _[fo1/_U~U3cxNsܣ8 fl! ? ,// ɗ *FrR6Qh?= IL:0={zzJ'Y(Sa ;;;p8iϪ*{^~#aa\]J'h_-INd_G)~ euk6_𫪪ic>3YMv޲>G1Uq> Xۚ$mSb/{g.Jrs\)S-%7 7.CÌ%f9qnf.3zGoV{~хzcU55G7(?Р4~~ 7)2 IFjpʕ%ĸ*?҅+)7 ̖ KI eOJXQ!~zD_Q~l>@̀(&xӵXfCatP`t*y"AQ~/ĦbCbA)?Ӟ<zEQ~_B"~)$':@'/>`$BH7zH? ^D(}L>QHf!tOD%>zH?iOb_aa!G.7lpd0䳃+XvR =q`K*?87ׯ_(G7tS~~~qɓժU담i޼yVV+gOC?Asw%ES$^~lB_B͛7KpA~nN8g?YJ.\@3*+CͺuhIƍS/su%G}у'|'NQFfͨ>|85o)Z _{56q6 6k=S~"qq̙3gҥKz)F={L{ʕ+g͚8K.(#^,_%Kb.Q>6mpu#JLyW"?N>8qsn|j>}/vmDO)?Y~Lrݻ='88xqܸq\bݻw|nڵї;v5}E5mY95@G=uOD .D~Ĉa E)?'"H 8X~!F1GlԨQiBK4YV-Gl/lْy@Yt'O6mرc-[F&'T~GT~XIt%aÆ:uFBl8&LвeKjǏsH0÷fjp*# &RDԓwC T~G)?'ʏ؎hu]dXT_^9sX 9Q&;tP%!|!, ,ضm[k[>pDZ,U~=?h]9Q 6E^rCb8+VhժN%Yc{rIO)?'/C !Xt\"fO)?' ^iOS~"OT~ODOmΦ3{~\'S~[ ?p%>ԥ߾_]~3_f=O\YVm;N|r\X"S~}p 7z嵻|]czZɘ@y<#'/(W7A~A(?w,xQ~MF]LNAl] ʫ2sP~"I#RG߈I^"5m_A$"ObUjqG=ix{gP#.xQ~jOuX 5 ;;ٴ2O8P&#?>)y՞"_rKܥs~A=>y!Iёf'fRp/zuhL{$;_^-?/,tL{Q~O)?'S~f+S~OD)?(?Ӟ"O)?D3S~O)?sS~O)?gS(ʔ)7PHf!M~aOD)Ele˖ˣC&i$S~ --wzH?I8lۛ3)oӦM_}2g>=/"_/;n,]l~s}EL&#?bT }0Q~kآ~].f|eR1N\VA[ URnZp5ר5N]Ŵv1`>H1⋒>C`'6ltCnvF -׼ET7gɦ{* 7iТͫo45.DDxđ3Ư}o[vG+[%dY[?U~>i'~YvȲJ:}'&Z~"/ki;>Bﺏ@ u}Px\NUk#:i]Ox:{m.<>Jfኝ1-*%T Y=T~"qc~!p9dm8{YSb.$%:a&Q`]N|F r薛`YR8),*?'.xIN"\'Sp3OKB7\Œk7=@Vsϑz-%g Lq沭I$?'OćܙcUE. X Cyu<@0G3ҠȖJ#OL{K#ҋ q{3Ӟ"OD)?'Q~O$'iOD3Yԯ_qFrٳQ~ʯ9z}v|޼yw }Y|: ۷oS~AԩSofES:u4c ,B̄Tj֬ɻ͚5{h?=HOOꫯ<{lJJUW]աCD HN0!tܹsϟ?O{xg|o6'''dĉ?cn[N;v<K!o;@'_h͑,@|s*̙CP>s C#N:vΝ;OСCI0^{_|ßp=EE~~~ 7)Ჿ IFjpͪVG|C31Y Y-#_[f  ԫWߧA׮]322C PH JK%vEp޽'OL=?*xYKb TN2%.A)Z~KK%]"Sb8O> mƌCIիWnݚ¶mϟ*~aQ~/ IN aPXݬ;8@'iOD3)DD(6mJKKcc2e.+9X\(?_}޽x"N WP(?W,TX1555(!y?9@0)_c>~)$':@'P~=3 F-D }Q~/>aOzH? (?Y|A!'O)?8%luC6HFT~"O%n;q}=J:p~IV^9az~\~_n8v9g>ЖoOW~ł'R^Lyۥ+4Pʏ?w_׸n >W9*soR/3Q~uwO]4rn;/6xl|_eMÂ5AIygriz׸ۛ#qt-eo}rWpÕ1a%W\yCxs8\"EnXNS~""?|oҚor5zoάߴ>qփ[P&mZ:&i֠n;6sٶeܓs3k 26,ů~}6qr[si9]+S~.x) >҈Z[ny|gj8bjq1kiܜgB~j1%|I|,2|ʰ ?<ɋ^{õn_>bMS~R,(}`ZњOwiN$#Eٴ]»;琞BiXS6-Z0rO*^lq,x e;n}2#X Qρl{饗Zj]iӦ}% /˖-~ʔ)*39tB[Ʌ9\!$qT9!A &FXi׮#XwAGˋo>yA' \ɣwRq$/zuhF~xP/2:{i6bjrrrW&4s(R K7S䒧Ai/ý=S}iB)?DiOKZS~"O)?S~"OEQ~O)?'L{*?DS~O($B)?(?o&^~eʔo\~~a* ޔ(?WՋ_!`ʖ-ˡt1vX=`nH'ޔ(?Wp*B ֻwo =0ߛJ~WJ*O{rL_ }ޔ(?Wpz8BFq|dicc,DIYX*! @xD Q^,Wν9gq;sFg3OBBB'ڨ {kczO MiS]\\c;;;ŧ'G53߯)l۶,+,,3òaUM/0Fo ?O¯hpp{a{{/5%%u@j1\LL 0<YYY˃AC DQ|cBVzX^FBdG᧦=IIIAeee'3⻺8<::2s><<9p 899Ғ1>>Y*++ hllUn˼5LڨaJ< /**On $66$--mkkLڈG!1ڑ ~b4ۅSy~` 6 Hh`l r fggp`s3(Ȝ aWT>Er &'"R*wp쩦#dÏh)eO?cD$}+ X0)}i>*1tوa!Ⱦ:DX__dIT0Va0(~Rf =Fj ?؃آm?!" < ?Dx3ryy  zޞD" 2G NN_yLNGX(<|fe~{ffSS#ͣSS1hDq>)xjٍHtiFDDpH u">U)&h8h&<ӼkAi888L~mKϧO>O2|Sej;I,x<#:LCzU~@/2k:!-*))RSsIvC}a~jj ?z/j777?~nQT ( 7.\¼F-V։R 2t0PU4 >4O+|/.J)>G/AIrYo}OIX,d>qIDATx왁"Q{z!HHRJ "*=@BJRR!UUaFXUkmw|!eGd2Nj%!')dtzrl6V\.lv>}~jn녻KP>lT*φG" T*xPp\(Az9T*jzn7VU.yrL= 9λ7N?~&a$VY5Nd`h4o4f+B 6|b;(^uG6 zzE @@*m(c2s8fi ]p8lXpg(+|>h4 D"<HKe2Iv;Nt:yR|L!l6&Zv#dr,3Ј^HH@@P$P""RBTJ@(+w>L ΃;}99gk~~~9nǓ!/Q̏mf]XX@U7: -Y^^zgo'''QS:!{CnĦ6Bf"B/Ȳ(4CM.1e4SSSE񓹰]9IJ 4 ١3F&֑걭ח /p{{ 9Rzi{&b~~)_Ao,..ȡN;;;@sBl.Ol6.>=@2EP=Y5Tk<Rr-bvv? |0HS/pzz*@dr5fVWWDί]3&9%S]vVS/9~cc# "p!]ٯ]?LR) K$ė 3)kkk-[fޠ; ڻ+qpWz$6涰YZZ%f_{ Y1O/5^l!Ëϛ q>(9d%)D% BFFF@~Lseϛ٧54 ⚗"^a%l|61Sg;{~;ض(!(Q  *B@( "!A !I!EEB"[qViuԜXw~{m)=oe&QwGDDD4?ODDDODDDGSjNDJCw/TWnee-*RF q$ĔȖXׅb]P"zt`chNT%Ě!{555q:333'"š;ԇCmujGY2dqz!z.MAH4)@܉>9P4c>quTG/#" 4cV@('''kkk~4*!ė(dGv܌8S>D(FCj6_{zzOz ĝ3 7h~|DQQg A(4?f7<d~̟>K?oeu /D.X72-"M7?g-D,oe;?Fr]0a,V;%-DlG:W"O qW k"b/xG%^ {66ekmm=<<,&_.KzE]]F?gE;h3==Exyww) ]Kn'%Va =l^__WWWsLoo/_h~CCCc~ytt>E ߒ+; ]l1]C/]pBSl2D711ǿGui ",$PI*P(T$(@QUER !,)"`vx{oZ{g::33s=}<x"6JWd_"뫽Bˋb+j Ӧ~ummtie+M ONN\>&mhhנ:ʛ7 )ݥ/FLB r>4~.2CwS0NR֐Y!ʂxjx``@ ˽OCi카IC̉z}}=8kY2` tG~|j{{)P䤺Nĝ1tPiDGN&jM ؍ -9>>jӆ:gAh#cs(ۮy]&2=6+#R\0\__'3ĕUMӲ?0/_Mו}3z~R:& Z^^$E, m"0 W3*okJ1 K6qNė]S!f>}L~$?7\e_.nځMOO+/WTI8w@"՛?wY2O>cz~ :շ 2ehïȫ>??a>lj@N]P־"6բd+FGd7mL~eD`TaS:@.n $ }|yyqjswg *s.%jΦ&=L~5+___WZ4\\\DP@4QNA3}j__UNo+acc}(mt/!-_idkkWAbLdPb &p=yDkb jr6Y2BLWŴ#Ya.VO@B#R*3袋_|2oqqQE#TڬSIk:W6Tb3ф\VLL 2 *JIoh=2Rrzz j! K&=qJ1hQE3SAVD~>6?6 ԿH_ǵ! !RV*.?^ ԿH+' caXåsT'I~BXhKf2=x*s5lnj||'4λ/_$?=A͆Kǣ[Flw'ތR$?O=I~'<I~ў!I~zOg=OSSJ'ӧO?~/qܞ9XEr^iαKnu&/?!$?CxWl76mڥ4lc&0l<ɓ' BAAgyu۷Oi[_ yKMFOu|v;l0l` ӏWΟ#w'wܹ'xwarKMMElG,tg7b2ܹsg1N'e9{v-zW:GI9 m_%+?r(n[t{Pr~&nk .X5۷o8pKs=, D0_ӦMqdTT'I~D[5vw=dҌENV?4 )lml3р! ?___ȲvV譴ePPk?*]$?Oku;}x:6H`񗖥f.Yu,rx&n ו`VC%Q K!HD~"I~DQSh(|3=h0ث~EKOתSr~&J{ ߾Ubʄwh/? o۶$w{ 7ɾ@AfbU g)DŽhSr?ٲ3p"""^"9yuرqƈGdX{{~A=%?o(`ñSZIDբmg`&2'+׈qSm77lL$?!2ړGXӎVXmf+ᙓC'(< #H~Gyn^ZĂ"4c|anܸ2nl駟7|SNCyd'ĵk? ͲgB@~H~BOl޼YLUFX̙3Ԍ5ً2̛7gC]= ~L) !""",'|G?C۵kץKڵkFFF6me |/~VӾ;>>]֥{S1o9[7O(BOz>XPP`-[o֬YեweզYnn. ._Lى-(@xwz3N@߸qc&M6l`FX^݋a/%%%uy΅ N]BB8Oo=&Uzր{fd>]oknO$?ܜ>~>x.::#?Z x8q"D/^ȐQ1bm`˖ooLW!&i㱠i`j7onF[je*-7MB s}d5+9-)׷PO!$*Nʕƽ xV-#?!'͟f ^!n3g$?! lvF5OOUqM! !ؽ{رcgΜy1~K0oڅ9&Mn:3[/Oo߾Lr/Em6W|Q޽Yemɒ%f&YyqܹqY)e">ӷH~S~_|7$!s֫Wϲё#GB17dQ4Hc.`fOW^|mfϞm<Ĭl2S0kǰ825H+>>J1:f\4N>!p4o5;vNg8kf+V`~&,lONHH0$U,OCZȔeZ`g+y5*ͤ`f %C~l6a|1Fu233Xq-Zfee1k0 "_~O*a5kְ ")jM,=p@ڛ؈wϚ56 .aͷ EM2?wЁum8,ߛ'y5'+y:]Xv\~$6$3ɂ8xbmdMԄh#[&eĺ̯o֚Alk)7!#Vfy6vDž|*!-ͷZYr>|y]^gSSh&Z" G,PYv,mG`aP𞓓CfFc"<)YDmРADiӦMc;uɓMh9N-ZpzP3gK~hM6q2@w!o2/y5''9brah!$ </%BQ&H)M#?9>b=DwGԄ=z@ul+**21][O i@MΝ ]li9c t$?1/f4AǞ!B`G~[$911hvȑ#9:8)Zb)Q|#?dVi_iCsʕ{̷P`RN/E'6BW\q o#I ڴY6Hٟ0@R`%nou= $# !$?! !=BB()$?!B$?o&͛n2j9:j^ii07Won ;wԊر'' O_ڑԚvfk3b!})cES^reGeygr8SSO! CQANg>\v''y^Skݱqޝ>E^k&zK)N|9aՋ'yN޿}8r=wx_T$?!$?'<@~ox{`=IS2Ŕ l7;В cPyYݿՀ!$?'<@~ V7X5#c'm_iq$i_r6$?!,W6m4%%l /rѓ&MzZll2ݻ߿?5_|yҥ)SٹsӧO|2_`6رc'I~B8Y~Ν1fs]tQԩS1SÆ wڵalD9qDդI,}||m氉̙s…>$''sP%{×/_~ڵkKvD3f0H$*I~dѩFFe6'ORPPLּy󢢢3gťKղi`"۳gfFF7ڵk,,YMK/:u :t(** mٲQ`#?4/' |"3+JI%ƢA׏8 gV7!Q&ԩ#×O 4%AرcW\f͚;vA@/%?!))8O2+)/\]@fBld8ÇJe6cɱiz؎҄HRO^25'@hYaMOH~Ηў?<"#'i|7mڴGd,y'jȉ2aܑ#G6 ؄kw0gx'|nؖӀMvDzUSSH~B!w4h`NFB܆8)iQ޹sap*9١3ɯ U'fx "X /<OPj @=EtxxfxW-ODƻOODK~BOBB$?!I~P' YH~'' ѻ! 7>?!~wɯ2"9U%eɯ܏U%UMO I~B}~'' !I~BH~B$?$?OOOp?''' =' !I~B}~F^O~̓>xZj?މ7ԩSСC#Fߖ z~Bx09]FT6jt}=z>|9`ܸq?&OFȱc.]d^ąTܹsѢEQk~ڻwt޼y?# ._Ǧt5##M6TRp̙3gyf̷͈ɓfBp˖-잘a9f@@w/h?}~WFsDORRRt%}uȏ!Y{O޽ YStEVZZJ AÇk֬yuJN;O?477 /hwŊ&M;E~!& ;T-ănܚ||u<_ /LH+""Ȍmž~ K#zkړx񫯾:}4DT"9k/#Z?Nv?SO_og5EoeY)׀^"?ɏ;7ޘ?>Q 5n r m7n.qqqfe x^ D~|K.ti@1+wGlM`l^A2ou9vaoGfH-8ۢmn}R3yNo8g9,ywwǎk'5ioTKG_]z:tyl۶bqتU(G~ђLP8q \x8g$G{33a'/CTM^x%6)YK?@8*r& XpzAg|+=5gh:vɡ%y·;ȲeOqɯeCrF~8W^$TΞ=Լo9`TTl޼}JiSp &&eI cgZje} ĚQўBx}sD Oתs;>"۸-~3nO҂XC>yaO&$#lo_ hRSM=qEo`_ά'Mo*}%snm\τ},C! NϿ?߈@ u<|6WGn56wɚ VDj=|Y5JfLhOMG}y/l,%?)yX' ɯ)͠?x5l.A|6{*M"E6Ioy76M;y>8-J,NG %?) $?!FuI9Om[*6%?Y0 a.?!4KtըP'7x__5j5ZO?PB܏ҬY^=@z0<<Ȁj 7mz@&O\\\j,N2& |r㰬RѦM>( QҞntQJJ3ѕoX J%m(ZJJ԰x>`iu5kּ~Y '-1pl IZ5fڵcv7eA?B wH%%%Qݻw͚&M 3ƃ*)DL]^_esCVVœ+W;Q!ٳ B!22UV:D?Pi+8_^'?7`Rvwr%.\ 44v$azM ͆N!===**T6op@OO`ڵk %?!䧴OOBiO!$?OOO^~}acjlo8G?PB\?cӧOlΣ9p&g( !I~.I{hѢgϞRB87@'# O>'Bs#@OOsN77UN87ΐTS$?}t>Qp~'/΁a P 9O@OOs )Agy:8J~BH~J{ZT9Og{NG۝+|2JrbEEnL;vO;nZO$?!\%?w}_%3'Ξ}kC^jb6 6%p lԴ[ߢ[ii%?OSS/t_|ZjUWGjݱUiP~~Y v|#+ͥةynMqS,lܲy4&=/o»oubwL7o5D/?9'I~'K QڠQ6>Uu&kݨ{.Lۨ߰}=)My[ܑc`S vGy葇SRLaiĬ&I> ]+k$?~j?QQװ>'2[oI寽b]n Yܰ9mƢ%ˋ$9'Q#߲hB#?<ɋCMLi9kr'I~BD~fS: - 7omgfBL>RG 2{^ܩASFiXCM{Xsϔ hL#5+xkđą3XƤI%?=%? x%{?GߠQ:t l&q=| )O7fR 'I~"͌ /"U} !Y7$?O(?/BH~'eddJ~BH~W嗞޴iSMƌSt^{i6u/---((Bbbϯ]vddҞBx$?O~G;uwނGqqqmƌoN9882ܺub#? A4E={6*,,j֬YӵkW#իGEE5lGI~$?Ok۶n СDo/bhh(R0'))`ѢEgϞѣKJJׯ?`O#? x 'I~{7l03gq. l7g;vPjժzI~$?O[d zζjFA$JJJZhRƑ6#G4jRӓ'igӦMrss͛,X`%<(Q& ?v|>ʅK^*w+hvڶ<}L> gy:7Ox_%&**fm/99̙O΍3<{apI~ *16OݗӧgϞTV87P?כH~[u؊ hڵ Xؚ_b5Ynn JJJ$k׮On9-ZOpe 8ΊstҸ$?{7|͹sN<{!&&ơAqqqN)$qXr``U^|O<M3o1vos|;p>.+3᪓Ӡ.r/C%%\r%raRGӴga#~wn?mٲ%..#r~NLLl֬Yttup΍4Xn׫W;r(pٲe|1/f W.#CKH'2s!!I裏 5B޵kW D{Z4dV^8l޼y8*LJJ2OB48i؝Hy7{U̙3իpiړktq2R$LÈ!_'ϟQ Ґ٬YX0i۶m?,$RؑPq|a(ƙB6 B9 ̜9sϞ=(S2&^0\g \ouy)!b&~XM0:7B4g/Ԥ%О|֭[KKB ) Zucǎ(WNq! LG#Ӱ\gu饗\-BכH~ր6"KI=$<7nh0`3- (իI : e )P5S+I%1RN:,]g-(1(onki ˏ_.)$?3Γ+MzWdޭ6B^Po{@?LҞEpuqpQ/{I^>ǎ $?lL\i/I~n Ӟ\WVWu'?{/}*XF5;Q0ha3J*(h])ۛ[pj6;$|F0 :ӣ9 >>^G9<Jꆰ蠮9mOOO+.:b1000::ݻ'O"@t())"aYA4.:h䄺0LScdpҥ__*J{{;с> n:WLMM[˂s4;_$:gKKKnnE7#}yqqhΧn$:D% Q7.A)AC.l^^^h(1.vhADbА7i +D : : :ŋCK>\l)//':233Xklj 0 ;x;w\(Fӕ'6nXSSmCDśU^jUNN s}6oT0Ʞ+-6?<0Wޠ>oBB-AU(Ï4Ab&gSQt uli0V|ƣCtXY_z>}ڟAahB DP ҃VPՊ"`V tT^9hjj*++ӊԀZڃZ!c˖-G v`}Ն vڥ#Zڃ Бޮ_^&:uVs/,Va>jѣݳgРKhھwܹsG ]*9a|~}S$wJnVj T5 ?~ЧN%%% '/Iđ#GtԩSDgϞOGHk +ʐ`|\tpw !Ic"nWAņeQX@t^FvtLuX|%4Dﰈjn'zCCCmmTUUC]KEmj;55wu-4lѣ]_~k׮=+++u\=NTAw3TZ?4}z_nBU"DwDXϟ5%SSS^1445?4r zWub]=TGzO]*PO=wq&̯::`u@u@u PfX:^TT̰PPPP0:::`0cv]9$'Q4pXLmTeYc$-eaVu{{nE{Z< {IrTfX|;-qq=rT<<??.@<$?lcO{I* ߾xiDj3m6$ҸVtYmĝU%n[W3ӽ6:tL˪vRj貰(U@ @iE)ޢO|o~93w߈y{yw9<<Ң|aaa@@y9sgϞ]pQ0*Kȏ:d a:i8~a׮]siccY,S0lհPlyS5f_mڴݻt006Z2;/dFq٢aTEJP^^.>;|p Q16 fs^ArL 0Lnx5j`/#dZ !> 0L,YҲeK'`@kв0#̼bHkXa:UUUtNh0ɞ-,<3!9`a ?~w}֭[ ;́n0Z L 0\⺨Ü9sruq_}U$nVl/y:a8uQ;w;h|-=o)p9U?}Ɵy:a:\jjjcEQ=/S0 S>{\xϟ7u0up ( 0LΝ;w}_صkWee%}auI.[ҥKpO2;v:L 0\â6{l5._S$!/^ԱC0 (:ِou ,ML 0 {ɣ0||RիW^O4i~?SN4߿Ϛ:a:h`޽uo߾͛oW#F« Sװ0 øA9Xr%^틑̘1ӂ큳k֬Ec~~mYY~ aqg}k׮mU:8aÌ3Fy)ޜV㏛5k&-ZNM6qv޼yF@)ϭ  믿F_~i$,l7>ZSS0 Srm8F4+U9©.]}9(//~T @ςװ:ujZ`")*|N׮]A-`!'l%0˗SSX0 p "Q7I";ruqH4Z=FnJd`8sL<#ƛ-[ЇVƠhSSj܋GĢDaU/nH)u`Hlnu.WӧҥKq8pвh"<`MA(KaB U?w0 !{JKuL0A5hEd7xcpႾTb+H=)SСgϞĿ2=5%D 0mkN]5kۤiĥlYhTRj{N_=Q4P94"BO߿<@LWTTWDE0 QڕԲK,AP_cZ꟞jצ>WQk4 gϞM EMi$L$S /s۶mSS"S0 "z!$Ou@S*>1c'6֯_Uv@OZvdtl-SS"S0ۧO}gMit Ib YZ rpY\÷{ڪ||Da+ 6mws"( s+0쒒% M2>UV|sr>Dٳg.\dq`X=0#[ly~{n"Uҵ|~0{iR,y8ԡOD2,uh$  `G9>|n:g#r_Âv5g ey< .caMYIJ◞'NDm*!cNY,Mr/ڴiӻw!ucrݔ,˳yK/7xW:7.]"L)>\ ֐Ԓ P#ly.{!Qfa8m4iWڨQ#M'`edY-Ϗ5m*P}&o۶-Tg) `gzoݻjuM<ׁm':Pj'#sly.::lݺ[>w$ޗu? SSCmm0/SɳsVU:$l$9BbZ ~0u0uhY[g0xqcSuQpE{^Ŭ(BD#Ζ(iH:|}̱sm{ ?{3g {\bWi`QFTG(t)KITů* ly4`@ iW};kX(oV,4klȑ}QBJSu֍۶mʼnSQWQIgm޼ـ4({; Mx .Y-[ѹsLg:::gmZ0B̲8@q;2&Xd.ϧROMPnY9Ť$3f.`luxG}eeS++??t{졘E3MLLp} wsݐ650K@xA^yzS@;G vM68o<2q-_'\mG:@2ZhAٽ9b)0aBضyuDm!YFERS1kod`~X65ǜ@ u:6y~DyyuUɆpۧW^6tEH!A %N]N1dPpd6+J H… $x">!~|7y䛥BDB o [dH25bS YJp_LJ}6$`$;h켐z"?)]ȁ G)ҪGU‘t9w+ u]@ n:k;_o ~GW4{ 6w#>i6.L'##?i wє)S"AbW/Mry Ûw = XvmH[ u\gv+Ȳ'Ѫ-8D޶m\  ۷/P=>Wr} re(x[3P>iӦ<xX^=$`$Qy^ :A]7^smO:s,x'Iz={Ͼ\޼E:n!J lEFEU( ޠnow.aoېoРAoQg-[+8[@t,nW-;he0r ,Pu_2ر#:xK鯈\C@$R.0#( @ԩS!O.!;s'Os$V=ਫ਼DeѢE!1fo˳o߳0 V 9Rk!mxA7ix4@'1p@Z\- o*E D+K2 3cKb%I~W83i%ަxw5;r 3BABB~;_ Q=(FohP~|3b`XMb䰮$Xh%%7~* AN~LuP9^s<ȷ4=pR0!W}v$ ~UUU[D5a6lA=AqW2}L\">=ԹsaxBiIu˹lϞ=SW }4b:&B.H"u4 5u!|81"?GK!kG=q„ 0N"T 3up^:كlt̅` 򋔽A!ԁwރ5W {\W'/ʐ_=u.-x~:C0-HzKa/y?`/C}fϞD %䊐)Vu3t+]+Lł"JQ[-ϦA^@38ۗ !!e~LB^:DA8r,WIJBNz _M"G8I^Iq!)T Ѭ .27 Z :><:hӍߚ=<O _KwiӦՙM0-0C:j~PPpƌkXq$x:u*Ѝg Zډ,d>IFB7%a#RUF84G-Ϧ,;d[s={|)P޽]tc!+")F+gF-[(NL\qD> ڧU"$F4o˳`i_^g3ڗh\â`:Vi^g3 &L J:x% 8ʳ0L 4sja ԰0Ҽ^(i(B`Fy 4׹^``U9ihm4Ҭً;NܶvkX$p^S4Q ?4Mm?0g4M2:{muH2%J\p 4*:huN3?h\߾֕3 WLW #u6Ύؿ}{ٯ~zSڼa޼yT -`uг^Sdo+n"ۃU9p6Il|a {yqM 2c`u62QbDyks^~ާ \k1QFѵwމ`YgV;ѧigsUܴbmڄF3qB~4(/B=1\ I aT<uk׮6uuNQ9<%964݊?PccY,kPn[/H $`U% IyRװPlyS~! 0m dv^Ȍ,A´5jMϲo`X)ρ:8C:RdT2FyŐ@U~IYOLL9sO 11 DMSS%KlRIݘʼbHNaR ;dYML\]]mYW/1 1fyN=u0u8~8[>̆LG31CaajXN̙ 7ꫯ2->+y:a`\uعs'0zo&C,0 SSAMM v~Ee 0u0uϺw%\(***Ο?o`PG 2LLΝ;w}_صkWee%}U+aCѠ7e˖]t ()SиcSakXfϞͭ._S$!/^ 0 AZ{%^_o߼P<뜙::a:Aa޽uo߾͛o_zj: |I&M֯_wԉ:\YS0 SsrJ+Ͼ}>fi5k`eر~[VV3C:_1"W #,Laa3f9rSxZ?n֬[|h-KM6qv޼y7B y-37n>cڵ#PЭ Su0 0u  F jGT.] >x`^Μ9uA l߾=mԁ嗑F&H6ɂa10Ȟ߱cG1Ν;w\1@/tHBjXnIuHLL 0LHT:p!ׯ_֭[ øq8;n:Q&ٲe .1tP~0!~4u(y:a<)po%BqYԁL|=zn/݁t0Yfa O<֭[PMʔ(3bk~aV(: "3g΄:`Q bB򗤿,6lcY}@:C:_-X@#8p |LaEMN>~T @ς'|/8p v^OqAZ@ְ0up^0 /$}ԫGM:5w-LL 05O-5Ɔw}wU$!k׮p1!%O5|rE5/S0.Qg~8Uy&O Kho\xO.2}t|6 BR/="ȋm^aM !U/:P-[ЇY(Ba[@l<"-y:aT<񄨃3s'Nܴ|jS@+:#k$ R /u].MdtR\Q̴,Zc:0@GݔaÆ(TܹsXϫhkXagjMs .u`B<@:h<&LPi.T䟡v mpo1ɿɫTNL1UEp)SСgϞdP-J[uSWR07_y]ICLdtJ9Hu"~|&Փ/r>G>Q$ՠ_ZZ ΃t Ƕ+**bX++0 c5S++$:86sݎF#mf'{.;U`]̰z 8>CX,3LjV߂u!4`>h6x|irNZS`]DO+J{or EtDns^8P΋*m]D :DD"*Tٷ."^{y>@Uj\׿9~u]o{w9q8(PEc 8ƀ7HX|c;Bۅn%Kv7̎%$]__OI>2 ;Hb?3#>J1;[I11eӅk.s93njN=Fˋ$wziz|U|.gFӛ7Iu]+iIUUE#I3"ɉ>N$Ql'g>3{w+aSV>xooo;;;s"əqrVrwLZ9LxCw___8'ފ9uz!emA u"i ty\{`]NE78'N okyj2{.'!EntK\w,~4S|O{e'K)9pn?Ew sCIOo͂}w;mz\i(vQ jeIWWW1pyy[Ü`B%G'Exh̥ۖ@0bt &JHqȜPUiIgN(|pRܯ/ T Wzx,6P:D>TJzQtxxohRڶ-L=Z|7}Oi8.Ol{VU]Y^Ka û1qIENDB`puma-3.12.4/docs/nginx.md000066400000000000000000000045271362626474300151650ustar00rootroot00000000000000# Nginx configuration example file This is a very common setup using an upstream. It was adapted from some Capistrano recipe I found on the Internet a while ago. ``` upstream myapp { server unix:///myapp/tmp/puma.sock; } server { listen 80; server_name myapp.com; # ~2 seconds is often enough for most folks to parse HTML/CSS and # retrieve needed images/icons/frames, connections are cheap in # nginx so increasing this is generally safe... keepalive_timeout 5; # path for static files root /myapp/public; access_log /myapp/log/nginx.access.log; error_log /myapp/log/nginx.error.log info; # this rewrites all the requests to the maintenance.html # page if it exists in the doc root. This is for capistrano's # disable web task if (-f $document_root/maintenance.html) { rewrite ^(.*)$ /maintenance.html last; break; } location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; # If the file exists as a static file serve it directly without # running all the other rewrite tests on it if (-f $request_filename) { break; } # check for index.html for directory index # if it's there on the filesystem then rewrite # the url to add /index.html to the end of it # and then break to send it to the next config rules. if (-f $request_filename/index.html) { rewrite (.*) $1/index.html break; } # this is the meat of the rack page caching config # it adds .html to the end of the url and then checks # the filesystem for that file. If it exists, then we # rewrite the url to have explicit .html on the end # and then send it on its way to the next config rule. # if there is no file on the fs then it sets all the # necessary headers and proxies to our upstream pumas if (-f $request_filename.html) { rewrite (.*) $1.html break; } if (!-f $request_filename) { proxy_pass http://myapp; break; } } # Now this supposedly should work as it gets the filenames with querystrings that Rails provides. # BUT there's a chance it could break the ajax calls. location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ { expires max; break; } # Error pages # error_page 500 502 503 504 /500.html; location = /500.html { root /myapp/current/public; } } ``` puma-3.12.4/docs/plugins.md000066400000000000000000000026721362626474300155220ustar00rootroot00000000000000## Plugins Puma 3.0 added support for plugins that can augment configuration and service operations. 2 canonical plugins to look to aid in development of further plugins: * [tmp\_restart](https://github.com/puma/puma/blob/master/lib/puma/plugin/tmp_restart.rb): Restarts the server if the file `tmp/restart.txt` is touched * [heroku](https://github.com/puma/puma-heroku/blob/master/lib/puma/plugin/heroku.rb): Packages up the default configuration used by puma on Heroku Plugins are activated in a puma configuration file (such as `config/puma.rb'`) by adding `plugin "name"`, such as `plugin "heroku"`. Plugins are activated based simply on path requirements so, activating the `heroku` plugin will simply be doing `require "puma/plugin/heroku"`. This allows gems to provide multiple plugins (as well as unrelated gems to provide puma plugins). The `tmp_restart` plugin is bundled with puma, so it can always be used. To use the `heroku` plugin, add `puma-heroku` to your Gemfile or install it. ### API At present, there are 2 hooks that plugins can use: `start` and `config`. `start` runs when the server has started and allows the plugin to start other functionality to augment puma. `config` runs when the server is being configured and is passed a `Puma::DSL` object that can be used to add additional configuration. Any public methods in `Puma::Plugin` are the public API that any plugin may use. In the future, more hooks and APIs will be added. puma-3.12.4/docs/restart.md000066400000000000000000000055571362626474300155320ustar00rootroot00000000000000# Restarts To perform a restart, there are 3 builtin mechanisms: * Send the `puma` process the `SIGUSR2` signal * Send the `puma` process the `SIGUSR1` signal (rolling restart, cluster mode only) * Use the status server and issue `/restart` No code is shared between the current and restarted process, so it should be safe to issue a restart any place where you would manually stop Puma and start it again. If the new process is unable to load, it will simply exit. You should therefore run Puma under a process monitor (see below) when using it in production. ### Normal vs Hot vs Phased Restart A hot restart means that no requests will be lost while deploying your new code, since the server socket is kept open between restarts. But beware, hot restart does not mean that the incoming requests won’t hang for multiple seconds while your new code has not fully deployed. If you need a zero downtime and zero hanging requests deploy, you must use phased restart. When you run pumactl phased-restart, Puma kills workers one-by-one, meaning that at least another worker is still available to serve requests, which lead to zero hanging requests (yay!). But again beware, upgrading an application sometimes involves upgrading the database schema. With phased restart, there may be a moment during the deployment where processes belonging to the previous version and processes belonging to the new version both exist at the same time. Any database schema upgrades you perform must therefore be backwards-compatible with the old application version. If you perform a lot of database migrations, you probably should not use phased restart and use a normal/hot restart instead (`pumactl restart`). That way, no code is shared while deploying (in that case, `preload_app!` might help for quicker deployment, see ["Clustered Mode" in the README](../README.md#clustered-mode)). ### Release Directory If your symlink releases into a common working directory (i.e., `/current` from Capistrano), Puma won't pick up your new changes when running phased restarts without additional configuration. You should set your working directory within Puma's config to specify the directory it should use. This is a change from earlier versions of Puma (< 2.15) that would infer the directory for you. ```ruby # config/puma.rb directory '/var/www/current' ``` ### Cleanup Code Puma isn't able to understand all the resources that your app may use, so it provides a hook in the configuration file you pass to `-C` called `on_restart`. The block passed to `on_restart` will be called, unsurprisingly, just before Puma restarts itself. You should place code to close global log files, redis connections, etc. in this block so that their file descriptors don't leak into the restarted process. Failure to do so will result in slowly running out of descriptors and eventually obscure crashes as the server is restarted many times. puma-3.12.4/docs/signals.md000066400000000000000000000057311362626474300155000ustar00rootroot00000000000000The [unix signal](http://en.wikipedia.org/wiki/Unix_signal) is a method of sending messages between [processes](http://en.wikipedia.org/wiki/Process_(computing)). When a signal is sent, the operating system interrupts the target process's normal flow of execution. There are standard signals that are used to stop a process but there are also custom signals that can be used for other purposes. This document is an attempt to list all supported signals that Puma will respond to. In general, signals need only be sent to the master process of a cluster. ## Sending Signals If you are new to signals it can be useful to see how they can be used. When a process is created in a *nix like operating system it will have a [PID - or process identifier](http://en.wikipedia.org/wiki/Process_identifier) that can be used to send signals to the process. For demonstration we will create an infinitely running process by tailing a file: ```sh $ echo "foo" >> my.log $ irb > pid = Process.spawn 'tail -f my.log' ``` From here we can see that the tail process is running by using the `ps` command: ```sh $ ps aux | grep tail schneems 87152 0.0 0.0 2432772 492 s032 S+ 12:46PM 0:00.00 tail -f my.log ``` You can send a signal in Ruby using the [Process module](http://www.ruby-doc.org/core-2.1.1/Process.html#kill-method): ``` $ irb > puts pid => 87152 Process.detach(pid) # http://ruby-doc.org/core-2.1.1/Process.html#method-c-detach Process.kill("TERM", pid) ``` Now you will see via `ps` that there is no more `tail` process. Sometimes when referring to signals the `SIG` prefix will be used for instance `SIGTERM` is equivalent to sending `TERM` via `Process.kill`. ## Puma Signals Puma cluster responds to these signals: - `TTIN` increment the worker count by 1 - `TTOU` decrement the worker count by 1 - `TERM` send `TERM` to worker. Worker will attempt to finish then exit. - `USR2` restart workers. This also reloads puma configuration file, if there is one. - `USR1` restart workers in phases, a rolling restart. This will not reload configuration file. - `HUP` reopen log files defined in stdout_redirect configuration parameter. If there is no stdout_redirect option provided it will behave like `INT` - `INT` equivalent of sending Ctrl-C to cluster. Will attempt to finish then exit. - `CHLD` ## Callbacks order in case of different signals ### Start application ``` puma configuration file reloaded, if there is one * Pruning Bundler environment puma configuration file reloaded, if there is one before_fork on_worker_fork after_worker_fork Gemfile in context on_worker_boot Code of the app is loaded and running ``` ### Send USR2 ``` on_worker_shutdown on_restart puma configuration file reloaded, if there is one before_fork on_worker_fork after_worker_fork Gemfile in context on_worker_boot Code of the app is loaded and running ``` ### Send USR1 ``` on_worker_shutdown on_worker_fork after_worker_fork Gemfile in context on_worker_boot Code of the app is loaded and running ``` puma-3.12.4/docs/systemd.md000066400000000000000000000220711362626474300155240ustar00rootroot00000000000000# systemd [systemd](https://www.freedesktop.org/wiki/Software/systemd/) is a commonly available init system (PID 1) on many Linux distributions. It offers process monitoring (including automatic restarts) and other useful features for running Puma in production. ## Service Configuration Below is a sample puma.service configuration file for systemd, which can be copied or symlinked to /etc/systemd/system/puma.service, or if 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. ~~~~ ini [Unit] Description=Puma HTTP Server After=network.target # Uncomment for socket activation (see below) # Requires=puma.socket [Service] # Foreground process (do not use --daemon in ExecStart or config.rb) Type=simple # Preferably configure a non-privileged user # User= # The path to the puma application root # Also replace the "" place holders below with this path. WorkingDirectory= # Helpful for debugging socket activation, etc. # Environment=PUMA_DEBUG=1 # The command to start Puma. This variant uses a binstub generated via # `bundle binstubs puma --path ./sbin` in the WorkingDirectory # (replace "" below) ExecStart=/sbin/puma -b tcp://0.0.0.0:9292 -b ssl://0.0.0.0:9293?key=key.pem&cert=cert.pem # Variant: Use config file with `bind` directives instead: # ExecStart=/sbin/puma -C config.rb # Variant: Use `bundle exec --keep-file-descriptors puma` instead of binstub Restart=always [Install] WantedBy=multi-user.target ~~~~ See [systemd.exec](https://www.freedesktop.org/software/systemd/man/systemd.exec.html) for additional details. ## Socket Activation systemd and puma also support socket activation, where systemd opens the listening socket(s) in advance and provides them to the puma master process on startup. Among other advantages, this keeps listening sockets open across puma restarts and achieves graceful restarts, including when upgraded puma, and is compatible with both clustered mode and application preload. **Note:** Socket activation doesn't currently work on jruby. This is tracked in [#1367]. To use socket activation, configure one or more `ListenStream` sockets in a companion `*.socket` unit file. Also uncomment the associated `Requires` directive for the socket unit in the service file (see above.) Here is a sample puma.socket, matching the ports used in the above puma.service: ~~~~ ini [Unit] Description=Puma HTTP Server Accept Sockets [Socket] ListenStream=0.0.0.0:9292 ListenStream=0.0.0.0:9293 # AF_UNIX domain socket # SocketUser, SocketGroup, etc. may be needed for Unix domain sockets # ListenStream=/run/puma.sock # Socket options matching Puma defaults NoDelay=true ReusePort=true Backlog=1024 [Install] WantedBy=sockets.target ~~~~ See [systemd.socket](https://www.freedesktop.org/software/systemd/man/systemd.socket.html) for additional configuration details. Note that the above configurations will work with Puma in either single process or cluster mode. ### Sockets and symlinks When using releases folders, you should set the socket path using the shared folder path (ex. `/srv/projet/shared/tmp/puma.sock`), not the release folder path (`/srv/projet/releases/1234/tmp/puma.sock`). Puma will detect the release path socket as different than the one provided by systemd and attempt to bind it again, resulting in the exception `There is already a server bound to:`. ## Usage Without socket activation, use `systemctl` as root (e.g. via `sudo`) as with other system services: ~~~~ sh # After installing or making changes to puma.service systemctl daemon-reload # Enable so it starts on boot systemctl enable puma.service # Initial start up. systemctl start puma.service # Check status systemctl status puma.service # A normal restart. Warning: listeners sockets will be closed # while a new puma process initializes. systemctl restart puma.service ~~~~ With socket activation, several but not all of these commands should be run for both socket and service: ~~~~ sh # After installing or making changes to either puma.socket or # puma.service. systemctl daemon-reload # Enable both socket and service so they start on boot. Alternatively # you could leave puma.service disabled and systemd will start it on # first use (with startup lag on first request) systemctl enable puma.socket puma.service # Initial start up. The Requires directive (see above) ensures the # socket is started before the service. systemctl start puma.socket puma.service # Check status of both socket and service. systemctl status puma.socket puma.service # A "hot" restart, with systemd keeping puma.socket listening and # providing to the new puma (master) instance. systemctl restart puma.service # A normal restart, needed to handle changes to # puma.socket, such as changing the ListenStream ports. Note # daemon-reload (above) should be run first. systemctl restart puma.socket puma.service ~~~~ Here is sample output from `systemctl status` with both service and socket running: ~~~~ ● puma.socket - Puma HTTP Server Accept Sockets Loaded: loaded (/etc/systemd/system/puma.socket; enabled; vendor preset: enabled) Active: active (running) since Thu 2016-04-07 08:40:19 PDT; 1h 2min ago Listen: 0.0.0.0:9233 (Stream) 0.0.0.0:9234 (Stream) Apr 07 08:40:19 hx systemd[874]: Listening on Puma HTTP Server Accept Sockets. ● puma.service - Puma HTTP Server Loaded: loaded (/etc/systemd/system/puma.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2016-04-07 08:40:19 PDT; 1h 2min ago Main PID: 28320 (ruby) CGroup: /system.slice/puma.service ├─28320 puma 3.3.0 (tcp://0.0.0.0:9233,ssl://0.0.0.0:9234?key=key.pem&cert=cert.pem) [app] ├─28323 puma: cluster worker 0: 28320 [app] └─28327 puma: cluster worker 1: 28320 [app] Apr 07 08:40:19 hx puma[28320]: Puma starting in cluster mode... Apr 07 08:40:19 hx puma[28320]: * Version 3.3.0 (ruby 2.2.4-p230), codename: Jovial Platypus Apr 07 08:40:19 hx puma[28320]: * Min threads: 0, max threads: 16 Apr 07 08:40:19 hx puma[28320]: * Environment: production Apr 07 08:40:19 hx puma[28320]: * Process workers: 2 Apr 07 08:40:19 hx puma[28320]: * Phased restart available Apr 07 08:40:19 hx puma[28320]: * Activated tcp://0.0.0.0:9233 Apr 07 08:40:19 hx puma[28320]: * Activated ssl://0.0.0.0:9234?key=key.pem&cert=cert.pem 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 "" place holders below with this path. WorkingDirectory= # The command to start Puma # (replace "" below) ExecStart=bundle exec puma -C /shared/puma.rb --daemon # The command to stop Puma # (replace "" below) ExecStop=bundle exec pumactl -S /shared/tmp/pids/puma.state stop # Path to PID file so that systemd knows which is the master process PIDFile=/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 [Install] WantedBy=multi-user.target ~~~~ ### capistrano3-puma By default, [capistrano3-puma](https://github.com/seuros/capistrano-puma) uses `pumactl` for deployment restarts, outside of systemd. To learn the exact commands that this tool would use for `ExecStart` and `ExecStop`, use the following `cap` commands in dry-run mode, and update from the above forking service configuration accordingly. Note also that the configured `User` should likely be the same as the capistrano3-puma `:puma_user` option. ~~~~ sh stage=production # or different stage, as needed cap $stage puma:start --dry-run cap $stage puma:stop --dry-run ~~~~ [Restart]: https://www.freedesktop.org/software/systemd/man/systemd.service.html#Restart= [#1367]: https://github.com/puma/puma/issues/1367 puma-3.12.4/examples/000077500000000000000000000000001362626474300143765ustar00rootroot00000000000000puma-3.12.4/examples/CA/000077500000000000000000000000001362626474300146615ustar00rootroot00000000000000puma-3.12.4/examples/CA/cacert.pem000066400000000000000000000025331362626474300166300ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDxzCCAq+gAwIBAgIBADANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJVUzEO MAwGA1UECgwFbG9jYWwxDTALBgNVBAsMBGFlcm8xCzAJBgNVBAMMAkNBMB4XDTEy MDExNDAwMTcyN1oXDTE3MDExMjAwMTcyN1owOTELMAkGA1UEBhMCVVMxDjAMBgNV BAoMBWxvY2FsMQ0wCwYDVQQLDARhZXJvMQswCQYDVQQDDAJDQTCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAN8bteVsrQxfKHzYuwP1vjH2r6qavPK/agCK bbPXZmWfUUGjL4ZT4jmnz/B6QNBBKTE/zWcuLXvyRR2FUCi8c5itUvraJVIuBPT/ lvAZfbyIMpdHG1RPwA6jgTTXm7hnfZc0lCzsFRLk106XrjKeIkZOWffnVLNS2dyH X51/yZAS8wFyfx58gabC3hvzJLWw/fDSB/qQsOjp5XCCrP30ILPads/P2dEFNZ1M bjGNErVjmEWEorbUsh6Gu3OyElicVf9hgHspFYNwl1rc5IX7Z5eQM9Yd/Lm1mlvU iM839ZPn2UOtS9EDdeeZImTSALSUoFJjMdt8+synSDUuGPczUzECAwEAAaOB2TCB 1jAPBgNVHRMBAf8EBTADAQH/MDEGCWCGSAGG+EIBDQQkFiJSdWJ5L09wZW5TU0wg R2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBReztszntEK4mwESl/gPjc8 VKU2ljAOBgNVHQ8BAf8EBAMCAQYwYQYDVR0jBFowWIAUXs7bM57RCuJsBEpf4D43 PFSlNpahPaQ7MDkxCzAJBgNVBAYTAlVTMQ4wDAYDVQQKDAVsb2NhbDENMAsGA1UE CwwEYWVybzELMAkGA1UEAwwCQ0GCAQAwDQYJKoZIhvcNAQEFBQADggEBABC6pRY+ c+MKGG6hWv9FKTW5drw/9bfKxl+dVcKPP5YWuoAMtStkCVnDleQ7K2oN4o7kwr7Q cU3mmYJZjqRu43JBebzupBGKqe/mNWGN0EuCMT7khFEXbO3bwpcL0fhCO7+RZccx GF/LKglLgQSE+/SKOHlHdJZlS3EgPghrtoSiptx9ytXzkgCoEKypbAEmcArWvzzF 81ZYjkLAwCrrB/qNAKnI0AKXMCiqnZu+8a16p5z+HGCjpTLB3NQ3YlyFF0jbr/ow R1Fb07t0uO2o22nuua+iK8lKqWLE6eQUIu/YB6DMEgjk+D6of+WQ+38GC35QyPKA 9nQ8kMf2RkiGN6M= -----END CERTIFICATE----- puma-3.12.4/examples/CA/newcerts/000077500000000000000000000000001362626474300165135ustar00rootroot00000000000000puma-3.12.4/examples/CA/newcerts/cert_1.pem000066400000000000000000000021131362626474300203700ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC/jCCAeagAwIBAgIBATANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJVUzEO MAwGA1UECgwFbG9jYWwxDTALBgNVBAsMBGFlcm8xCzAJBgNVBAMMAkNBMB4XDTEy MDExNDAwMTcyN1oXDTEzMDExMzAwMTcyN1owSDELMAkGA1UEBhMCVVMxDjAMBgNV BAoMBWxvY2FsMQ0wCwYDVQQLDARhZXJvMQswCQYDVQQLDAJDQTENMAsGA1UEAwwE cHVtYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxkKjXHIYV4CVFB4YZuVk sXqdb7X9+igKPSkxFZoyjwW+AGiN27OwTvETLQiPMaB1WiJtDF9NEmlwYXl4dHWz 5QPVJtQ5ud7FdCJWBUc+K6LS4xwixwis/FNZVfVcyxkR+cm7mVwxwrxle1lJasoJ ouqtVePdt+j+9hcJfLIaZD8CAwEAAaOBhTCBgjAMBgNVHRMBAf8EAjAAMDEGCWCG SAGG+EIBDQQkFiJSdWJ5L09wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G A1UdDgQWBBQDrltCvbaqNl/TvFNb/NEIEnbJ2jALBgNVHQ8EBAMCBaAwEwYDVR0l BAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADggEBAF8xrbIbIz7YW6ydjM6g gesu8T6q5RcJo9k2CWhd4RgJdfVJSZbCtAAMoRQTErX9Ng6afdlMWD1KnCfbP0IJ dq+Umh1BMfzhpYR35NPO/RZPR7Et0OMmmWCbwJBzgI6z8R8qiSuR/to6C7BjiWzo rp7S2fenkB6DfzZvHvIDojQ0OpnD2oYBOn/UyAma4I7XzXWe9IIUMARjS5CYZsv9 HBU3B+e5F9ANi3lRc7x5jIAqVt292HaH+c1UCn0/r/73cW1Z/iNYA9PgS2QKdmYq b3oQRFk0wM5stNVsrn7xGftmOETv8pD6r4P8jyw7Ib+ypr10WrFOm7uOscPS4QE2 Mf0= -----END CERTIFICATE----- puma-3.12.4/examples/CA/newcerts/cert_2.pem000066400000000000000000000021131362626474300203710ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC/jCCAeagAwIBAgIBAjANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJVUzEO MAwGA1UECgwFbG9jYWwxDTALBgNVBAsMBGFlcm8xCzAJBgNVBAMMAkNBMB4XDTEy MDExNDAwMjcyN1oXDTEzMDExMzAwMjcyN1owSDELMAkGA1UEBhMCVVMxDjAMBgNV BAoMBWxvY2FsMQ0wCwYDVQQLDARhZXJvMQswCQYDVQQLDAJDQTENMAsGA1UEAwwE cHVtYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArxfNMp+g/pKhsDEkB3KR 1MAkbfnN/UKMvfXwlnXpz7YX1LHHnMutiI/PqymAp6BPcu+umuW2qMHQyqqtyATm Z9jr3t837nhmxwG1noRaKRtsckn9FD43ZlpPg0Q5QnhS4oOsXwJzilqPjdDFYrKN 3TSvIGM2+hVqpVoGYAHDKbMCAwEAAaOBhTCBgjAMBgNVHRMBAf8EAjAAMDEGCWCG SAGG+EIBDQQkFiJSdWJ5L09wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G A1UdDgQWBBTyDyJlmYBDwfWdRj6lWGvoY43k9DALBgNVHQ8EBAMCBaAwEwYDVR0l BAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADggEBAIbBVfoVCG8RyVesPW+q 5i0wAMbHZ1fwv1RKp17c68DYDs0YYPi0bA0ss8AgpU6thWmskxPiFaE6D5x8iv9f zkcHxgr1Mrbx6RLx9tLUVehSmRv3aiVO4k9Mp6vf+rJK1AYeaGBmvoqTBLwy7Jrt ytKMdqMJj5jKWkWgEGgTnjzbcOClmCQab9isigIzTxMyC/LjeKZe8pPeVX6OM8bY y8XGZp9B7uwdPzqt/g25IzTC0KsQwq8cB0raAtZzIyTNv42zcUjmQNVazAozCTcq MsEtK2z7TYBC3udTsdyS2qVqCpsk7IMOBGrw8vk4SNhO+coiDObW2K/HNvhl0tZC oQI= -----END CERTIFICATE----- puma-3.12.4/examples/CA/private/000077500000000000000000000000001362626474300163335ustar00rootroot00000000000000puma-3.12.4/examples/CA/private/cakeypair.pem000066400000000000000000000033271362626474300210130ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,8FE374A296255ED1 g6YSW6TUA/9dSscxCWPm11bG6DedWJ6fanU6V7O2n9WbGOLE0ogz877D/5gPr94+ WJHnCb0O4gyKQA307XA9nq+HAPTyJFKroEz1CPXVrITV8AO+vJ/PUc1y1LQ1ymMk fcvI3ZNdbDBr7OL7luYch7qoVULJ4kwJTU7WT9XzINiSnS3Ccqh6ZEPFyKIcxP2s 11WkpxdDJ911nCXVUoa9Hd5tQk7mHZuf7XL01up08SDobx/imaU9VN8QQG6AFE55 jVtfv7MxP+9gHmHQxuhYuDMnu5GIwuJPFHvI7Gi9jcwvee/GhcKBnKdpFc92fJJ8 +TIqR2D21EHDBoep1fMGgbPOl+9z1hdE78Sj6tHwjeRF93mhJWyYNQWQ5ViKLnoF j11idWOXwkOCFttRBMd74QG6GyxTvs8FNDOXmm361Muk94a4fbKRJvKvYZlBnYKu fOmJNFf2zEVVHjBCbvM4swAT09cWLxRMRTiFb5y7QAEmtFO4WLavlmnNCdMq/uC4 CpFqGtoiaCimunjTfvkBaJngSfTYSrd4cStnx/c0XK++dni+bLXUHOyMxvihl5vn SiFlzWTmoWf1gxNZgOSKY432R6T1CQXfnAd3x/FCJjfPqFt+RAFXjlVFNA0FZyVE sCxhVx1eZsr7aMJ5H9RehUr6b9swUEm4UGX5H3/GG7GNCZU+fA+Wfi9cl1zqJFey Ho5UjjmRgdV1qapioqCd+Ce/mG0LxRPt/hYdA6G5h4zheRc3KZ7YbIwWRwlkm2w5 is4ToZKwheycaaQnUfOdHUTtZ4Kv0kRof+LMcDUDTrsydWF4T4xGxGD7/CVJkH1G 5OTVsfv6Tw7kEMYaXYBQPs0u3GSxY3CZ+k5wATr9PBBYcArSkt5WNQYCJfO/MnWF z/31hp/ziCIoesgo6uZMO4Dr5Pka54nc4O4KOblvUUMX07WkYGrc4nxBGvhQ5Jl4 A8dJBPCK3OlsVCnHYrDQ0cemhLOYPuiyKTtCUIs2nHuiM4RwoCRJgsVBUnKK+tTx AkM9uQvYsrZ/DoBooBdXJQy3uiHH86zEskiy72H8Wgcu8GbLt2JgCyhXkwDzrIRf hnAN4FS2VNOt5dDTVHBWG1vIxxlM2+LrYpY/QqihNgotZ+C4VWHkoDwbF478JgxM 5Yk+0X9kGvLQbZCJFXdAKAyr/AzRH+Hx1cDvSi7gypf8qOEZwD1rq7f0qw8jnqfG 3QIFoN1/+xTAV8lTlGhvbQYz1XHVBH9l7TSQDLIrnwHTIv+PdZbTveGftCCnLdDo wBLBnw4mKVCtnHrEgXMQF62yuwueQ8zhdh8jf3osYV/COlRZwQQGgZtnQCeeyDIh 8GJR9b4uv22QDNv7J2vcqTEWJdnpAZvIBFGuCBCAgev+URLGW2ELXfWQwNgc5+yP nGRXo+IwD1uhvEqtuin+cAn/sJhOa66g0ZcV/3AcrdQhbicn12YM71cMvA/XRKf5 rpo8bAEwDqyoFoywH4IHM3HNV45rS+brskz6tZC5ELondCPVmUqgVu7ELHlJfPXx RbzbMPJEGr8WjWUiTDhrD2vWgoJ6NRKkDAUYm6KQb8Sbajd2JAAlYntLz5jKqNqN -----END RSA PRIVATE KEY----- puma-3.12.4/examples/CA/serial000066400000000000000000000000041362626474300160550ustar00rootroot000000000000000003puma-3.12.4/examples/config.rb000066400000000000000000000126071362626474300161760ustar00rootroot00000000000000#!/usr/bin/env puma # The directory to operate out of. # # The default is the current directory. # # directory '/u/apps/lolcat' # Use an object or block as the rack application. This allows the # config file to be the application itself. # # app do |env| # puts env # # body = 'Hello, World!' # # [200, { 'Content-Type' => 'text/plain', 'Content-Length' => body.length.to_s }, [body]] # end # Load "path" as a rackup file. # # The default is "config.ru". # # rackup '/u/apps/lolcat/config.ru' # Set the environment in which the rack's app will run. The value must be a string. # # The default is "development". # # environment 'production' # Daemonize the server into the background. Highly suggest that # this be combined with "pidfile" and "stdout_redirect". # # The default is "false". # # daemonize # daemonize false # Store the pid of the server in the file at "path". # # pidfile '/u/apps/lolcat/tmp/pids/puma.pid' # Use "path" as the file to store the server info state. This is # used by "pumactl" to query and control the server. # # state_path '/u/apps/lolcat/tmp/pids/puma.state' # Redirect STDOUT and STDERR to files specified. The 3rd parameter # ("append") specifies whether the output is appended, the default is # "false". # # stdout_redirect '/u/apps/lolcat/log/stdout', '/u/apps/lolcat/log/stderr' # stdout_redirect '/u/apps/lolcat/log/stdout', '/u/apps/lolcat/log/stderr', true # Disable request logging. # # The default is "false". # # quiet # Configure "min" to be the minimum number of threads to use to answer # requests and "max" the maximum. # # The default is "0, 16". # # threads 0, 16 # Bind the server to "url". "tcp://", "unix://" and "ssl://" are the only # accepted protocols. # # The default is "tcp://0.0.0.0:9292". # # bind 'tcp://0.0.0.0:9292' # bind 'unix:///var/run/puma.sock' # bind 'unix:///var/run/puma.sock?umask=0111' # bind 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert' # Instead of "bind 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert'" you # can also use the "ssl_bind" option. # # ssl_bind '127.0.0.1', '9292', { # key: path_to_key, # cert: path_to_cert # } # for JRuby additional keys are required: # keystore: path_to_keystore, # keystore_pass: password # Code to run before doing a restart. This code should # close log files, database connections, etc. # # This can be called multiple times to add code each time. # # on_restart do # puts 'On restart...' # end # Command to use to restart puma. This should be just how to # load puma itself (ie. 'ruby -Ilib bin/puma'), not the arguments # to puma, as those are the same as the original process. # # restart_command '/u/app/lolcat/bin/restart_puma' # === Cluster mode === # How many worker processes to run. Typically this is set to # to the number of available cores. # # The default is "0". # # workers 2 # Code to run immediately before the master starts workers. # # before_fork do # puts "Starting workers..." # end # Code to run in a worker before it starts serving requests. # # This is called everytime a worker is to be started. # # on_worker_boot do # puts 'On worker boot...' # end # Code to run in a worker right before it exits. # # This is called everytime a worker is to about to shutdown. # # on_worker_shutdown do # puts 'On worker shutdown...' # end # Code to run in the master right before a worker is started. The worker's # index is passed as an argument. # # This is called everytime a worker is to be started. # # on_worker_fork do # puts 'Before worker fork...' # end # Code to run in the master after a worker has been started. The worker's # index is passed as an argument. # # This is called everytime a worker is to be started. # # after_worker_fork do # puts 'After worker fork...' # end # Allow workers to reload bundler context when master process is issued # a USR1 signal. This allows proper reloading of gems while the master # is preserved across a phased-restart. (incompatible with preload_app) # (off by default) # prune_bundler # Preload the application before starting the workers; this conflicts with # phased restart feature. (off by default) # preload_app! # Additional text to display in process listing # # tag 'app name' # # If you do not specify a tag, Puma will infer it. If you do not want Puma # to add a tag, use an empty string. # Verifies that all workers have checked in to the master process within # the given timeout. If not the worker process will be restarted. This is # not a request timeout, it is to protect against a hung or dead process. # Setting this value will not protect against slow requests. # Default value is 60 seconds. # # worker_timeout 60 # Change the default worker timeout for booting # # If unspecified, this defaults to the value of worker_timeout. # # worker_boot_timeout 60 # === Puma control rack application === # Start the puma control rack application on "url". This application can # be communicated with to control the main server. Additionally, you can # provide an authentication token, so all requests to the control server # will need to include that token as a query parameter. This allows for # simple authentication. # # Check out https://github.com/puma/puma/blob/master/lib/puma/app/status.rb # to see what the app has available. # # activate_control_app 'unix:///var/run/pumactl.sock' # activate_control_app 'unix:///var/run/pumactl.sock', { auth_token: '12345' } # activate_control_app 'unix:///var/run/pumactl.sock', { no_token: true } puma-3.12.4/examples/plugins/000077500000000000000000000000001362626474300160575ustar00rootroot00000000000000puma-3.12.4/examples/plugins/redis_stop_puma.rb000066400000000000000000000025571362626474300216120ustar00rootroot00000000000000require 'puma/plugin' require 'redis' # How to stop Puma on Heroku # - You can't use normal methods because the dyno is not accessible # - There's no file system, no way to send signals # but ... # - You can use Redis or Memcache; any network distributed key-value # store # 1. Add this plugin to your 'lib' directory # 2. In the `puma.rb` config file add the following lines # === Plugins === # require './lib/puma/plugins/redis_stop_puma' # plugin 'redis_stop_puma' # 3. Now, when you set the redis key "puma::restart::web.1", your web.1 dyno # will restart # 4. Sniffing the Heroku logs for R14 errors is application (and configuration) # specific. I use the Logentries service, watch for the pattern and the call # a webhook back into my app to set the Redis key. YMMV # You can test this locally by setting the DYNO environment variable when # when starting puma, e.g. `DYNO=pants.1 puma` Puma::Plugin.create do def start(launcher) hostname = ENV['DYNO'] return unless hostname redis = Redis.new(url: ENV.fetch('REDIS_URL', nil)) return unless redis.ping == 'PONG' in_background do while true sleep 2 if message = redis.get("puma::restart::#{hostname}") redis.del("puma::restart::#{hostname}") $stderr.puts message launcher.stop break end end end end end puma-3.12.4/examples/puma/000077500000000000000000000000001362626474300153405ustar00rootroot00000000000000puma-3.12.4/examples/puma/cert_puma.pem000066400000000000000000000021131362626474300200170ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC/jCCAeagAwIBAgIBAjANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJVUzEO MAwGA1UECgwFbG9jYWwxDTALBgNVBAsMBGFlcm8xCzAJBgNVBAMMAkNBMB4XDTEy MDExNDAwMjcyN1oXDTEzMDExMzAwMjcyN1owSDELMAkGA1UEBhMCVVMxDjAMBgNV BAoMBWxvY2FsMQ0wCwYDVQQLDARhZXJvMQswCQYDVQQLDAJDQTENMAsGA1UEAwwE cHVtYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArxfNMp+g/pKhsDEkB3KR 1MAkbfnN/UKMvfXwlnXpz7YX1LHHnMutiI/PqymAp6BPcu+umuW2qMHQyqqtyATm Z9jr3t837nhmxwG1noRaKRtsckn9FD43ZlpPg0Q5QnhS4oOsXwJzilqPjdDFYrKN 3TSvIGM2+hVqpVoGYAHDKbMCAwEAAaOBhTCBgjAMBgNVHRMBAf8EAjAAMDEGCWCG SAGG+EIBDQQkFiJSdWJ5L09wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G A1UdDgQWBBTyDyJlmYBDwfWdRj6lWGvoY43k9DALBgNVHQ8EBAMCBaAwEwYDVR0l BAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQADggEBAIbBVfoVCG8RyVesPW+q 5i0wAMbHZ1fwv1RKp17c68DYDs0YYPi0bA0ss8AgpU6thWmskxPiFaE6D5x8iv9f zkcHxgr1Mrbx6RLx9tLUVehSmRv3aiVO4k9Mp6vf+rJK1AYeaGBmvoqTBLwy7Jrt ytKMdqMJj5jKWkWgEGgTnjzbcOClmCQab9isigIzTxMyC/LjeKZe8pPeVX6OM8bY y8XGZp9B7uwdPzqt/g25IzTC0KsQwq8cB0raAtZzIyTNv42zcUjmQNVazAozCTcq MsEtK2z7TYBC3udTsdyS2qVqCpsk7IMOBGrw8vk4SNhO+coiDObW2K/HNvhl0tZC oQI= -----END CERTIFICATE----- puma-3.12.4/examples/puma/client-certs/000077500000000000000000000000001362626474300177345ustar00rootroot00000000000000puma-3.12.4/examples/puma/client-certs/ca.crt000066400000000000000000000021431362626474300210310ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDEDCCAfigAwIBAgIBATANBgkqhkiG9w0BAQUFADA4MRMwEQYKCZImiZPyLGQB GRYDbmV0MRQwEgYKCZImiZPyLGQBGRYEcHVtYTELMAkGA1UEAwwCY2EwIBcNMTQw MjAyMjAwODI3WhgPMjExNTAxMDkyMDA4MjdaMDgxEzARBgoJkiaJk/IsZAEZFgNu ZXQxFDASBgoJkiaJk/IsZAEZFgRwdW1hMQswCQYDVQQDDAJjYTCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAN0u8/heGbkoFDDsx6uidE6DKPvjIPnZPFzR CMkzrNgIeq/hfAItIJAO0m8YZivkUWeE3ut4ibSL+OVTvLRWDL/L736LILUxrD2f joKHHLSVIUWl3H0VjYDE2RCiVkvxP4sAo7EYecZesTtb7W7DdAjHztFZIl+wT+ri MlxDRmYxwsOPQtL0/wJZF80uTpC29V47NY9ITd/A+1xMblPAuQKO3vqZ4Yq07mO/ KKSbepo07v7jMhNOSHf8VBFlTzzG5AHmxZUW0qjCkJBV8N1MiT9cIk81ZuSqOZu3 A+aDAlOYPJe2WVpGskCme9HkJaHTeP87tQUsLqRsLgq/AXh5R58CAwEAAaMjMCEw DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQAD ggEBAKiGZ57rSAlL9slQ0FklVjpe+YrfZvmaXTRPl+9YhikoVI91u1r/qA9PrXKn cL6u66SU6kJwI5572uT1TpKO7jQLXJZV0LO17WuF3P7y44QnNb53Em2GYi8DD/gq X0Y1u8QzIxo4uomWiE73fnao2I9eErKNi/xCySaX/SLQ/9tcEgUyeLlTtJZ3feVF 7K0llR+hSb0Wy/uWnP7qP59YsyCJl1H23j7IEVCTMsOQ4tyIK16+qRA+aVLtE9f5 orsrOWWGJOdAn1nCJweKqhG1vd3GKGRW3Rf/iugCbvgJy0NFLfTpeJ4fJosC3A/K 6K+pe9hNsi2kBPwC67QeVjnbqd4= -----END CERTIFICATE----- puma-3.12.4/examples/puma/client-certs/ca.key000066400000000000000000000032131362626474300210300ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA3S7z+F4ZuSgUMOzHq6J0ToMo++Mg+dk8XNEIyTOs2Ah6r+F8 Ai0gkA7SbxhmK+RRZ4Te63iJtIv45VO8tFYMv8vvfosgtTGsPZ+OgocctJUhRaXc fRWNgMTZEKJWS/E/iwCjsRh5xl6xO1vtbsN0CMfO0VkiX7BP6uIyXENGZjHCw49C 0vT/AlkXzS5OkLb1Xjs1j0hN38D7XExuU8C5Ao7e+pnhirTuY78opJt6mjTu/uMy E05Id/xUEWVPPMbkAebFlRbSqMKQkFXw3UyJP1wiTzVm5Ko5m7cD5oMCU5g8l7ZZ WkayQKZ70eQlodN4/zu1BSwupGwuCr8BeHlHnwIDAQABAoIBABjXyj1OTHNYhhQM tEyZ3Zhn8PWByFVnyfje3a7DqBlHsogIuoYADZVApPAnfGpXpbEL4oHuMwFda2JO qnZS5/Gu9UJwXAceAiuVvUr55AaAbZFGFOLTxeX9tifBJBI5kZqKQtiEWEEop510 MNHtEB5gWuF2sn6u7fsC1wc34zNdE/4hR1njsppIlJ1GP0ICQUI/YKybipF6+pI7 +vg7bF5DU93/uUoRY83NjREeAswFE67OOHi592YIyr5TLu06NDqJ2EBneXyO63Q7 pAakX84SI7k8p8t9XBW6D56pT23JYcrDXwayH1P+SAA4FdDhPlZlGPWjXqQc0biA l1HlfiECgYEA8bwVsukWSGE+XdRnilVATP4zTS7ZpcYzUufS9+pa5w0sVIkDScgL 3YrB0rY7BS/kImOg+xrFz7ILCoIyjDCEVtly0Hc1aZw9SWu545lfFWcd7s7nN5nQ iM9jGxoAWu/VT2GKIfhxMK89CzLD1DGgqsiyYxmPMyplekupUEkkiHECgYEA6jxl uNddzzfKZKHEWS1Tax0hgchOaVMyML35ySz5kgw1tSO8G64eKtrSCq7wcSvb4uc3 hz1yl5Ydxqa+1qX5Qi+UcoRhZZHqGTsbid1aiQJKltk4ImtlptBbX+NTvEDIDblQ fzse/a+upesutwaTchlXtuPG2F53UZeQ831GWQ8CgYAqKdtDDILVdxiwtwakS0Be 7Yu3L6/IyWxUTpkuotLeMB8GU6ueJ+Vh6/zoqt5ahkLteKEwizfrhSuF1rXIXAIJ P/5VvCU12YmbD84pk6vRCN5gs/gCa7LC2iF4La3YLrLvGJ1GVZYwnrAwDte3YDyc 7UqoHGIs031FuoK6vTdBEQKBgHG5bz3mOqKgGMDxFY6ihgzMcPc9FGzousaVhhAZ qPYyvWS7+9mImRb/dNlBBHY98B1jWz9rIxbcCIrpbGB05ucuiKltAoi45mrnmsA9 23YHycUho7J6aDkskiClE4OkBD09iwqq3qoWwPnHjL/KDo5oJYEjZ+inPNE9gF/n o98bAoGBAMLZ7BYOXU1svCwuEz9RdAyXsrOX+Z9DW9i6WMVlfk9K1IxwpXYCvOjO J+wJuQtuNbwKqNPw1DUEp/25cDVoekRAxaKgYGlJFib8vEGtbQ7GQK9bDA11/sIz PQSfc92Y4+qpQ9WzhsZXip49itzBFgmN7/4eaohpvyHCFkVCZVpf -----END RSA PRIVATE KEY----- puma-3.12.4/examples/puma/client-certs/client.crt000066400000000000000000000021231362626474300217220ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDAzCCAeugAwIBAgIBAzANBgkqhkiG9w0BAQUFADA4MRMwEQYKCZImiZPyLGQB GRYDbmV0MRQwEgYKCZImiZPyLGQBGRYEcHVtYTELMAkGA1UEAwwCY2EwIBcNMTQw MjAyMjAwODI3WhgPMjExNTAxMDkyMDA4MjdaMDwxEzARBgoJkiaJk/IsZAEZFgNu ZXQxFDASBgoJkiaJk/IsZAEZFgRwdW1hMQ8wDQYDVQQDDAZjbGllbnQwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGnFKNrNj9pvHIK+iUf1UoDJ0O7hpj jn7QWd65xnHWN9YF9RfBVRxg7HLcPls6GL4c+e5KQP1W0o4gSzwbUc3a/LyqkTEA dligEjXTkQY6tCn/51CuClynreQ98wdgQrayzobKhWMALG7IRLraprZmiQJpxWOF evd7WkF32AwSklZMEdWcLdI36swTV0UzuR9IDUnIh5GGPbikF/6hQ1E1+rL/sZkh czYNEniJGk2pD3MqJguTvYTF24k1KEOV5koSuAPnyl4E/dX9g3AIHWo8OhqVs/4P hrX6++qmrsVz9LIvPw3+SMAE3QU49J7uAANRQMBlxWhlbIpeFi8zNig7AgMBAAGj EjAQMA4GA1UdDwEB/wQEAwIEsDANBgkqhkiG9w0BAQUFAAOCAQEAtxbX3CfQgMwa CWYTjupyTC+KDajbkLLsNXw49PeTIj0FOjAdKA1zyEZcrxtaU+flJr8QHdI8HyZH hpofnOTSBg5k9y4Qz8gjI1Nsh0H8WU/d7F//2l2fUDOhVAb6JtTAKnMpU4snb0GD bxcO6QxfNh50Qdb7KoJH7baJ3aAnsRrLVGqQ7jH20iMu163j/pYw4dDskFMr65Le bMB3NeQ5pHwtYf2J5EliKCtH+Df/BTIl9u1vviZs84gA0Odai/YaMZWCqFiWqIax lkMHNSDWh2G++qMn9erLjRtYDAbIt3VhMncUpEBx3lBEIaVg7qyfpWQ4EkkkylH6 WRv06vukVg== -----END CERTIFICATE----- puma-3.12.4/examples/puma/client-certs/client.key000066400000000000000000000032171362626474300217270ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAxpxSjazY/abxyCvolH9VKAydDu4aY45+0FneucZx1jfWBfUX wVUcYOxy3D5bOhi+HPnuSkD9VtKOIEs8G1HN2vy8qpExAHZYoBI105EGOrQp/+dQ rgpcp63kPfMHYEK2ss6GyoVjACxuyES62qa2ZokCacVjhXr3e1pBd9gMEpJWTBHV nC3SN+rME1dFM7kfSA1JyIeRhj24pBf+oUNRNfqy/7GZIXM2DRJ4iRpNqQ9zKiYL k72ExduJNShDleZKErgD58peBP3V/YNwCB1qPDoalbP+D4a1+vvqpq7Fc/SyLz8N /kjABN0FOPSe7gADUUDAZcVoZWyKXhYvMzYoOwIDAQABAoIBAA7C+aPMEAiyStAs 60l2OVcTsOy2J8H0ilpkA5jdNgLM/ZxNvilBcS2HBXZ3MAKeairvLJXaRLoaRjQC Q4JoTxuSo1cuGW1GXonvMI77/XGJiIGbqLR20rIny4oLMSYnbzrU/NG6nkQaCVXb PeQYdgAi+MnxwNbf79r8N1d3+FW85vjczo2aobWnJpir8U+xp5pe4xpqP8nddP7v tdfIku8HBt76pu4ZfZynO6z9C+ZKS1s7YmBkGNuE46kwl9dhdmZGawAHxNYNAAJS FPLHR11f3syjtPUUm3MAr5BlCFd6vgWYJFrdKv8/uH++WAiVvApnG/FjPh90i42p muGHJbECgYEA+eiHPNzlCwYpPzY+n/AfBQ48G7sYeHk/yEDvhkJOnwAQEQMMw1s9 AGHFTaKa2rb6fruZp8qCXiuzhWq2x7e5+W2Buj+VU15fI1JCJW1s2GdlGOAclvEW HvhcmlwtmSOWgTv1bbVSgQZB9hjbVK+81yQ9hn6AxJUT9k1IU3HALGkCgYEAy3Ow DBX97AVn+1h4I/7cTqzjjLCaBn4UVcy0s3hsbvW/b+aYb8a3F4wJ7mWXvFk9Lb4h uMfka6DYHszma1Bp7BEr63QIAhf936PXAljgtBBCron+9Y6DD83mz/9sX/MTo/pE 2J/qHOqYwoboDoscgtVXZxNM3UX70RJ6RBKAKwMCgYEAxpX+kWC/KWl18WM7lICN Rckv/qFIKsO+6XSgYcHjE/pKyhnwVHT2Ho2S6cRi5ZYtq/OLgIgt3INBnq1UHZRj 1k8snUHVeXAujbTaFz/DFJvk/EVqso9Vkrqta4QAQAbFnGB3APzrWNgOJm9OKxeT KisEMRHpZU1JlZmH9bcYjLECgYAPb1tvz0tQWKim3PNgZ7l3Do7E4bENxQrt53Xe F8jCMkqvxqLR+BVz59/pAjQcyfhmPAJ67k9aCv3aeFkS0yr2Ced3GXpyDjfoe5mY R/3kK0ejzjxVjNZMoKZeKVajgOGAk0Ad3yP3xaSJPYrlb5BeLKlQ3Jn8P473MZut BmpK2QKBgQCr7aaFL5Ypv21kBKVI478jk7v/6PcYOztkFbOsje5A4SlkhlaE5u0h iK0jON8MnAieLeP5QvyXy5n6wL/6THUSxpm3ZJRXpNgKHqENJrZBh3HpmrtzXjxF WLMGl20yrsNUE8WR8wAJ5ECxwUPGZazDY9CD6C0Vm0LUWYdgZQnjnA== -----END RSA PRIVATE KEY----- puma-3.12.4/examples/puma/client-certs/client_expired.crt000066400000000000000000000021331362626474300234430ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDCTCCAfGgAwIBAgIBBDANBgkqhkiG9w0BAQUFADA4MRMwEQYKCZImiZPyLGQB GRYDbmV0MRQwEgYKCZImiZPyLGQBGRYEcHVtYTELMAkGA1UEAwwCY2EwHhcNMTQw MjAyMjAwODI3WhcNMTQwODA0MDgwODI3WjBEMRMwEQYKCZImiZPyLGQBGRYDbmV0 MRQwEgYKCZImiZPyLGQBGRYEcHVtYTEXMBUGA1UEAwwOY2xpZW50LWV4cGlyZWQw ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZQSVfI4KNwus/+9gE9Rww +cehxzw80fNi4tmruSApitTIk1u1r1rYVexkBkVTtl6Fg/aNAAdsI4aATanyGj0m yRqEMxYMt8RtzAYHY6ZEJBm4WUAa44W7WNG2ZA/e0bCDq4Sn+hlPJw0e4iQimJqi 8+iitgyTdicTKDR+9kTS3W/33PZqSwqqnN55m9n9A5FIKwd8fbPsO8k6xIhFS2sL KZ2TkAYLNXu2vFGJR7b37U8mYcHObB1p7U7WYJ2JCf21WZOC4iI25Xk7MFSUYPqb W/iV+41EcslbHwAZHEjqeNynKNlnZokVrviOFeFrHqXbVKp43027L3RZr/JXfxMl AgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIEsDANBgkqhkiG9w0BAQUFAAOCAQEAdCLR jmHeQDrtl9w0cr8Vls+clhoWSDIEj2NC7PRUbDS5T0kAnF/N64n9RJFPS+4bpZaT c9v3DXzdaTTp7moUrwVc3EKVLV5EJcm+TcuUhbL2ZnRgFHggVaoePShBHkDJGLz9 lR30KJnKsyFKEDEyD4rYtYvg98858EtkuxKLsD8efQ/9V8WDLAJJWTsJweEbEpIq GqblQnBeNrLZ7yS32NAM9jnB9wPsMXPZnAAV/o/U6TTwIO9ChApWX+qer1/mIoc7 90/XhxEVw6EcXfGPnsLJ85n9FNGbWnLFRxvFAYcD0z6KQYxVHDiUAMSKqAkpENYO k3gVOw5YNxNpPmUrjw== -----END CERTIFICATE----- puma-3.12.4/examples/puma/client-certs/client_expired.key000066400000000000000000000032131362626474300234430ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA2UElXyOCjcLrP/vYBPUcMPnHocc8PNHzYuLZq7kgKYrUyJNb ta9a2FXsZAZFU7ZehYP2jQAHbCOGgE2p8ho9JskahDMWDLfEbcwGB2OmRCQZuFlA GuOFu1jRtmQP3tGwg6uEp/oZTycNHuIkIpiaovPoorYMk3YnEyg0fvZE0t1v99z2 aksKqpzeeZvZ/QORSCsHfH2z7DvJOsSIRUtrCymdk5AGCzV7trxRiUe29+1PJmHB zmwdae1O1mCdiQn9tVmTguIiNuV5OzBUlGD6m1v4lfuNRHLJWx8AGRxI6njcpyjZ Z2aJFa74jhXhax6l21SqeN9Nuy90Wa/yV38TJQIDAQABAoIBAQDOhvSc7aflVZ/H koT3qX8kO78AVuM3uiqSHa7pZTJi63x+ND9hhxJoR75SE/gBrYNLj3ho79cegOMS w0HEShdJ8LFJbTsP2f5cljBBBAUCEAN3UTj0lsgBolyx84t2uYYAlaOk/8bhjPEX I8lQLhwKvq2vSDrKT+6zcmv9KeWhQWoQavq+QAkTVO9gAqmlkdMEjZntT8hau/qC jkgW7MG/U/CkbALcrbhAWBtMSvUDSKHrrva7XPaMq5nDvX0Wj6PZhY9KaaweR8ZR xfrgzbFfRSKdbT5dD7IcwQhjV51hev6+q8pIFgTiFimeNq4TvKgH5MMwixBnVM+3 djBTB4+dAoGBAO5BYdbpEuVDlwMfHo//R/BJEGJn9dwc3ZpBmJ6vQGmLGjf/oXBr 9tDf/yZKDLwVAgnRdVkllMxpEWrFjD3OpnukbvzTijBi0AQAljRSwNlMTsLwAifi EBXvENFG/7iJKssCQBD6rkeNir3VRlMa5khI9jHahZ0B53RtQCYYDzZ/AoGBAOlv W005wD3g9K2P5BIo+qXB43ZFFsAFOnhu7jUyTciu/95iJ+zw/AGHI/JhNy+jSHnw ZCARLy1c2CImAshadYuWDqR5okR+xHHj3Lgf9ig7lbSf+skn3R4y6fTlxxNdbbU7 dbZbiMm5CyUHTR6957BQaS7mfQZJG0OP5G9fl0xbAoGAOSNa+HRbALqN68S5yqTZ Nsn+8OqnrssJZiYXGO9Ejks61XUr3U83GO6vPRqDJVQQchRWhTObFM6Zy7ZmpKf7 iylrKJz+xg3cfyk43IGAGFzRgrSWf8QaQXhc2yOgzjuvFJKMlMXZp/VM8avFOsb3 tRwyVtBmPLopLOXKfZhFhbcCgYA/etbbU18h9LDVGhItlhNDTEys9vDO2x0hbxk8 QifA8UYHla3B027Ug4mU+jblr4OgFW1FAydPMLZd4vRSw7a/dNkahTFJayfEyPBW 6eoo2rtFWVP7q+mHstTIkkvmyjtxU3AZXR7/rGCJe0jPmVkOK2/PH0LUmMDfSJwY ZWhhjQKBgDCB823bmF6+7J0mtNFFKvRMz6k0wKz7Qe6+AkwmyR3v9IBpL4UMFgIq xdRR7iGhlRHaWVZyzG3WQ1ZgLmVUsfmk9OrD5PfhKaElKvaRr8e+MHOesQ6AgWW2 YXr6vgr6tykVtjG4/v98r05+9q10HH0xOhbuBz+1P7IyLfTCWxbE -----END RSA PRIVATE KEY----- puma-3.12.4/examples/puma/client-certs/client_unknown.crt000066400000000000000000000021471362626474300235070ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDEzCCAfugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBAMRMwEQYKCZImiZPyLGQB GRYDbmV0MRQwEgYKCZImiZPyLGQBGRYEcHVtYTETMBEGA1UEAwwKY2EtdW5rbm93 bjAgFw0xNDAyMDIyMDA4MjdaGA8yMTE1MDEwOTIwMDgyN1owRDETMBEGCgmSJomT 8ixkARkWA25ldDEUMBIGCgmSJomT8ixkARkWBHB1bWExFzAVBgNVBAMMDmNsaWVu dC11bmtub3duMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Ewf7nWJ WcHI3NB+gjz5jWnpNwnU47hjIWq+S41iIymN7FsNdssfzeGb3ZElcQAfofvNf75F 6mE7YEpbKRU7t/Nptvx+Rd1YGOS2N/PWdj3IcJgvQ2guiU0aYkdB7lC1vlI0QzHT dte9pGK/ZPp/mvRZGwi9WmwlNhBwOzvdyRuLyi63dmZ8vgyZrfbmGhZYdhCZ77Uv i+VYqYv5X30I2gQkV6YQMj/AF5Fmt9a4TNGfIjXb6FKmNhlsDMduovrfQMh2umMK YYQ4A+Vi2yMAZkKeD5cogGLS8wmg76n7miPaKLVU9xTEf55IO+HjIBIqz6VG8qbg iBV4Lr6BkkaKTQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCBLAwDQYJKoZIhvcNAQEF BQADggEBAGGaA7fZLOqxx1wIIay0Ewni3ljzR+RAlpTHAh4x+NilcaQ2ils+JoGH /DCdX2iD5nevGVm1DANBhfAuFxXGGBjoOLqtg/sO7Rk51IV9WjDVB2rGeH3hoTCk Qi6Bazdlcvvs3SyFEKcJm2zXizR7O9I+tDv++F6bbaHSBWB6tB9g93pZuMR+smvR Ll2+/jRGPe1Pif1UFs5DR8QshpvxrIwCmO1vznLhDeA5Pde6CtahGJvi1Y25L1h0 9l0LjMxxqgVh8h4A5AR8VufCcDiaT8lzCkz4G4jQYFhrJXmBn8Em6NZfdP/LmM9I 0zEB2Y3lp32ng+WMyaqNh6nfpxEfBoY= -----END CERTIFICATE----- puma-3.12.4/examples/puma/client-certs/client_unknown.key000066400000000000000000000032171362626474300235060ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA0Ewf7nWJWcHI3NB+gjz5jWnpNwnU47hjIWq+S41iIymN7FsN dssfzeGb3ZElcQAfofvNf75F6mE7YEpbKRU7t/Nptvx+Rd1YGOS2N/PWdj3IcJgv Q2guiU0aYkdB7lC1vlI0QzHTdte9pGK/ZPp/mvRZGwi9WmwlNhBwOzvdyRuLyi63 dmZ8vgyZrfbmGhZYdhCZ77Uvi+VYqYv5X30I2gQkV6YQMj/AF5Fmt9a4TNGfIjXb 6FKmNhlsDMduovrfQMh2umMKYYQ4A+Vi2yMAZkKeD5cogGLS8wmg76n7miPaKLVU 9xTEf55IO+HjIBIqz6VG8qbgiBV4Lr6BkkaKTQIDAQABAoIBAC41pSPWqWjjJ7ds /ZPRCR/JLjbKlJMMVdmU/7BtJidc0aJstLj06RJYiaaGy8Kc32elH/rF8GbFuVFs TXr4ve3aL0qsCytepmunWZFiI+LJZA0uhdWzaBeHpmHFIyhGeXtGa1e41wvXYrf0 PDefpu1uZdIshy1nLn4m+W76ogI6FrcyUQ+XRN6f5x6af70Kfi414qf9NLOvajnl JCx90WpdwBp1jC8totKDp9kvILCymFnGBl93NUl8F3Tz7zLkh2SIhaKxQIHTc9g0 LPNkd1Q5tIl/rG3lZ6vw2/UlCVB1NdLsItlJVIJhu6ChIrSXeoDg0GNlJFp7op6W jlJDoRUCgYEA67Nuzrv/lkVljRFDVw97DTiCbLL7SDXDH6Jf43+Bz6prGLqd4Nfd LTO7AdLGd5UTWB1Oj2U94pFXNFkucGv65ck6QAPZ4UrCk9lyjx/dN7d6CxUBDeu2 4zPeHy/mAgkVmKSzEy5L2ERwoQqK8A/g6HtuThB87gtKUpnG+QSfz6sCgYEA4jyE kPRudqne0VXWL/Mhnrls2PNo4MDIOxUp+KcFLGY/44RIEoit3WLRDXULAgT3U20Z lY7nQJyU+/CaEH6rIpADp6VRPA0XJo7HCuMGEO7bk8AAXkXTplVs+XbsD0221AKl GRpS0CtcvHllHiwG8iL+zHAJzL4wbNY36L97decCgYB0UuHk9bN2Hlm3/UUWunUo WTNFIjARuzbJbgGU7WDLdHfWhINWbDKkFFu+0p9QdSpO2mfjLTwVjVVUaI8avK/e qCkvXrcxEQxmm3KGYFt1HAAHaB5VGHfyOa7uBV2ms4UNCHu4g6i620warnFTeQKu ufv+WvTNJpVPnsUsMLQOcQKBgQDcfgDxydjTPDIWseLjrsGIkc29EFaaHinIM5NJ bXbEVA9WbflUXvOc/g8jX3xQBokKPR2fPryxoyos9c0h4GJoeBWn0Z5/uX5jrOne +W5TGIjW0l1JhCKITV+9LqNZMvPKY52G/rnRe0GRy3q60kwet+6/Tz6t1nsZyBqL c/we5wKBgQDc0rE459diZTpxKzumgUGlutKWhqPDGO+NwMa8xaaPvn/k6bg5avx2 8by3BSWhc/YEK7qtVcO1sDr7m9dHtqxrk8+2CC+ZI6wfc359xB9uImrbs9Jqz3VZ +Ji2VOirgm/oZNzhpi2l7yG2atXg0PqLMkS/ft6gWyAjzy/Q8WDSjQ== -----END RSA PRIVATE KEY----- puma-3.12.4/examples/puma/client-certs/generate.rb000066400000000000000000000053471362626474300220640ustar00rootroot00000000000000require "bundler/setup" require "puma" require "puma/minissl" case ARGV[0] when "s" app = proc {|env| p env['puma.peercert'] [200, {}, [ env['puma.peercert'] ]] } events = Puma::Events.new($stdout, $stderr) server = Puma::Server.new(app, events) context = Puma::MiniSSL::Context.new context.key = "certs/server.key" context.cert = "certs/server.crt" context.ca = "certs/ca.crt" #context.verify_mode = Puma::MiniSSL::VERIFY_NONE #context.verify_mode = Puma::MiniSSL::VERIFY_PEER context.verify_mode = Puma::MiniSSL::VERIFY_PEER | Puma::MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT server.add_ssl_listener("127.0.0.1", 4000, context) server.run sleep #server.stop(true) when "g" def issue_cert(dn, key, serial, not_before, not_after, extensions, issuer, issuer_key, digest) cert = OpenSSL::X509::Certificate.new issuer = cert unless issuer issuer_key = key unless issuer_key cert.version = 2 cert.serial = serial cert.subject = dn cert.issuer = issuer.subject cert.public_key = key.public_key cert.not_before = not_before cert.not_after = not_after ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = cert ef.issuer_certificate = issuer extensions.each {|oid, value, critical| cert.add_extension(ef.create_extension(oid, value, critical)) } cert.sign(issuer_key, digest) cert end @ca_key = OpenSSL::PKey::RSA.generate(2048) @svr_key = OpenSSL::PKey::RSA.generate(2048) @cli_key = OpenSSL::PKey::RSA.generate(2048) @ca = OpenSSL::X509::Name.parse("/DC=net/DC=client-cbhq/CN=CA") @svr = OpenSSL::X509::Name.parse("/DC=net/DC=client-cbhq/CN=localhost") @cli = OpenSSL::X509::Name.parse("/DC=net/DC=client-cbhq/CN=localhost") now = Time.at(Time.now.to_i) ca_exts = [ ["basicConstraints","CA:TRUE",true], ["keyUsage","cRLSign,keyCertSign",true], ] ee_exts = [ #["keyUsage","keyEncipherment,digitalSignature",true], ["keyUsage","keyEncipherment,dataEncipherment,digitalSignature",true], ] @ca_cert = issue_cert(@ca, @ca_key, 1, now, now+3600_000, ca_exts, nil, nil, OpenSSL::Digest::SHA1.new) @svr_cert = issue_cert(@svr, @svr_key, 2, now, now+1800_000, ee_exts, @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) @cli_cert = issue_cert(@cli, @cli_key, 3, now, now+1800_000, ee_exts, @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) File.open("ca.crt","wb") {|f| f.print @ca_cert.to_pem } File.open("ca.key","wb") {|f| f.print @ca_key.to_pem } File.open("server.crt","wb") {|f| f.print @svr_cert.to_pem } File.open("server.key","wb") {|f| f.print @svr_key.to_pem } File.open("client1.crt","wb") {|f| f.print @cli_cert.to_pem } File.open("client1.key","wb") {|f| f.print @cli_key.to_pem } end puma-3.12.4/examples/puma/client-certs/keystore.jks000066400000000000000000000072371362626474300223230ustar00rootroot00000000000000caQO1X.509000  *H 0810 &,dnet10 &,dpuma1 0 U ca0  140202200827Z21150109200827Z0810 &,dnet10 &,dpuma1 0 U ca0"0  *H 0 .^(0ǫtN( <\3z|- of+QgxSV ~ 1=!E}VK?y^;[ntY"_O2\CFf1ÏBY.N^;5HM\LnSኴc(z42NHwTeO<ŕҨUL?\"O5f9SP2Ð܈+^>iR+9e$@Y'(dVn CE-x&诩{M-V9۩serverQO100 +*$Q Srd5ڞua7ɘO~ݱfxxuiVS(?3JKogsa]HqyΛ[]Z֓w\LUt*qLGFb$RgrSCE 6(RHf/^\jHVV-Ĵy@]717;b+y( SI<7ݎ%xxuaCsĝF#!N C?8O[3.F#TK2 ? S:~ 9(SxIoQ2ĤiѲD=WiT4!DBh'F'A+GA6F>$~dVkHoXB`S\E e^܂ԃq7EUd -d[u( vb^F2~{VˢN-irAuM@.rMscsCY/IOl7N=P*)GuCfIl-}Z?PCr^K|#"L%6NXUH v6өy_s0 nu% E 9tP%up+_v _p/Frwkg,dpkLn 0]zTwizåwV:D!ٞL㉩plmox8bMNPkɋR!paVz^%ѣU8U a!Qd!U騶xv"-d1Ls~ -qbv&;)6r}Qg>I*Eƪ k 7d4LP  !Oҷ=C;h'C4bAZ(ˈ3gWv)Mg9{C Y3o `OVx_S]4׈:aMIyIQ~=d% ʬ~H=)p9Pl&uҔ_\!# 6Q@u{,^ݶ"B=reæ.{Nk͂j7j+Z6aPBJX,]R+'n\'H5/ÜD߸eyi`K.^iBOy\].&(i۹n\Ȑ>VL|]}cvDj/<, 8"=16X}(1} AP2Ð܈+^>iR+9e$@Y'(dVn CE-x&诩{M-V9۩lN7氯Zc#02ERpuma-3.12.4/examples/puma/client-certs/server.crt000066400000000000000000000021271362626474300217560ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDBjCCAe6gAwIBAgIBAjANBgkqhkiG9w0BAQUFADA4MRMwEQYKCZImiZPyLGQB GRYDbmV0MRQwEgYKCZImiZPyLGQBGRYEcHVtYTELMAkGA1UEAwwCY2EwIBcNMTQw MjAyMjAwODI3WhgPMjExNTAxMDkyMDA4MjdaMD8xEzARBgoJkiaJk/IsZAEZFgNu ZXQxFDASBgoJkiaJk/IsZAEZFgRwdW1hMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDK2ioejidqPJzhGgYh9Nc/CD8g n/vFqchULvmG796R01Rx0Xk5v7OgWs4GMhvJ8o4soCxTmACyPStdemDlocdzZf2d yfv1alVVfBBwqSsiekiB7IiSvpyg5t3h8XqWJcKtP00tPEYmAkVuMbVSxQPrsEi5 47kxu7zyiV0RavaZbODgxkupSjEr0DHa1h7pkip53ekz/rnoceVcvSnCdOahUVj6 ZwMkOQtay/b6746ttbfQh1ygbqTbV/lcV9erldlDkqKG0gQ6gxaBcbIiom1p+ohu CcoTDGZu431KOU6ZygbGxaIEZY9Zbyg9Dp+o6Zyyd7UTY/0JcCWUq7O/XaN5AgMB AAGjEjAQMA4GA1UdDwEB/wQEAwIEsDANBgkqhkiG9w0BAQUFAAOCAQEAIfMqanJJ aVD6XuS3aj0I31L4RiPSfKhkPiuO+lqBGzZhUEKwnEqVWLosFF1SK8Inbu1c1uyP zRb0tB4nSO01L8Oc5kTfuN9lr3nNaWDpGksa/S5e9WndQk95XF3FLt7FJii8wWnM 9xGW27lurskbpuZc1M7IkD5W90y2fF19qB8fY8B2RGovPJEsDKSZ7pwSozijGR4Q 2iIY4Lk9/vYxEYMRixE2+exYiKTNfaPt+CgxHxXksn0LvbYYQTxUmDgvSxXdrnCc 4Kb1BbxOmB8XF17aJuRdUJxDxlnQK5LpoUWGfW7jFPbfX4d3nzpxjPaxvr3peRQV DNtRoD9mFvocbQ== -----END CERTIFICATE----- puma-3.12.4/examples/puma/client-certs/server.key000066400000000000000000000032131362626474300217530ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAytoqHo4najyc4RoGIfTXPwg/IJ/7xanIVC75hu/ekdNUcdF5 Ob+zoFrOBjIbyfKOLKAsU5gAsj0rXXpg5aHHc2X9ncn79WpVVXwQcKkrInpIgeyI kr6coObd4fF6liXCrT9NLTxGJgJFbjG1UsUD67BIueO5Mbu88oldEWr2mWzg4MZL qUoxK9Ax2tYe6ZIqed3pM/656HHlXL0pwnTmoVFY+mcDJDkLWsv2+u+OrbW30Idc oG6k21f5XFfXq5XZQ5KihtIEOoMWgXGyIqJtafqIbgnKEwxmbuN9SjlOmcoGxsWi BGWPWW8oPQ6fqOmcsne1E2P9CXAllKuzv12jeQIDAQABAoIBABBL4JBd2TrGrc/D uHRn6BbvQasMTzy8/BQPRgqaIKZUdPdD3dpO1U5vnReQVP0vWE6re4QntP6cvWwg FcK88XoK2oofnPdFWJ+qfOOgI4/8hPCzIPGxEII4qeCp9rAzTmV+rWOR8QzCp/NH WQrSOxNnMSCF8+3T6EUP1gM9NZxzpycvoa3Xk4QduPdSN9+ifLOohvRwq0PsP8z5 6tPIxEyHTmUjJhDh0en2fXFs6ncxvzQJ+p8R3cSaACDDmAR3uuKgpinC7zLKRyIB rqThVMOO+yxMYNNZ+JIJbuaAAAQy1znPfPsy8syFEvOBhZXJNLEpTqr7n4kHvxpW MI8ukTUCgYEA6zCSMEb9lH/z/qgJPYbZcUuT/M9/EJdiDlazfYrzAHsmwL/FRXjc vAJCeayy3A/oMBWJ+tQrRCPb0e/LU2kqLJRWENKN7uTAHGntDJOtM94ZWDLcAySv zo6usr7BhLmP8ySVojjbWoWI4+SHONYcxsk1v5O7f0ZbzMoDoQPPcl8CgYEA3M0Y l8mDcPlm90r0/CKq5egpzWvb6dvz5Sly83bJIK1CnjyZUbmQZSO2fp9fFFffZ3SG tbgDJ5xQ5Ie+H2mTCsCqkIRqi8tCnbHCXcN40N3SXxcS4e4UcMhVCAHrGODqHrAb if8uTxwozxZtYklaZwhszdtY0lWRG2BzILfOKScCgYBOjyvVqnDboJ3cyz5C6f9J 48fr41d7MEXVqkpMPhSLbZd1PNllKkj5F/wibnhUH5AcN6WePi6xlRTBHEsbcn5e 47GX7uzwBkLReuRulgl90MtAdcSd3CxJX8mk9Sjo757QxcChrkI/C2m9TcGJT6PP Fri4ZF111wek8TmjGAW8GwKBgDhuuvBgcpW3SJe/sqmWerNUCQsVnBlDPCy/0T9k hrcxUSt8NXtrv/n5jLUEKpracqDQaXWcWEIRc6NVBkSlCQ3gfDd/gHPGOXpwakro oMJRT2k6TnssDFFfAkyPoPS012GMhR1Z+Q4DFnMHOmG6eb6HqrdabnMjp3ilyAb+ s1RVAoGAQCGfhL3j9ShiTlpbOcL6CdERk4Jzw7mD4g6gVvyKLJWwACl5Y7YgVcfU Bsm9c3GM2OkAAHDlYd8oBvaWArI5eN93zLgD4uU/Bm08SKpQqOOghqrFQy3B9Ngr eEgVYYvmHikJfcUzOYfotRdH4APGt8EAL2007oyox7Yucv5pzNA= -----END RSA PRIVATE KEY----- puma-3.12.4/examples/puma/client-certs/server.p12000066400000000000000000000063121362626474300215700ustar00rootroot000000000000000 0  *H  } y0 u0 *H 00 *H 0 *H  0~HLʩ98u="giy(-Olۦ} SKBZm]vV ajRb%*;¥n.z X0RȦD9?D(TuLwN{D7 Gqh5!s=͊Aij)ZYޯ1==K^s0fUBT#Q(qo %4yusyT)5}:< s*",D7CEAjC,rJ?N5#n{5XqB;ɶϺ9&v]Pr w9&Z; 5ִC rߍұG֚I#U_rkO@'U_E( @ .(aD>_z2GXfr׮`xV ;s젵a\O~QwO{g0"u3*Ȋi"㪈(0>GʢW &a3󩩽E. ̫ղ?SО Ϛ5l y< nv=F:^~!U E8MQ"Wv1 'thq '¾|5Ou4$%dq _[,:4C9ixwnt!o՗u7S3v1U rwh"ٹ˪q?+KqO`!9)uWu1'-r|S*.um]vEcYANm y甴C^l>avI7 iD &Vr SĪ}f\an]:.gt9p˱mg,7'Fpၖ} T& vEhbX}=ŭL cf^b?Hq@3mKЂ7w4cV ڏ`IQʘG|ʭKK6)% ]) }hQeEA:Ώ Jpbn E)*JVnŸ @׃f,}d^qxQ# /˘&fg h-{<ʹ0^T=aI`P>~"7ΛL W\ 5J-$뜆7nNr%POJ/'}dɻ%> 0k 1m./' 8Wk4^"Tl+LV4}}6S5&v UEF_{7 )r˯=/*:uqMg!`ԵNڀ|)WljpM_PFYrȽ*ΕvUfۘ0Av%ѹ=юx@7VuATo!lފEmQ~( f)x tvEs +8Š!{{EDSO(HQAK{H'KrgْJL ][4$4L遾:&q}Ĺ F/쩛fg>~ړ4%b {nӅʝ 8AF\1stՌ>CˉK煦!%!b<:]"P|kUtЏu1cl@!,<%PʸRp; MNz7 ˋXY1+Clf؅>62@010!0 +bdt// {7!Spuma-3.12.4/examples/puma/client-certs/unknown_ca.crt000066400000000000000000000021671362626474300226160ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDIDCCAgigAwIBAgIBATANBgkqhkiG9w0BAQUFADBAMRMwEQYKCZImiZPyLGQB GRYDbmV0MRQwEgYKCZImiZPyLGQBGRYEcHVtYTETMBEGA1UEAwwKY2EtdW5rbm93 bjAgFw0xNDAyMDIyMDA4MjdaGA8yMTE1MDEwOTIwMDgyN1owQDETMBEGCgmSJomT 8ixkARkWA25ldDEUMBIGCgmSJomT8ixkARkWBHB1bWExEzARBgNVBAMMCmNhLXVu a25vd24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDlvCM8F2q9vE7d PMgao53q0U3MIJgNMOw4eZGDfxZrbGM3x5Zws3/7jzngE8BFc/Y+RhC73T/dN6E9 ZT2jfwlRRSUxx8Pq2OUMA9Pb8fj8TAoLGwC6txKaqy/UqKqhVGjQ3FUS3cXBzR85 PGN9mhIB72+ftcWzw0KSNb+pYG8tg+1p6Nb+UlSrjS9/Z0KM8zKnteMG75qhtKnC rtD6RBiqp98c5r/JJ+LANODaCjtVj5SJTVd/MyshvrNlfYPlMgt+/tU8qSlKzwMa HcN7KA+oT0blOojaUNJMjgqwCI8QeTP1/DEDfvJvTtzPkaz/ctrmbHzQvLS8Lh6f KVv32cg9AgMBAAGjIzAhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG MA0GCSqGSIb3DQEBBQUAA4IBAQCpp/LR62GvtZVrscMVKMfHtlotU67g1nP+FESE 7nJ5Av4rhaxRFHkF1YdQINyB6mL4fHzDu1g4aLdZmTRjOZcYw6Y2xrJZ/X1lIg29 7X4s5AlyHJUstWJnk/FrycPBJqZ75b5SJOayaMiAW+fEsQM2wETISkLitQyVlU3V CtITVjcvgrnsFmnN/qi75EnxxkohZFZGtC2f/NZufYmbpB2FHMt9hhddG7nMawGK dnpbEAiDiQO757Td3vSfAQN6ahopwe2YbrgirrwMQpScoy5pKdbrhMXTLCuwXZmj KR6n2WyS0IzminNy1M4FeB4Pq82VH4rFPwl+t6PWjSHaF87V -----END CERTIFICATE----- puma-3.12.4/examples/puma/client-certs/unknown_ca.key000066400000000000000000000032171362626474300226130ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA5bwjPBdqvbxO3TzIGqOd6tFNzCCYDTDsOHmRg38Wa2xjN8eW cLN/+4854BPARXP2PkYQu90/3TehPWU9o38JUUUlMcfD6tjlDAPT2/H4/EwKCxsA urcSmqsv1KiqoVRo0NxVEt3Fwc0fOTxjfZoSAe9vn7XFs8NCkjW/qWBvLYPtaejW /lJUq40vf2dCjPMyp7XjBu+aobSpwq7Q+kQYqqffHOa/ySfiwDTg2go7VY+UiU1X fzMrIb6zZX2D5TILfv7VPKkpSs8DGh3DeygPqE9G5TqI2lDSTI4KsAiPEHkz9fwx A37yb07cz5Gs/3La5mx80Ly0vC4enylb99nIPQIDAQABAoIBAQCbxT6K30HkFsvO nQj9bxWDg5nhn/QZdaOmA2AULlbwTdTUnIM4Na3Az3OpqRrEvQUpYm60Qyergq3U qFHsCxYxQdYfc9k24wwjYnEDgIWX5KMmto9/CuUVdJ+A7UCNFWPgwpT4ruEJMGFM eNLo9k/heg1Q2HqOEgaQhttHKHkZ/UJaR6XXeucBfJtXSIWf42omeDRNhlwsQ+LY WbTv3XmiFbu1Bhkk67xlpyuGEL1g9Auz9P2z+2Q2LV66kNhfInIFtUYFeNpkjgUJ TtDRU7UBOm+YPMjDfjVUzPbzvCVAtxG4t0ZSJJcQF3N4+HfpoL1c33CCqYwkh1KI xJi1CgjtAoGBAPR3u8OovMmSJ6tdee0WyTahYmK6VOtmSm4IJf1t/wUvf/u6X/Q6 U06TxUAiAs8rMwvvtgPeLYxtaEKO0PSD0rHNL1MHnBwYAmLvAFyCc5tuuyb3ZIyg 1oAz/hW5bYgAL32nmDrlwq0W+KU478SRWWYZA2raO3Ha08I1YVbgkSHPAoGBAPCS fIexjEPxeyZJe4+iKQcmWW42HA9rWIpu/9FDZxvn+PpWhBwMzlxjTlQS2V3nMSHp Jtzyj+Y2R1SO8OQoqZ6s7G+cv8Ni+FqOidcUPUH6aDc2A0ihb0FANp4FQsrv7riP W12mWfniTxZri+nKAwpXjEjko8yi05go3y0dTjQzAoGAJq+n6/+Q2Ikjc+/X8pfv gZCqZBs+gv3t+1mYwXEdsTFiHHDS7HAqbL3fshVvwl8AtfvaHuSS6q0JmbbGBFu0 BOUGfyouHxgBkKxnrzwJlWhBf5oYtFRjfWg85i0w0xvMaCMUaQWg+AkxkdvfvYiO 0CRXMRqV25+YcRxHahshfGsCgYEAus9Vql1B6YS8N4f6ThgDOg0ahw23jnWyJJV7 SznG+JGS8np6TfnXyUBIE9srNdMQgR+20P3+piriCxSQlOvKg3AOjcEv2/6fklp7 SSvrQa+8e5sSw7SwWwANKXo2WrYkLucLcNZ7qiKFfYh39kyrPb2sLvJ1C7QpEVAz tam7D6cCgYBdqh+SlwryiX351eh+tLOlysAPJ1cb3JotnZY1WYKfR9r5PlJsisut dcjOOaz5/Uo/UlVKOjOxxUuB8FIIJGPvx6lo0hq8ornS0CdqhDspUx4aAD0iZ6+y iccYnG0CLW3HpS4B1B7a8ktXW59m+tT9Fl+usOmwdPNIzcdAMqff+A== -----END RSA PRIVATE KEY----- puma-3.12.4/examples/puma/csr_puma.pem000066400000000000000000000011371362626474300176560ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIBhzCB8QIBADBIMQswCQYDVQQGEwJVUzEOMAwGA1UECgwFbG9jYWwxDTALBgNV BAsMBGFlcm8xCzAJBgNVBAsMAkNBMQ0wCwYDVQQDDARwdW1hMIGfMA0GCSqGSIb3 DQEBAQUAA4GNADCBiQKBgQCvF80yn6D+kqGwMSQHcpHUwCRt+c39Qoy99fCWdenP thfUscecy62Ij8+rKYCnoE9y766a5baowdDKqq3IBOZn2Ove3zfueGbHAbWehFop G2xySf0UPjdmWk+DRDlCeFLig6xfAnOKWo+N0MViso3dNK8gYzb6FWqlWgZgAcMp swIDAQABoAAwDQYJKoZIhvcNAQEEBQADgYEAmRsmIQ0pF9iPOO7V1NeHxrVpFz1B CZK0yAIGlCWqzpFO/OILN1hJfFnsFl7hZWipoARk15fN1sSXQF3Xb7/sc/8qVhyz oY38uu/8CE9CTdUutniLzP/4sUomXjslKNVV0qKtmfsFkj2tHtWjJkGAyZUcoKeG hDJxQlIHhZa7Xvw= -----END CERTIFICATE REQUEST----- puma-3.12.4/examples/puma/keystore.jks000066400000000000000000000042431362626474300177210ustar00rootroot00000000000000mydomain9Vz 00 +*Ko. â0ʴm*}_i6UkgS ; iqQC-Nť*W$p[-Idv7djp0SKvovT?M a+jDŽtL;U*鱄>C'.h&i8}OO;6 6҅g( &EdAr}و!KۚE`T?w:e[%P%Uӡӿ-@rQ;#V '&ffG&:suh}?6PuZ)r#lOm_⎝'P#)HF5Tmd bv8\ (&)Z J?`)f8v{[U'({L}E{,3%E[lڪyQhT .CsJLPVpWI3 %T.?ͬٗRA! 2hj~gZ d|C-MZ}y|/u0p QܲNЉpiHҲ,dDn7k 9{sP6X67?5MwgINT˾=C@]f:Lg>KE_/z:r)X4x!Fº3|ģ u3q>%g6NvErP_N%HV'~Mz:*3҆do7VF5"N3 ^JX)FxvøxEkE7oɞeDl1Awq]o3~h$' A.l֎0ʨ֨]!J^ģRU G%';0HdeMmңhX.509X0T0<P6l0  *H 0l10UUnknown10UUnknown10UUnknown10U Unknown10U Unknown10UUnknown0 120824034340Z 121122034340Z0l10UUnknown10UUnknown10UUnknown10U Unknown10U Unknown10UUnknown0"0  *H 0 1n(Vhekt⽫s >"_}PI -U=ßատύ\SN9QՠfAs(ej=۵BH 0.g}|DC& B.Q$ַڦG,"o>pve4V ctMq/L/MѠKhKO"3\ 'server', :hostname => 'puma' } puma-3.12.4/ext/000077500000000000000000000000001362626474300133605ustar00rootroot00000000000000puma-3.12.4/ext/puma_http11/000077500000000000000000000000001362626474300155235ustar00rootroot00000000000000puma-3.12.4/ext/puma_http11/PumaHttp11Service.java000066400000000000000000000006571362626474300216230ustar00rootroot00000000000000package puma; import java.io.IOException; import org.jruby.Ruby; import org.jruby.runtime.load.BasicLibraryService; import org.jruby.puma.Http11; import org.jruby.puma.MiniSSL; public class PumaHttp11Service implements BasicLibraryService { public boolean basicLoad(final Ruby runtime) throws IOException { Http11.createHttp11(runtime); MiniSSL.createMiniSSL(runtime); return true; } } puma-3.12.4/ext/puma_http11/ext_help.h000066400000000000000000000010441362626474300175030ustar00rootroot00000000000000#ifndef ext_help_h #define ext_help_h #define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "%s", "NULL found for " # T " when shouldn't be."); #define DATA_GET(from,type,name) Data_Get_Struct(from,type,name); RAISE_NOT_NULL(name); #define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "%s", "Wrong argument type for " # V " required " # T); #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) #ifdef DEBUG #define TRACE() fprintf(stderr, "> %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__) #else #define TRACE() #endif #endif puma-3.12.4/ext/puma_http11/extconf.rb000066400000000000000000000005011362626474300175120ustar00rootroot00000000000000require 'mkmf' dir_config("puma_http11") unless ENV["DISABLE_SSL"] dir_config("openssl") if %w'crypto libeay32'.find {|crypto| have_library(crypto, 'BIO_read')} and %w'ssl ssleay32'.find {|ssl| have_library(ssl, 'SSL_CTX_new')} have_header "openssl/bio.h" end end create_makefile("puma/puma_http11") puma-3.12.4/ext/puma_http11/http11_parser.c000066400000000000000000000521671362626474300203770ustar00rootroot00000000000000 #line 1 "ext/puma_http11/http11_parser.rl" /** * Copyright (c) 2005 Zed A. Shaw * You can redistribute it and/or modify it under the same terms as Ruby. * License 3-clause BSD */ #include "http11_parser.h" #include #include #include #include #include /* * capitalizes all lower-case ASCII characters, * converts dashes to underscores. */ static void snake_upcase_char(char *c) { if (*c >= 'a' && *c <= 'z') *c &= ~0x20; else if (*c == '-') *c = '_'; } #define LEN(AT, FPC) (FPC - buffer - parser->AT) #define MARK(M,FPC) (parser->M = (FPC) - buffer) #define PTR_TO(F) (buffer + parser->F) /** Machine **/ #line 79 "ext/puma_http11/http11_parser.rl" /** Data **/ #line 40 "ext/puma_http11/http11_parser.c" static const int puma_parser_start = 1; static const int puma_parser_first_final = 47; static const int puma_parser_error = 0; static const int puma_parser_en_main = 1; #line 83 "ext/puma_http11/http11_parser.rl" int puma_parser_init(puma_parser *parser) { int cs = 0; #line 53 "ext/puma_http11/http11_parser.c" { cs = puma_parser_start; } #line 87 "ext/puma_http11/http11_parser.rl" parser->cs = cs; parser->body_start = 0; parser->content_len = 0; parser->mark = 0; parser->nread = 0; parser->field_len = 0; parser->field_start = 0; parser->request = Qnil; parser->body = Qnil; return 1; } /** exec **/ size_t puma_parser_execute(puma_parser *parser, const char *buffer, size_t len, size_t off) { const char *p, *pe; int cs = parser->cs; assert(off <= len && "offset past end of buffer"); p = buffer+off; pe = buffer+len; /* assert(*pe == '\0' && "pointer does not end on NUL"); */ assert((size_t) (pe - p) == len - off && "pointers aren't same distance"); #line 87 "ext/puma_http11/http11_parser.c" { if ( p == pe ) goto _test_eof; switch ( cs ) { case 1: switch( (*p) ) { case 36: goto tr0; case 95: goto tr0; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto tr0; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto tr0; } else goto tr0; goto st0; st0: cs = 0; goto _out; tr0: #line 35 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } goto st2; st2: if ( ++p == pe ) goto _test_eof2; case 2: #line 118 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr2; case 36: goto st28; case 95: goto st28; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st28; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st28; } else goto st28; goto st0; tr2: #line 48 "ext/puma_http11/http11_parser.rl" { parser->request_method(parser, PTR_TO(mark), LEN(mark, p)); } goto st3; st3: if ( ++p == pe ) goto _test_eof3; case 3: #line 143 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 42: goto tr4; case 43: goto tr5; case 47: goto tr6; case 58: goto tr7; } if ( (*p) < 65 ) { if ( 45 <= (*p) && (*p) <= 57 ) goto tr5; } else if ( (*p) > 90 ) { if ( 97 <= (*p) && (*p) <= 122 ) goto tr5; } else goto tr5; goto st0; tr4: #line 35 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } goto st4; st4: if ( ++p == pe ) goto _test_eof4; case 4: #line 167 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr8; case 35: goto tr9; } goto st0; tr8: #line 51 "ext/puma_http11/http11_parser.rl" { parser->request_uri(parser, PTR_TO(mark), LEN(mark, p)); } goto st5; tr31: #line 35 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } #line 54 "ext/puma_http11/http11_parser.rl" { parser->fragment(parser, PTR_TO(mark), LEN(mark, p)); } goto st5; tr33: #line 54 "ext/puma_http11/http11_parser.rl" { parser->fragment(parser, PTR_TO(mark), LEN(mark, p)); } goto st5; tr37: #line 67 "ext/puma_http11/http11_parser.rl" { parser->request_path(parser, PTR_TO(mark), LEN(mark,p)); } #line 51 "ext/puma_http11/http11_parser.rl" { parser->request_uri(parser, PTR_TO(mark), LEN(mark, p)); } goto st5; tr44: #line 58 "ext/puma_http11/http11_parser.rl" { MARK(query_start, p); } #line 59 "ext/puma_http11/http11_parser.rl" { parser->query_string(parser, PTR_TO(query_start), LEN(query_start, p)); } #line 51 "ext/puma_http11/http11_parser.rl" { parser->request_uri(parser, PTR_TO(mark), LEN(mark, p)); } goto st5; tr47: #line 59 "ext/puma_http11/http11_parser.rl" { parser->query_string(parser, PTR_TO(query_start), LEN(query_start, p)); } #line 51 "ext/puma_http11/http11_parser.rl" { parser->request_uri(parser, PTR_TO(mark), LEN(mark, p)); } goto st5; st5: if ( ++p == pe ) goto _test_eof5; case 5: #line 229 "ext/puma_http11/http11_parser.c" if ( (*p) == 72 ) goto tr10; goto st0; tr10: #line 35 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } goto st6; st6: if ( ++p == pe ) goto _test_eof6; case 6: #line 241 "ext/puma_http11/http11_parser.c" if ( (*p) == 84 ) goto st7; goto st0; st7: if ( ++p == pe ) goto _test_eof7; case 7: if ( (*p) == 84 ) goto st8; goto st0; st8: if ( ++p == pe ) goto _test_eof8; case 8: if ( (*p) == 80 ) goto st9; goto st0; st9: if ( ++p == pe ) goto _test_eof9; case 9: if ( (*p) == 47 ) goto st10; goto st0; st10: if ( ++p == pe ) goto _test_eof10; case 10: if ( 48 <= (*p) && (*p) <= 57 ) goto st11; goto st0; st11: if ( ++p == pe ) goto _test_eof11; case 11: if ( (*p) == 46 ) goto st12; if ( 48 <= (*p) && (*p) <= 57 ) goto st11; goto st0; st12: if ( ++p == pe ) goto _test_eof12; case 12: if ( 48 <= (*p) && (*p) <= 57 ) goto st13; goto st0; st13: if ( ++p == pe ) goto _test_eof13; case 13: if ( (*p) == 13 ) goto tr18; if ( 48 <= (*p) && (*p) <= 57 ) goto st13; goto st0; tr18: #line 63 "ext/puma_http11/http11_parser.rl" { parser->http_version(parser, PTR_TO(mark), LEN(mark, p)); } goto st14; tr26: #line 44 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } #line 45 "ext/puma_http11/http11_parser.rl" { parser->http_field(parser, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, p)); } goto st14; tr29: #line 45 "ext/puma_http11/http11_parser.rl" { parser->http_field(parser, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, p)); } goto st14; st14: if ( ++p == pe ) goto _test_eof14; case 14: #line 322 "ext/puma_http11/http11_parser.c" if ( (*p) == 10 ) goto st15; goto st0; st15: if ( ++p == pe ) goto _test_eof15; case 15: switch( (*p) ) { case 13: goto st16; case 33: goto tr21; case 124: goto tr21; case 126: goto tr21; } if ( (*p) < 45 ) { if ( (*p) > 39 ) { if ( 42 <= (*p) && (*p) <= 43 ) goto tr21; } else if ( (*p) >= 35 ) goto tr21; } else if ( (*p) > 46 ) { if ( (*p) < 65 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto tr21; } else if ( (*p) > 90 ) { if ( 94 <= (*p) && (*p) <= 122 ) goto tr21; } else goto tr21; } else goto tr21; goto st0; st16: if ( ++p == pe ) goto _test_eof16; case 16: if ( (*p) == 10 ) goto tr22; goto st0; tr22: #line 71 "ext/puma_http11/http11_parser.rl" { parser->body_start = p - buffer + 1; parser->header_done(parser, p + 1, pe - p - 1); {p++; cs = 47; goto _out;} } goto st47; st47: if ( ++p == pe ) goto _test_eof47; case 47: #line 373 "ext/puma_http11/http11_parser.c" goto st0; tr21: #line 38 "ext/puma_http11/http11_parser.rl" { MARK(field_start, p); } #line 39 "ext/puma_http11/http11_parser.rl" { snake_upcase_char((char *)p); } goto st17; tr23: #line 39 "ext/puma_http11/http11_parser.rl" { snake_upcase_char((char *)p); } goto st17; st17: if ( ++p == pe ) goto _test_eof17; case 17: #line 389 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 33: goto tr23; case 58: goto tr24; case 124: goto tr23; case 126: goto tr23; } if ( (*p) < 45 ) { if ( (*p) > 39 ) { if ( 42 <= (*p) && (*p) <= 43 ) goto tr23; } else if ( (*p) >= 35 ) goto tr23; } else if ( (*p) > 46 ) { if ( (*p) < 65 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto tr23; } else if ( (*p) > 90 ) { if ( 94 <= (*p) && (*p) <= 122 ) goto tr23; } else goto tr23; } else goto tr23; goto st0; tr24: #line 40 "ext/puma_http11/http11_parser.rl" { parser->field_len = LEN(field_start, p); } goto st18; tr27: #line 44 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } goto st18; st18: if ( ++p == pe ) goto _test_eof18; case 18: #line 428 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 13: goto tr26; case 32: goto tr27; } goto tr25; tr25: #line 44 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } goto st19; st19: if ( ++p == pe ) goto _test_eof19; case 19: #line 442 "ext/puma_http11/http11_parser.c" if ( (*p) == 13 ) goto tr29; goto st19; tr9: #line 51 "ext/puma_http11/http11_parser.rl" { parser->request_uri(parser, PTR_TO(mark), LEN(mark, p)); } goto st20; tr38: #line 67 "ext/puma_http11/http11_parser.rl" { parser->request_path(parser, PTR_TO(mark), LEN(mark,p)); } #line 51 "ext/puma_http11/http11_parser.rl" { parser->request_uri(parser, PTR_TO(mark), LEN(mark, p)); } goto st20; tr45: #line 58 "ext/puma_http11/http11_parser.rl" { MARK(query_start, p); } #line 59 "ext/puma_http11/http11_parser.rl" { parser->query_string(parser, PTR_TO(query_start), LEN(query_start, p)); } #line 51 "ext/puma_http11/http11_parser.rl" { parser->request_uri(parser, PTR_TO(mark), LEN(mark, p)); } goto st20; tr48: #line 59 "ext/puma_http11/http11_parser.rl" { parser->query_string(parser, PTR_TO(query_start), LEN(query_start, p)); } #line 51 "ext/puma_http11/http11_parser.rl" { parser->request_uri(parser, PTR_TO(mark), LEN(mark, p)); } goto st20; st20: if ( ++p == pe ) goto _test_eof20; case 20: #line 488 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr31; case 60: goto st0; case 62: goto st0; case 127: goto st0; } if ( (*p) > 31 ) { if ( 34 <= (*p) && (*p) <= 35 ) goto st0; } else if ( (*p) >= 0 ) goto st0; goto tr30; tr30: #line 35 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } goto st21; st21: if ( ++p == pe ) goto _test_eof21; case 21: #line 509 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr33; case 60: goto st0; case 62: goto st0; case 127: goto st0; } if ( (*p) > 31 ) { if ( 34 <= (*p) && (*p) <= 35 ) goto st0; } else if ( (*p) >= 0 ) goto st0; goto st21; tr5: #line 35 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } goto st22; st22: if ( ++p == pe ) goto _test_eof22; case 22: #line 530 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 43: goto st22; case 58: goto st23; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st22; } else if ( (*p) > 57 ) { if ( (*p) > 90 ) { if ( 97 <= (*p) && (*p) <= 122 ) goto st22; } else if ( (*p) >= 65 ) goto st22; } else goto st22; goto st0; tr7: #line 35 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } goto st23; st23: if ( ++p == pe ) goto _test_eof23; case 23: #line 555 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr8; case 34: goto st0; case 35: goto tr9; case 60: goto st0; case 62: goto st0; case 127: goto st0; } if ( 0 <= (*p) && (*p) <= 31 ) goto st0; goto st23; tr6: #line 35 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } goto st24; st24: if ( ++p == pe ) goto _test_eof24; case 24: #line 575 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr37; case 34: goto st0; case 35: goto tr38; case 59: goto tr39; case 60: goto st0; case 62: goto st0; case 63: goto tr40; case 127: goto st0; } if ( 0 <= (*p) && (*p) <= 31 ) goto st0; goto st24; tr39: #line 67 "ext/puma_http11/http11_parser.rl" { parser->request_path(parser, PTR_TO(mark), LEN(mark,p)); } goto st25; st25: if ( ++p == pe ) goto _test_eof25; case 25: #line 599 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr8; case 34: goto st0; case 35: goto tr9; case 60: goto st0; case 62: goto st0; case 63: goto st26; case 127: goto st0; } if ( 0 <= (*p) && (*p) <= 31 ) goto st0; goto st25; tr40: #line 67 "ext/puma_http11/http11_parser.rl" { parser->request_path(parser, PTR_TO(mark), LEN(mark,p)); } goto st26; st26: if ( ++p == pe ) goto _test_eof26; case 26: #line 622 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr44; case 34: goto st0; case 35: goto tr45; case 60: goto st0; case 62: goto st0; case 127: goto st0; } if ( 0 <= (*p) && (*p) <= 31 ) goto st0; goto tr43; tr43: #line 58 "ext/puma_http11/http11_parser.rl" { MARK(query_start, p); } goto st27; st27: if ( ++p == pe ) goto _test_eof27; case 27: #line 642 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr47; case 34: goto st0; case 35: goto tr48; case 60: goto st0; case 62: goto st0; case 127: goto st0; } if ( 0 <= (*p) && (*p) <= 31 ) goto st0; goto st27; st28: if ( ++p == pe ) goto _test_eof28; case 28: switch( (*p) ) { case 32: goto tr2; case 36: goto st29; case 95: goto st29; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st29; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st29; } else goto st29; goto st0; st29: if ( ++p == pe ) goto _test_eof29; case 29: switch( (*p) ) { case 32: goto tr2; case 36: goto st30; case 95: goto st30; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st30; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st30; } else goto st30; goto st0; st30: if ( ++p == pe ) goto _test_eof30; case 30: switch( (*p) ) { case 32: goto tr2; case 36: goto st31; case 95: goto st31; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st31; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st31; } else goto st31; goto st0; st31: if ( ++p == pe ) goto _test_eof31; case 31: switch( (*p) ) { case 32: goto tr2; case 36: goto st32; case 95: goto st32; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st32; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st32; } else goto st32; goto st0; st32: if ( ++p == pe ) goto _test_eof32; case 32: switch( (*p) ) { case 32: goto tr2; case 36: goto st33; case 95: goto st33; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st33; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st33; } else goto st33; goto st0; st33: if ( ++p == pe ) goto _test_eof33; case 33: switch( (*p) ) { case 32: goto tr2; case 36: goto st34; case 95: goto st34; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st34; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st34; } else goto st34; goto st0; st34: if ( ++p == pe ) goto _test_eof34; case 34: switch( (*p) ) { case 32: goto tr2; case 36: goto st35; case 95: goto st35; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st35; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st35; } else goto st35; goto st0; st35: if ( ++p == pe ) goto _test_eof35; case 35: switch( (*p) ) { case 32: goto tr2; case 36: goto st36; case 95: goto st36; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st36; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st36; } else goto st36; goto st0; st36: if ( ++p == pe ) goto _test_eof36; case 36: switch( (*p) ) { case 32: goto tr2; case 36: goto st37; case 95: goto st37; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st37; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st37; } else goto st37; goto st0; st37: if ( ++p == pe ) goto _test_eof37; case 37: switch( (*p) ) { case 32: goto tr2; case 36: goto st38; case 95: goto st38; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st38; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st38; } else goto st38; goto st0; st38: if ( ++p == pe ) goto _test_eof38; case 38: switch( (*p) ) { case 32: goto tr2; case 36: goto st39; case 95: goto st39; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st39; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st39; } else goto st39; goto st0; st39: if ( ++p == pe ) goto _test_eof39; case 39: switch( (*p) ) { case 32: goto tr2; case 36: goto st40; case 95: goto st40; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st40; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st40; } else goto st40; goto st0; st40: if ( ++p == pe ) goto _test_eof40; case 40: switch( (*p) ) { case 32: goto tr2; case 36: goto st41; case 95: goto st41; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st41; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st41; } else goto st41; goto st0; st41: if ( ++p == pe ) goto _test_eof41; case 41: switch( (*p) ) { case 32: goto tr2; case 36: goto st42; case 95: goto st42; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st42; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st42; } else goto st42; goto st0; st42: if ( ++p == pe ) goto _test_eof42; case 42: switch( (*p) ) { case 32: goto tr2; case 36: goto st43; case 95: goto st43; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st43; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st43; } else goto st43; goto st0; st43: if ( ++p == pe ) goto _test_eof43; case 43: switch( (*p) ) { case 32: goto tr2; case 36: goto st44; case 95: goto st44; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st44; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st44; } else goto st44; goto st0; st44: if ( ++p == pe ) goto _test_eof44; case 44: switch( (*p) ) { case 32: goto tr2; case 36: goto st45; case 95: goto st45; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st45; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st45; } else goto st45; goto st0; st45: if ( ++p == pe ) goto _test_eof45; case 45: switch( (*p) ) { case 32: goto tr2; case 36: goto st46; case 95: goto st46; } if ( (*p) < 48 ) { if ( 45 <= (*p) && (*p) <= 46 ) goto st46; } else if ( (*p) > 57 ) { if ( 65 <= (*p) && (*p) <= 90 ) goto st46; } else goto st46; goto st0; st46: if ( ++p == pe ) goto _test_eof46; case 46: if ( (*p) == 32 ) goto tr2; goto st0; } _test_eof2: cs = 2; goto _test_eof; _test_eof3: cs = 3; goto _test_eof; _test_eof4: cs = 4; goto _test_eof; _test_eof5: cs = 5; goto _test_eof; _test_eof6: cs = 6; goto _test_eof; _test_eof7: cs = 7; goto _test_eof; _test_eof8: cs = 8; goto _test_eof; _test_eof9: cs = 9; goto _test_eof; _test_eof10: cs = 10; goto _test_eof; _test_eof11: cs = 11; goto _test_eof; _test_eof12: cs = 12; goto _test_eof; _test_eof13: cs = 13; goto _test_eof; _test_eof14: cs = 14; goto _test_eof; _test_eof15: cs = 15; goto _test_eof; _test_eof16: cs = 16; goto _test_eof; _test_eof47: cs = 47; goto _test_eof; _test_eof17: cs = 17; goto _test_eof; _test_eof18: cs = 18; goto _test_eof; _test_eof19: cs = 19; goto _test_eof; _test_eof20: cs = 20; goto _test_eof; _test_eof21: cs = 21; goto _test_eof; _test_eof22: cs = 22; goto _test_eof; _test_eof23: cs = 23; goto _test_eof; _test_eof24: cs = 24; goto _test_eof; _test_eof25: cs = 25; goto _test_eof; _test_eof26: cs = 26; goto _test_eof; _test_eof27: cs = 27; goto _test_eof; _test_eof28: cs = 28; goto _test_eof; _test_eof29: cs = 29; goto _test_eof; _test_eof30: cs = 30; goto _test_eof; _test_eof31: cs = 31; goto _test_eof; _test_eof32: cs = 32; goto _test_eof; _test_eof33: cs = 33; goto _test_eof; _test_eof34: cs = 34; goto _test_eof; _test_eof35: cs = 35; goto _test_eof; _test_eof36: cs = 36; goto _test_eof; _test_eof37: cs = 37; goto _test_eof; _test_eof38: cs = 38; goto _test_eof; _test_eof39: cs = 39; goto _test_eof; _test_eof40: cs = 40; goto _test_eof; _test_eof41: cs = 41; goto _test_eof; _test_eof42: cs = 42; goto _test_eof; _test_eof43: cs = 43; goto _test_eof; _test_eof44: cs = 44; goto _test_eof; _test_eof45: cs = 45; goto _test_eof; _test_eof46: cs = 46; goto _test_eof; _test_eof: {} _out: {} } #line 115 "ext/puma_http11/http11_parser.rl" if (!puma_parser_has_error(parser)) parser->cs = cs; parser->nread += p - (buffer + off); assert(p <= pe && "buffer overflow after parsing execute"); assert(parser->nread <= len && "nread longer than length"); assert(parser->body_start <= len && "body starts after buffer end"); assert(parser->mark < len && "mark is after buffer end"); assert(parser->field_len <= len && "field has length longer than whole buffer"); assert(parser->field_start < len && "field starts after buffer end"); return(parser->nread); } int puma_parser_finish(puma_parser *parser) { if (puma_parser_has_error(parser) ) { return -1; } else if (puma_parser_is_finished(parser) ) { return 1; } else { return 0; } } int puma_parser_has_error(puma_parser *parser) { return parser->cs == puma_parser_error; } int puma_parser_is_finished(puma_parser *parser) { return parser->cs >= puma_parser_first_final; } puma-3.12.4/ext/puma_http11/http11_parser.h000066400000000000000000000026601362626474300203750ustar00rootroot00000000000000/** * Copyright (c) 2005 Zed A. Shaw * You can redistribute it and/or modify it under the same terms as Ruby. * License 3-clause BSD */ #ifndef http11_parser_h #define http11_parser_h #define RSTRING_NOT_MODIFIED 1 #include "ruby.h" #include #if defined(_WIN32) #include #endif #define BUFFER_LEN 1024 struct puma_parser; typedef void (*element_cb)(struct puma_parser* hp, const char *at, size_t length); typedef void (*field_cb)(struct puma_parser* hp, const char *field, size_t flen, const char *value, size_t vlen); typedef struct puma_parser { int cs; size_t body_start; int content_len; size_t nread; size_t mark; size_t field_start; size_t field_len; size_t query_start; VALUE request; VALUE body; field_cb http_field; element_cb request_method; element_cb request_uri; element_cb fragment; element_cb request_path; element_cb query_string; element_cb http_version; element_cb header_done; char buf[BUFFER_LEN]; } puma_parser; int puma_parser_init(puma_parser *parser); int puma_parser_finish(puma_parser *parser); size_t puma_parser_execute(puma_parser *parser, const char *data, size_t len, size_t off); int puma_parser_has_error(puma_parser *parser); int puma_parser_is_finished(puma_parser *parser); #define puma_parser_nread(parser) (parser)->nread #endif puma-3.12.4/ext/puma_http11/http11_parser.java.rl000066400000000000000000000077021362626474300215050ustar00rootroot00000000000000package org.jruby.puma; import org.jruby.util.ByteList; public class Http11Parser { /** Machine **/ %%{ machine puma_parser; action mark {parser.mark = fpc; } action start_field { parser.field_start = fpc; } action snake_upcase_field { /* FIXME stub */ } action write_field { parser.field_len = fpc-parser.field_start; } action start_value { parser.mark = fpc; } action write_value { if(parser.http_field != null) { parser.http_field.call(parser.data, parser.field_start, parser.field_len, parser.mark, fpc-parser.mark); } } action request_method { if(parser.request_method != null) parser.request_method.call(parser.data, parser.mark, fpc-parser.mark); } action request_uri { if(parser.request_uri != null) parser.request_uri.call(parser.data, parser.mark, fpc-parser.mark); } action fragment { if(parser.fragment != null) parser.fragment.call(parser.data, parser.mark, fpc-parser.mark); } action start_query {parser.query_start = fpc; } action query_string { if(parser.query_string != null) parser.query_string.call(parser.data, parser.query_start, fpc-parser.query_start); } action http_version { if(parser.http_version != null) parser.http_version.call(parser.data, parser.mark, fpc-parser.mark); } action request_path { if(parser.request_path != null) parser.request_path.call(parser.data, parser.mark, fpc-parser.mark); } action done { parser.body_start = fpc + 1; if(parser.header_done != null) parser.header_done.call(parser.data, fpc + 1, pe - fpc - 1); fbreak; } include puma_parser_common "http11_parser_common.rl"; }%% /** Data **/ %% write data; public static interface ElementCB { public void call(Object data, int at, int length); } public static interface FieldCB { public void call(Object data, int field, int flen, int value, int vlen); } public static class HttpParser { int cs; int body_start; int content_len; int nread; int mark; int field_start; int field_len; int query_start; Object data; ByteList buffer; public FieldCB http_field; public ElementCB request_method; public ElementCB request_uri; public ElementCB fragment; public ElementCB request_path; public ElementCB query_string; public ElementCB http_version; public ElementCB header_done; public void init() { cs = 0; %% write init; body_start = 0; content_len = 0; mark = 0; nread = 0; field_len = 0; field_start = 0; } } public final HttpParser parser = new HttpParser(); public int execute(ByteList buffer, int off) { int p, pe; int cs = parser.cs; int len = buffer.length(); assert off<=len : "offset past end of buffer"; p = off; pe = len; // get a copy of the bytes, since it may not start at 0 // FIXME: figure out how to just use the bytes in-place byte[] data = buffer.bytes(); parser.buffer = buffer; %% write exec; parser.cs = cs; parser.nread += (p - off); assert p <= pe : "buffer overflow after parsing execute"; assert parser.nread <= len : "nread longer than length"; assert parser.body_start <= len : "body starts after buffer end"; assert parser.mark < len : "mark is after buffer end"; assert parser.field_len <= len : "field has length longer than whole buffer"; assert parser.field_start < len : "field starts after buffer end"; return parser.nread; } public int finish() { if(has_error()) { return -1; } else if(is_finished()) { return 1; } else { return 0; } } public boolean has_error() { return parser.cs == puma_parser_error; } public boolean is_finished() { return parser.cs == puma_parser_first_final; } } puma-3.12.4/ext/puma_http11/http11_parser.rl000066400000000000000000000067321362626474300205670ustar00rootroot00000000000000/** * Copyright (c) 2005 Zed A. Shaw * You can redistribute it and/or modify it under the same terms as Ruby. * License 3-clause BSD */ #include "http11_parser.h" #include #include #include #include #include /* * capitalizes all lower-case ASCII characters, * converts dashes to underscores. */ static void snake_upcase_char(char *c) { if (*c >= 'a' && *c <= 'z') *c &= ~0x20; else if (*c == '-') *c = '_'; } #define LEN(AT, FPC) (FPC - buffer - parser->AT) #define MARK(M,FPC) (parser->M = (FPC) - buffer) #define PTR_TO(F) (buffer + parser->F) /** Machine **/ %%{ machine puma_parser; action mark { MARK(mark, fpc); } action start_field { MARK(field_start, fpc); } action snake_upcase_field { snake_upcase_char((char *)fpc); } action write_field { parser->field_len = LEN(field_start, fpc); } action start_value { MARK(mark, fpc); } action write_value { parser->http_field(parser, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc)); } action request_method { parser->request_method(parser, PTR_TO(mark), LEN(mark, fpc)); } action request_uri { parser->request_uri(parser, PTR_TO(mark), LEN(mark, fpc)); } action fragment { parser->fragment(parser, PTR_TO(mark), LEN(mark, fpc)); } action start_query { MARK(query_start, fpc); } action query_string { parser->query_string(parser, PTR_TO(query_start), LEN(query_start, fpc)); } action http_version { parser->http_version(parser, PTR_TO(mark), LEN(mark, fpc)); } action request_path { parser->request_path(parser, PTR_TO(mark), LEN(mark,fpc)); } action done { parser->body_start = fpc - buffer + 1; parser->header_done(parser, fpc + 1, pe - fpc - 1); fbreak; } include puma_parser_common "http11_parser_common.rl"; }%% /** Data **/ %% write data; int puma_parser_init(puma_parser *parser) { int cs = 0; %% write init; parser->cs = cs; parser->body_start = 0; parser->content_len = 0; parser->mark = 0; parser->nread = 0; parser->field_len = 0; parser->field_start = 0; parser->request = Qnil; parser->body = Qnil; return 1; } /** exec **/ size_t puma_parser_execute(puma_parser *parser, const char *buffer, size_t len, size_t off) { const char *p, *pe; int cs = parser->cs; assert(off <= len && "offset past end of buffer"); p = buffer+off; pe = buffer+len; /* assert(*pe == '\0' && "pointer does not end on NUL"); */ assert((size_t) (pe - p) == len - off && "pointers aren't same distance"); %% write exec; if (!puma_parser_has_error(parser)) parser->cs = cs; parser->nread += p - (buffer + off); assert(p <= pe && "buffer overflow after parsing execute"); assert(parser->nread <= len && "nread longer than length"); assert(parser->body_start <= len && "body starts after buffer end"); assert(parser->mark < len && "mark is after buffer end"); assert(parser->field_len <= len && "field has length longer than whole buffer"); assert(parser->field_start < len && "field starts after buffer end"); return(parser->nread); } int puma_parser_finish(puma_parser *parser) { if (puma_parser_has_error(parser) ) { return -1; } else if (puma_parser_is_finished(parser) ) { return 1; } else { return 0; } } int puma_parser_has_error(puma_parser *parser) { return parser->cs == puma_parser_error; } int puma_parser_is_finished(puma_parser *parser) { return parser->cs >= puma_parser_first_final; } puma-3.12.4/ext/puma_http11/http11_parser_common.rl000066400000000000000000000034551362626474300221360ustar00rootroot00000000000000%%{ machine puma_parser_common; #### HTTP PROTOCOL GRAMMAR # line endings CRLF = "\r\n"; # character types CTL = (cntrl | 127); safe = ("$" | "-" | "_" | "."); extra = ("!" | "*" | "'" | "(" | ")" | ","); reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"); unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">"); national = any -- (alpha | digit | reserved | extra | safe | unsafe); unreserved = (alpha | digit | safe | extra | national); escape = ("%" xdigit xdigit); uchar = (unreserved | escape | "%"); pchar = (uchar | ":" | "@" | "&" | "=" | "+"); tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t"); # elements token = (ascii -- (CTL | tspecials)); # URI schemes and absolute paths scheme = ( alpha | digit | "+" | "-" | "." )* ; absolute_uri = (scheme ":" (uchar | reserved )*); path = ( pchar+ ( "/" pchar* )* ) ; query = ( uchar | reserved )* %query_string ; param = ( pchar | "/" )* ; params = ( param ( ";" param )* ) ; rel_path = ( path? %request_path (";" params)? ) ("?" %start_query query)?; absolute_path = ( "/"+ rel_path ); Request_URI = ( "*" | absolute_uri | absolute_path ) >mark %request_uri; Fragment = ( uchar | reserved )* >mark %fragment; Method = ( upper | digit | safe ){1,20} >mark %request_method; http_number = ( digit+ "." digit+ ) ; HTTP_Version = ( "HTTP/" http_number ) >mark %http_version ; Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " " HTTP_Version CRLF ) ; field_name = ( token -- ":" )+ >start_field $snake_upcase_field %write_field; field_value = any* >start_value %write_value; message_header = field_name ":" " "* field_value :> CRLF; Request = Request_Line ( message_header )* ( CRLF @done ); main := Request; }%% puma-3.12.4/ext/puma_http11/io_buffer.c000066400000000000000000000062021362626474300176270ustar00rootroot00000000000000#define RSTRING_NOT_MODIFIED 1 #include "ruby.h" #include struct buf_int { uint8_t* top; uint8_t* cur; size_t size; }; #define BUF_DEFAULT_SIZE 4096 #define BUF_TOLERANCE 32 static void buf_free(struct buf_int* internal) { xfree(internal->top); xfree(internal); } static VALUE buf_alloc(VALUE self) { VALUE buf; struct buf_int* internal; buf = Data_Make_Struct(self, struct buf_int, 0, buf_free, internal); internal->size = BUF_DEFAULT_SIZE; internal->top = ALLOC_N(uint8_t, BUF_DEFAULT_SIZE); internal->cur = internal->top; return buf; } static VALUE buf_append(VALUE self, VALUE str) { struct buf_int* b; size_t used, str_len, new_size; Data_Get_Struct(self, struct buf_int, b); used = b->cur - b->top; StringValue(str); str_len = RSTRING_LEN(str); new_size = used + str_len; if(new_size > b->size) { size_t n = b->size + (b->size / 2); uint8_t* top; uint8_t* old; new_size = (n > new_size ? n : new_size + BUF_TOLERANCE); top = ALLOC_N(uint8_t, new_size); old = b->top; memcpy(top, old, used); b->top = top; b->cur = top + used; b->size = new_size; xfree(old); } memcpy(b->cur, RSTRING_PTR(str), str_len); b->cur += str_len; return self; } static VALUE buf_append2(int argc, VALUE* argv, VALUE self) { struct buf_int* b; size_t used, new_size; int i; VALUE str; Data_Get_Struct(self, struct buf_int, b); used = b->cur - b->top; new_size = used; for(i = 0; i < argc; i++) { StringValue(argv[i]); str = argv[i]; new_size += RSTRING_LEN(str); } if(new_size > b->size) { size_t n = b->size + (b->size / 2); uint8_t* top; uint8_t* old; new_size = (n > new_size ? n : new_size + BUF_TOLERANCE); top = ALLOC_N(uint8_t, new_size); old = b->top; memcpy(top, old, used); b->top = top; b->cur = top + used; b->size = new_size; xfree(old); } for(i = 0; i < argc; i++) { long str_len; str = argv[i]; str_len = RSTRING_LEN(str); memcpy(b->cur, RSTRING_PTR(str), str_len); b->cur += str_len; } return self; } static VALUE buf_to_str(VALUE self) { struct buf_int* b; Data_Get_Struct(self, struct buf_int, b); return rb_str_new((const char*)(b->top), b->cur - b->top); } static VALUE buf_used(VALUE self) { struct buf_int* b; Data_Get_Struct(self, struct buf_int, b); return INT2FIX(b->cur - b->top); } static VALUE buf_capa(VALUE self) { struct buf_int* b; Data_Get_Struct(self, struct buf_int, b); return INT2FIX(b->size); } static VALUE buf_reset(VALUE self) { struct buf_int* b; Data_Get_Struct(self, struct buf_int, b); b->cur = b->top; return self; } void Init_io_buffer(VALUE puma) { VALUE buf = rb_define_class_under(puma, "IOBuffer", rb_cObject); rb_define_alloc_func(buf, buf_alloc); rb_define_method(buf, "<<", buf_append, 1); rb_define_method(buf, "append", buf_append2, -1); rb_define_method(buf, "to_str", buf_to_str, 0); rb_define_method(buf, "to_s", buf_to_str, 0); rb_define_method(buf, "used", buf_used, 0); rb_define_method(buf, "capacity", buf_capa, 0); rb_define_method(buf, "reset", buf_reset, 0); } puma-3.12.4/ext/puma_http11/mini_ssl.c000066400000000000000000000262701362626474300175130ustar00rootroot00000000000000#define RSTRING_NOT_MODIFIED 1 #include #include #if RUBY_API_VERSION_MAJOR == 1 #include #else #include #endif #ifdef HAVE_OPENSSL_BIO_H #include #include #include #include #include #ifndef SSL_OP_NO_COMPRESSION #define SSL_OP_NO_COMPRESSION 0 #endif typedef struct { BIO* read; BIO* write; SSL* ssl; SSL_CTX* ctx; } ms_conn; typedef struct { unsigned char* buf; int bytes; } ms_cert_buf; void engine_free(ms_conn* conn) { ms_cert_buf* cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl); if(cert_buf) { OPENSSL_free(cert_buf->buf); free(cert_buf); } SSL_free(conn->ssl); SSL_CTX_free(conn->ctx); free(conn); } ms_conn* engine_alloc(VALUE klass, VALUE* obj) { ms_conn* conn; *obj = Data_Make_Struct(klass, ms_conn, 0, engine_free, conn); conn->read = BIO_new(BIO_s_mem()); BIO_set_nbio(conn->read, 1); conn->write = BIO_new(BIO_s_mem()); BIO_set_nbio(conn->write, 1); conn->ssl = 0; conn->ctx = 0; return conn; } DH *get_dh1024() { /* `openssl dhparam 1024 -C` * -----BEGIN DH PARAMETERS----- * MIGHAoGBALPwcEv0OstmQCZdfHw0N5r+07lmXMxkpQacy1blwj0LUqC+Divp6pBk * usTJ9W2/dOYr1X7zi6yXNLp4oLzc/31PUL3D9q8CpGS7vPz5gijKSw9BwCTT5z9+ * KF9v46qw8XqT5HHV87sWFlGQcVFq+pEkA2kPikkKZ/X/CCcpCAV7AgEC * -----END DH PARAMETERS----- */ static unsigned char dh1024_p[] = { 0xB3,0xF0,0x70,0x4B,0xF4,0x3A,0xCB,0x66,0x40,0x26,0x5D,0x7C, 0x7C,0x34,0x37,0x9A,0xFE,0xD3,0xB9,0x66,0x5C,0xCC,0x64,0xA5, 0x06,0x9C,0xCB,0x56,0xE5,0xC2,0x3D,0x0B,0x52,0xA0,0xBE,0x0E, 0x2B,0xE9,0xEA,0x90,0x64,0xBA,0xC4,0xC9,0xF5,0x6D,0xBF,0x74, 0xE6,0x2B,0xD5,0x7E,0xF3,0x8B,0xAC,0x97,0x34,0xBA,0x78,0xA0, 0xBC,0xDC,0xFF,0x7D,0x4F,0x50,0xBD,0xC3,0xF6,0xAF,0x02,0xA4, 0x64,0xBB,0xBC,0xFC,0xF9,0x82,0x28,0xCA,0x4B,0x0F,0x41,0xC0, 0x24,0xD3,0xE7,0x3F,0x7E,0x28,0x5F,0x6F,0xE3,0xAA,0xB0,0xF1, 0x7A,0x93,0xE4,0x71,0xD5,0xF3,0xBB,0x16,0x16,0x51,0x90,0x71, 0x51,0x6A,0xFA,0x91,0x24,0x03,0x69,0x0F,0x8A,0x49,0x0A,0x67, 0xF5,0xFF,0x08,0x27,0x29,0x08,0x05,0x7B }; static unsigned char dh1024_g[] = { 0x02 }; DH *dh; dh = DH_new(); #if OPENSSL_VERSION_NUMBER < 0x10100005L || defined(LIBRESSL_VERSION_NUMBER) dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL); dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL); if ((dh->p == NULL) || (dh->g == NULL)) { DH_free(dh); return NULL; } #else BIGNUM *p, *g; p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL); g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL); if (p == NULL || g == NULL || !DH_set0_pqg(dh, p, NULL, g)) { DH_free(dh); BN_free(p); BN_free(g); return NULL; } #endif return dh; } static int engine_verify_callback(int preverify_ok, X509_STORE_CTX* ctx) { X509* err_cert; SSL* ssl; int bytes; unsigned char* buf = NULL; if(!preverify_ok) { err_cert = X509_STORE_CTX_get_current_cert(ctx); if(err_cert) { /* * Save the failed certificate for inspection/logging. */ bytes = i2d_X509(err_cert, &buf); if(bytes > 0) { ms_cert_buf* cert_buf = (ms_cert_buf*)malloc(sizeof(ms_cert_buf)); cert_buf->buf = buf; cert_buf->bytes = bytes; ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); SSL_set_app_data(ssl, cert_buf); } } } return preverify_ok; } VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) { VALUE obj; SSL_CTX* ctx; SSL* ssl; ms_conn* conn = engine_alloc(self, &obj); ID sym_key = rb_intern("key"); VALUE key = rb_funcall(mini_ssl_ctx, sym_key, 0); StringValue(key); ID sym_cert = rb_intern("cert"); VALUE cert = rb_funcall(mini_ssl_ctx, sym_cert, 0); StringValue(cert); ID sym_ca = rb_intern("ca"); VALUE ca = rb_funcall(mini_ssl_ctx, sym_ca, 0); ID sym_verify_mode = rb_intern("verify_mode"); VALUE verify_mode = rb_funcall(mini_ssl_ctx, sym_verify_mode, 0); ID sym_ssl_cipher_filter = rb_intern("ssl_cipher_filter"); VALUE ssl_cipher_filter = rb_funcall(mini_ssl_ctx, sym_ssl_cipher_filter, 0); ctx = SSL_CTX_new(SSLv23_server_method()); conn->ctx = ctx; SSL_CTX_use_certificate_chain_file(ctx, RSTRING_PTR(cert)); SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), SSL_FILETYPE_PEM); if (!NIL_P(ca)) { StringValue(ca); SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL); } SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION); SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); if (!NIL_P(ssl_cipher_filter)) { StringValue(ssl_cipher_filter); SSL_CTX_set_cipher_list(ctx, RSTRING_PTR(ssl_cipher_filter)); } else { SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL@STRENGTH"); } DH *dh = get_dh1024(); SSL_CTX_set_tmp_dh(ctx, dh); #ifndef OPENSSL_NO_ECDH EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_secp521r1); if (ecdh) { SSL_CTX_set_tmp_ecdh(ctx, ecdh); EC_KEY_free(ecdh); } #endif ssl = SSL_new(ctx); conn->ssl = ssl; SSL_set_app_data(ssl, NULL); if (NIL_P(verify_mode)) { /* SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL); */ } else { SSL_set_verify(ssl, NUM2INT(verify_mode), engine_verify_callback); } SSL_set_bio(ssl, conn->read, conn->write); SSL_set_accept_state(ssl); return obj; } VALUE engine_init_client(VALUE klass) { VALUE obj; ms_conn* conn = engine_alloc(klass, &obj); conn->ctx = SSL_CTX_new(DTLSv1_method()); conn->ssl = SSL_new(conn->ctx); SSL_set_app_data(conn->ssl, NULL); SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL); SSL_set_bio(conn->ssl, conn->read, conn->write); SSL_set_connect_state(conn->ssl); return obj; } VALUE engine_inject(VALUE self, VALUE str) { ms_conn* conn; long used; Data_Get_Struct(self, ms_conn, conn); StringValue(str); used = BIO_write(conn->read, RSTRING_PTR(str), (int)RSTRING_LEN(str)); if(used == 0 || used == -1) { return Qfalse; } return INT2FIX(used); } static VALUE eError; void raise_error(SSL* ssl, int result) { char buf[512]; char msg[512]; const char* err_str; int err = errno; int ssl_err = SSL_get_error(ssl, result); int verify_err = (int) SSL_get_verify_result(ssl); if(SSL_ERROR_SYSCALL == ssl_err) { snprintf(msg, sizeof(msg), "System error: %s - %d", strerror(err), err); } else if(SSL_ERROR_SSL == ssl_err) { if(X509_V_OK != verify_err) { err_str = X509_verify_cert_error_string(verify_err); snprintf(msg, sizeof(msg), "OpenSSL certificate verification error: %s - %d", err_str, verify_err); } else { err = (int) ERR_get_error(); ERR_error_string_n(err, buf, sizeof(buf)); snprintf(msg, sizeof(msg), "OpenSSL error: %s - %d", buf, err); } } else { snprintf(msg, sizeof(msg), "Unknown OpenSSL error: %d", ssl_err); } ERR_clear_error(); rb_raise(eError, "%s", msg); } VALUE engine_read(VALUE self) { ms_conn* conn; char buf[512]; int bytes, error; Data_Get_Struct(self, ms_conn, conn); ERR_clear_error(); bytes = SSL_read(conn->ssl, (void*)buf, sizeof(buf)); if(bytes > 0) { return rb_str_new(buf, bytes); } if(SSL_want_read(conn->ssl)) return Qnil; error = SSL_get_error(conn->ssl, bytes); if(error == SSL_ERROR_ZERO_RETURN) { rb_eof_error(); } else { raise_error(conn->ssl, bytes); } return Qnil; } VALUE engine_write(VALUE self, VALUE str) { ms_conn* conn; int bytes; Data_Get_Struct(self, ms_conn, conn); StringValue(str); ERR_clear_error(); bytes = SSL_write(conn->ssl, (void*)RSTRING_PTR(str), (int)RSTRING_LEN(str)); if(bytes > 0) { return INT2FIX(bytes); } if(SSL_want_write(conn->ssl)) return Qnil; raise_error(conn->ssl, bytes); return Qnil; } VALUE engine_extract(VALUE self) { ms_conn* conn; int bytes; size_t pending; char buf[512]; Data_Get_Struct(self, ms_conn, conn); pending = BIO_pending(conn->write); if(pending > 0) { bytes = BIO_read(conn->write, buf, sizeof(buf)); if(bytes > 0) { return rb_str_new(buf, bytes); } else if(!BIO_should_retry(conn->write)) { raise_error(conn->ssl, bytes); } } return Qnil; } VALUE engine_shutdown(VALUE self) { ms_conn* conn; int ok; Data_Get_Struct(self, ms_conn, conn); ERR_clear_error(); ok = SSL_shutdown(conn->ssl); if (ok == 0) { return Qfalse; } return Qtrue; } VALUE engine_init(VALUE self) { ms_conn* conn; Data_Get_Struct(self, ms_conn, conn); return SSL_in_init(conn->ssl) ? Qtrue : Qfalse; } VALUE engine_peercert(VALUE self) { ms_conn* conn; X509* cert; int bytes; unsigned char* buf = NULL; ms_cert_buf* cert_buf = NULL; VALUE rb_cert_buf; Data_Get_Struct(self, ms_conn, conn); cert = SSL_get_peer_certificate(conn->ssl); if(!cert) { /* * See if there was a failed certificate associated with this client. */ cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl); if(!cert_buf) { return Qnil; } buf = cert_buf->buf; bytes = cert_buf->bytes; } else { bytes = i2d_X509(cert, &buf); X509_free(cert); if(bytes < 0) { return Qnil; } } rb_cert_buf = rb_str_new((const char*)(buf), bytes); if(!cert_buf) { OPENSSL_free(buf); } return rb_cert_buf; } VALUE noop(VALUE self) { return Qnil; } void Init_mini_ssl(VALUE puma) { VALUE mod, eng; /* Fake operation for documentation (RDoc, YARD) */ #if 0 == 1 puma = rb_define_module("Puma"); #endif SSL_library_init(); OpenSSL_add_ssl_algorithms(); SSL_load_error_strings(); ERR_load_crypto_strings(); mod = rb_define_module_under(puma, "MiniSSL"); eng = rb_define_class_under(mod, "Engine", rb_cObject); // OpenSSL Build / Runtime/Load versions /* Version of OpenSSL that Puma was compiled with */ rb_define_const(mod, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT)); #if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000 /* Version of OpenSSL that Puma loaded with */ rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(OpenSSL_version(OPENSSL_VERSION))); #else rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION))); #endif rb_define_singleton_method(mod, "check", noop, 0); eError = rb_define_class_under(mod, "SSLError", rb_eStandardError); rb_define_singleton_method(eng, "server", engine_init_server, 1); rb_define_singleton_method(eng, "client", engine_init_client, 0); rb_define_method(eng, "inject", engine_inject, 1); rb_define_method(eng, "read", engine_read, 0); rb_define_method(eng, "write", engine_write, 1); rb_define_method(eng, "extract", engine_extract, 0); rb_define_method(eng, "shutdown", engine_shutdown, 0); rb_define_method(eng, "init?", engine_init, 0); rb_define_method(eng, "peercert", engine_peercert, 0); } #else VALUE raise_error(VALUE self) { rb_raise(rb_eStandardError, "SSL not available in this build"); return Qnil; } void Init_mini_ssl(VALUE puma) { VALUE mod; mod = rb_define_module_under(puma, "MiniSSL"); rb_define_class_under(mod, "SSLError", rb_eStandardError); rb_define_singleton_method(mod, "check", raise_error, 0); } #endif puma-3.12.4/ext/puma_http11/org/000077500000000000000000000000001362626474300163125ustar00rootroot00000000000000puma-3.12.4/ext/puma_http11/org/jruby/000077500000000000000000000000001362626474300174455ustar00rootroot00000000000000puma-3.12.4/ext/puma_http11/org/jruby/puma/000077500000000000000000000000001362626474300204075ustar00rootroot00000000000000puma-3.12.4/ext/puma_http11/org/jruby/puma/Http11.java000066400000000000000000000236231362626474300223410ustar00rootroot00000000000000package org.jruby.puma; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyHash; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.exceptions.RaiseException; import org.jruby.util.ByteList; /** * @author Ola Bini */ public class Http11 extends RubyObject { public final static int MAX_FIELD_NAME_LENGTH = 256; public final static String MAX_FIELD_NAME_LENGTH_ERR = "HTTP element FIELD_NAME is longer than the 256 allowed length."; public final static int MAX_FIELD_VALUE_LENGTH = 80 * 1024; public final static String MAX_FIELD_VALUE_LENGTH_ERR = "HTTP element FIELD_VALUE is longer than the 81920 allowed length."; public final static int MAX_REQUEST_URI_LENGTH = 1024 * 12; 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_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); public final static String MAX_HEADER_LENGTH_ERR = "HTTP element HEADER is longer than the 114688 allowed length."; private static ObjectAllocator ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new Http11(runtime, klass); } }; public static void createHttp11(Ruby runtime) { RubyModule mPuma = runtime.defineModule("Puma"); mPuma.defineClassUnder("HttpParserError",runtime.getClass("IOError"),runtime.getClass("IOError").getAllocator()); RubyClass cHttpParser = mPuma.defineClassUnder("HttpParser",runtime.getObject(),ALLOCATOR); cHttpParser.defineAnnotatedMethods(Http11.class); } private Ruby runtime; private RubyClass eHttpParserError; private Http11Parser hp; private RubyString body; public Http11(Ruby runtime, RubyClass clazz) { super(runtime,clazz); this.runtime = runtime; this.eHttpParserError = (RubyClass)runtime.getModule("Puma").getConstant("HttpParserError"); this.hp = new Http11Parser(); this.hp.parser.http_field = http_field; this.hp.parser.request_method = request_method; this.hp.parser.request_uri = request_uri; this.hp.parser.fragment = fragment; this.hp.parser.request_path = request_path; this.hp.parser.query_string = query_string; this.hp.parser.http_version = http_version; this.hp.parser.header_done = header_done; this.hp.parser.init(); } public void validateMaxLength(int len, int max, String msg) { if(len>max) { throw new RaiseException(runtime, eHttpParserError, msg, true); } } private Http11Parser.FieldCB http_field = new Http11Parser.FieldCB() { public void call(Object data, int field, int flen, int value, int vlen) { RubyHash req = (RubyHash)data; RubyString f; IRubyObject v; validateMaxLength(flen, MAX_FIELD_NAME_LENGTH, MAX_FIELD_NAME_LENGTH_ERR); validateMaxLength(vlen, MAX_FIELD_VALUE_LENGTH, MAX_FIELD_VALUE_LENGTH_ERR); ByteList b = new ByteList(Http11.this.hp.parser.buffer,field,flen); for(int i = 0,j = b.length();i= d.length()) { throw new RaiseException(runtime, eHttpParserError, "Requested start is after data buffer end.", true); } else { this.hp.parser.data = req_hash; this.hp.execute(d,from); validateMaxLength(this.hp.parser.nread,MAX_HEADER_LENGTH, MAX_HEADER_LENGTH_ERR); if(this.hp.has_error()) { throw new RaiseException(runtime, eHttpParserError, "Invalid HTTP format, parsing fails.", true); } else { return runtime.newFixnum(this.hp.parser.nread); } } } @JRubyMethod(name = "error?") public IRubyObject has_error() { return this.hp.has_error() ? runtime.getTrue() : runtime.getFalse(); } @JRubyMethod(name = "finished?") public IRubyObject is_finished() { return this.hp.is_finished() ? runtime.getTrue() : runtime.getFalse(); } @JRubyMethod public IRubyObject nread() { return runtime.newFixnum(this.hp.parser.nread); } @JRubyMethod public IRubyObject body() { return body; } }// Http11 puma-3.12.4/ext/puma_http11/org/jruby/puma/Http11Parser.java000066400000000000000000000343301362626474300235130ustar00rootroot00000000000000 // line 1 "ext/puma_http11/http11_parser.java.rl" package org.jruby.puma; import org.jruby.util.ByteList; public class Http11Parser { /** Machine **/ // line 65 "ext/puma_http11/http11_parser.java.rl" /** Data **/ // line 18 "ext/puma_http11/org/jruby/puma/Http11Parser.java" private static byte[] init__puma_parser_actions_0() { return new byte [] { 0, 1, 0, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 11, 1, 12, 1, 13, 2, 0, 8, 2, 1, 2, 2, 4, 5, 2, 10, 7, 2, 12, 7, 3, 9, 10, 7 }; } private static final byte _puma_parser_actions[] = init__puma_parser_actions_0(); private static short[] init__puma_parser_key_offsets_0() { return new short [] { 0, 0, 8, 17, 27, 29, 30, 31, 32, 33, 34, 36, 39, 41, 44, 45, 61, 62, 78, 80, 81, 89, 97, 107, 115, 125, 134, 142, 150, 159, 168, 177, 186, 195, 204, 213, 222, 231, 240, 249, 258, 267, 276, 285, 294, 303, 312, 313 }; } private static final short _puma_parser_key_offsets[] = init__puma_parser_key_offsets_0(); private static char[] init__puma_parser_trans_keys_0() { return new char [] { 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 42, 43, 47, 58, 45, 57, 65, 90, 97, 122, 32, 35, 72, 84, 84, 80, 47, 48, 57, 46, 48, 57, 48, 57, 13, 48, 57, 10, 13, 33, 124, 126, 35, 39, 42, 43, 45, 46, 48, 57, 65, 90, 94, 122, 10, 33, 58, 124, 126, 35, 39, 42, 43, 45, 46, 48, 57, 65, 90, 94, 122, 13, 32, 13, 32, 60, 62, 127, 0, 31, 34, 35, 32, 60, 62, 127, 0, 31, 34, 35, 43, 58, 45, 46, 48, 57, 65, 90, 97, 122, 32, 34, 35, 60, 62, 127, 0, 31, 32, 34, 35, 59, 60, 62, 63, 127, 0, 31, 32, 34, 35, 60, 62, 63, 127, 0, 31, 32, 34, 35, 60, 62, 127, 0, 31, 32, 34, 35, 60, 62, 127, 0, 31, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 0 }; } private static final char _puma_parser_trans_keys[] = init__puma_parser_trans_keys_0(); private static byte[] init__puma_parser_single_lengths_0() { return new byte [] { 0, 2, 3, 4, 2, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 4, 1, 4, 2, 1, 4, 4, 2, 6, 8, 7, 6, 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 0 }; } private static final byte _puma_parser_single_lengths[] = init__puma_parser_single_lengths_0(); private static byte[] init__puma_parser_range_lengths_0() { return new byte [] { 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 6, 0, 6, 0, 0, 2, 2, 4, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0 }; } private static final byte _puma_parser_range_lengths[] = init__puma_parser_range_lengths_0(); private static short[] init__puma_parser_index_offsets_0() { return new short [] { 0, 0, 6, 13, 21, 24, 26, 28, 30, 32, 34, 36, 39, 41, 44, 46, 57, 59, 70, 73, 75, 82, 89, 96, 104, 114, 123, 131, 139, 146, 153, 160, 167, 174, 181, 188, 195, 202, 209, 216, 223, 230, 237, 244, 251, 258, 265, 267 }; } private static final short _puma_parser_index_offsets[] = init__puma_parser_index_offsets_0(); private static byte[] init__puma_parser_indicies_0() { return new byte [] { 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 3, 1, 4, 5, 6, 7, 5, 5, 5, 1, 8, 9, 1, 10, 1, 11, 1, 12, 1, 13, 1, 14, 1, 15, 1, 16, 15, 1, 17, 1, 18, 17, 1, 19, 1, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 22, 1, 23, 24, 23, 23, 23, 23, 23, 23, 23, 23, 1, 26, 27, 25, 29, 28, 30, 1, 1, 1, 1, 1, 31, 32, 1, 1, 1, 1, 1, 33, 34, 35, 34, 34, 34, 34, 1, 8, 1, 9, 1, 1, 1, 1, 35, 36, 1, 38, 39, 1, 1, 40, 1, 1, 37, 8, 1, 9, 1, 1, 42, 1, 1, 41, 43, 1, 45, 1, 1, 1, 1, 44, 46, 1, 48, 1, 1, 1, 1, 47, 2, 49, 49, 49, 49, 49, 1, 2, 50, 50, 50, 50, 50, 1, 2, 51, 51, 51, 51, 51, 1, 2, 52, 52, 52, 52, 52, 1, 2, 53, 53, 53, 53, 53, 1, 2, 54, 54, 54, 54, 54, 1, 2, 55, 55, 55, 55, 55, 1, 2, 56, 56, 56, 56, 56, 1, 2, 57, 57, 57, 57, 57, 1, 2, 58, 58, 58, 58, 58, 1, 2, 59, 59, 59, 59, 59, 1, 2, 60, 60, 60, 60, 60, 1, 2, 61, 61, 61, 61, 61, 1, 2, 62, 62, 62, 62, 62, 1, 2, 63, 63, 63, 63, 63, 1, 2, 64, 64, 64, 64, 64, 1, 2, 65, 65, 65, 65, 65, 1, 2, 66, 66, 66, 66, 66, 1, 2, 1, 1, 0 }; } private static final byte _puma_parser_indicies[] = init__puma_parser_indicies_0(); private static byte[] init__puma_parser_trans_targs_0() { return new byte [] { 2, 0, 3, 28, 4, 22, 24, 23, 5, 20, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 47, 17, 18, 19, 14, 18, 19, 14, 5, 21, 5, 21, 22, 23, 5, 24, 20, 25, 26, 25, 26, 5, 27, 20, 5, 27, 20, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46 }; } private static final byte _puma_parser_trans_targs[] = init__puma_parser_trans_targs_0(); private static byte[] init__puma_parser_trans_actions_0() { return new byte [] { 1, 0, 11, 0, 1, 1, 1, 1, 13, 13, 1, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 28, 23, 3, 5, 7, 31, 7, 0, 9, 25, 1, 15, 0, 0, 0, 37, 0, 37, 21, 21, 0, 0, 40, 17, 40, 34, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; } private static final byte _puma_parser_trans_actions[] = init__puma_parser_trans_actions_0(); static final int puma_parser_start = 1; static final int puma_parser_first_final = 47; static final int puma_parser_error = 0; // line 69 "ext/puma_http11/http11_parser.java.rl" public static interface ElementCB { public void call(Object data, int at, int length); } public static interface FieldCB { public void call(Object data, int field, int flen, int value, int vlen); } public static class HttpParser { int cs; int body_start; int content_len; int nread; int mark; int field_start; int field_len; int query_start; Object data; ByteList buffer; public FieldCB http_field; public ElementCB request_method; public ElementCB request_uri; public ElementCB fragment; public ElementCB request_path; public ElementCB query_string; public ElementCB http_version; public ElementCB header_done; public void init() { cs = 0; // line 225 "ext/puma_http11/org/jruby/puma/Http11Parser.java" { cs = puma_parser_start; } // line 104 "ext/puma_http11/http11_parser.java.rl" body_start = 0; content_len = 0; mark = 0; nread = 0; field_len = 0; field_start = 0; } } public final HttpParser parser = new HttpParser(); public int execute(ByteList buffer, int off) { int p, pe; int cs = parser.cs; int len = buffer.length(); assert off<=len : "offset past end of buffer"; p = off; pe = len; // get a copy of the bytes, since it may not start at 0 // FIXME: figure out how to just use the bytes in-place byte[] data = buffer.bytes(); parser.buffer = buffer; // line 257 "ext/puma_http11/org/jruby/puma/Http11Parser.java" { int _klen; int _trans = 0; int _acts; int _nacts; int _keys; int _goto_targ = 0; _goto: while (true) { switch ( _goto_targ ) { case 0: if ( p == pe ) { _goto_targ = 4; continue _goto; } if ( cs == 0 ) { _goto_targ = 5; continue _goto; } case 1: _match: do { _keys = _puma_parser_key_offsets[cs]; _trans = _puma_parser_index_offsets[cs]; _klen = _puma_parser_single_lengths[cs]; if ( _klen > 0 ) { int _lower = _keys; int _mid; int _upper = _keys + _klen - 1; while (true) { if ( _upper < _lower ) break; _mid = _lower + ((_upper-_lower) >> 1); if ( data[p] < _puma_parser_trans_keys[_mid] ) _upper = _mid - 1; else if ( data[p] > _puma_parser_trans_keys[_mid] ) _lower = _mid + 1; else { _trans += (_mid - _keys); break _match; } } _keys += _klen; _trans += _klen; } _klen = _puma_parser_range_lengths[cs]; if ( _klen > 0 ) { int _lower = _keys; int _mid; int _upper = _keys + (_klen<<1) - 2; while (true) { if ( _upper < _lower ) break; _mid = _lower + (((_upper-_lower) >> 1) & ~1); if ( data[p] < _puma_parser_trans_keys[_mid] ) _upper = _mid - 2; else if ( data[p] > _puma_parser_trans_keys[_mid+1] ) _lower = _mid + 2; else { _trans += ((_mid - _keys)>>1); break _match; } } _trans += _klen; } } while (false); _trans = _puma_parser_indicies[_trans]; cs = _puma_parser_trans_targs[_trans]; if ( _puma_parser_trans_actions[_trans] != 0 ) { _acts = _puma_parser_trans_actions[_trans]; _nacts = (int) _puma_parser_actions[_acts++]; while ( _nacts-- > 0 ) { switch ( _puma_parser_actions[_acts++] ) { case 0: // line 13 "ext/puma_http11/http11_parser.java.rl" {parser.mark = p; } break; case 1: // line 15 "ext/puma_http11/http11_parser.java.rl" { parser.field_start = p; } break; case 2: // line 16 "ext/puma_http11/http11_parser.java.rl" { /* FIXME stub */ } break; case 3: // line 17 "ext/puma_http11/http11_parser.java.rl" { parser.field_len = p-parser.field_start; } break; case 4: // line 21 "ext/puma_http11/http11_parser.java.rl" { parser.mark = p; } break; case 5: // line 22 "ext/puma_http11/http11_parser.java.rl" { if(parser.http_field != null) { parser.http_field.call(parser.data, parser.field_start, parser.field_len, parser.mark, p-parser.mark); } } break; case 6: // line 27 "ext/puma_http11/http11_parser.java.rl" { if(parser.request_method != null) parser.request_method.call(parser.data, parser.mark, p-parser.mark); } break; case 7: // line 31 "ext/puma_http11/http11_parser.java.rl" { if(parser.request_uri != null) parser.request_uri.call(parser.data, parser.mark, p-parser.mark); } break; case 8: // line 35 "ext/puma_http11/http11_parser.java.rl" { if(parser.fragment != null) parser.fragment.call(parser.data, parser.mark, p-parser.mark); } break; case 9: // line 40 "ext/puma_http11/http11_parser.java.rl" {parser.query_start = p; } break; case 10: // line 41 "ext/puma_http11/http11_parser.java.rl" { if(parser.query_string != null) parser.query_string.call(parser.data, parser.query_start, p-parser.query_start); } break; case 11: // line 46 "ext/puma_http11/http11_parser.java.rl" { if(parser.http_version != null) parser.http_version.call(parser.data, parser.mark, p-parser.mark); } break; case 12: // line 51 "ext/puma_http11/http11_parser.java.rl" { if(parser.request_path != null) parser.request_path.call(parser.data, parser.mark, p-parser.mark); } break; case 13: // line 56 "ext/puma_http11/http11_parser.java.rl" { parser.body_start = p + 1; if(parser.header_done != null) parser.header_done.call(parser.data, p + 1, pe - p - 1); { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; // line 422 "ext/puma_http11/org/jruby/puma/Http11Parser.java" } } } case 2: if ( cs == 0 ) { _goto_targ = 5; continue _goto; } if ( ++p != pe ) { _goto_targ = 1; continue _goto; } case 4: case 5: } break; } } // line 130 "ext/puma_http11/http11_parser.java.rl" parser.cs = cs; parser.nread += (p - off); assert p <= pe : "buffer overflow after parsing execute"; assert parser.nread <= len : "nread longer than length"; assert parser.body_start <= len : "body starts after buffer end"; assert parser.mark < len : "mark is after buffer end"; assert parser.field_len <= len : "field has length longer than whole buffer"; assert parser.field_start < len : "field starts after buffer end"; return parser.nread; } public int finish() { if(has_error()) { return -1; } else if(is_finished()) { return 1; } else { return 0; } } public boolean has_error() { return parser.cs == puma_parser_error; } public boolean is_finished() { return parser.cs == puma_parser_first_final; } } puma-3.12.4/ext/puma_http11/org/jruby/puma/MiniSSL.java000066400000000000000000000256751362626474300225470ustar00rootroot00000000000000package org.jruby.puma; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyModule; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.javasupport.JavaEmbedUtils; import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import static javax.net.ssl.SSLEngineResult.Status; import static javax.net.ssl.SSLEngineResult.HandshakeStatus; public class MiniSSL extends RubyObject { private static ObjectAllocator ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new MiniSSL(runtime, klass); } }; public static void createMiniSSL(Ruby runtime) { RubyModule mPuma = runtime.defineModule("Puma"); RubyModule ssl = mPuma.defineModuleUnder("MiniSSL"); mPuma.defineClassUnder("SSLError", runtime.getClass("IOError"), runtime.getClass("IOError").getAllocator()); RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR); eng.defineAnnotatedMethods(MiniSSL.class); } /** * Fairly transparent wrapper around {@link java.nio.ByteBuffer} which adds the enhancements we need */ private static class MiniSSLBuffer { ByteBuffer buffer; private MiniSSLBuffer(int capacity) { buffer = ByteBuffer.allocate(capacity); } private MiniSSLBuffer(byte[] initialContents) { buffer = ByteBuffer.wrap(initialContents); } public void clear() { buffer.clear(); } public void compact() { buffer.compact(); } public void flip() { buffer.flip(); } public boolean hasRemaining() { return buffer.hasRemaining(); } public int position() { return buffer.position(); } public ByteBuffer getRawBuffer() { return buffer; } /** * Writes bytes to the buffer after ensuring there's room */ public void put(byte[] bytes) { if (buffer.remaining() < bytes.length) { resize(buffer.limit() + bytes.length); } buffer.put(bytes); } /** * Ensures that newCapacity bytes can be written to this buffer, only re-allocating if necessary */ public void resize(int newCapacity) { if (newCapacity > buffer.capacity()) { ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity); buffer.flip(); dstTmp.put(buffer); buffer = dstTmp; } else { buffer.limit(newCapacity); } } /** * Drains the buffer to a ByteList, or returns null for an empty buffer */ public ByteList asByteList() { buffer.flip(); if (!buffer.hasRemaining()) { buffer.clear(); return null; } byte[] bss = new byte[buffer.limit()]; buffer.get(bss); buffer.clear(); return new ByteList(bss); } @Override public String toString() { return buffer.toString(); } } private SSLEngine engine; private MiniSSLBuffer inboundNetData; private MiniSSLBuffer outboundAppData; private MiniSSLBuffer outboundNetData; public MiniSSL(Ruby runtime, RubyClass klass) { super(runtime, klass); } @JRubyMethod(meta = true) public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext) { RubyClass klass = (RubyClass) recv; return klass.newInstance(context, new IRubyObject[] { miniSSLContext }, Block.NULL_BLOCK); } @JRubyMethod public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType()); char[] password = miniSSLContext.callMethod(threadContext, "keystore_pass").convertToString().asJavaString().toCharArray(); String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString(); ks.load(new FileInputStream(keystoreFile), password); ts.load(new FileInputStream(keystoreFile), password); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, password); TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(ts); SSLContext sslCtx = SSLContext.getInstance("TLS"); sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); engine = sslCtx.createSSLEngine(); String[] protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" }; engine.setEnabledProtocols(protocols); engine.setUseClientMode(false); long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue(); if ((verify_mode & 0x1) != 0) { // 'peer' engine.setWantClientAuth(true); } if ((verify_mode & 0x2) != 0) { // 'force_peer' engine.setNeedClientAuth(true); } IRubyObject sslCipherListObject = miniSSLContext.callMethod(threadContext, "ssl_cipher_list"); if (!sslCipherListObject.isNil()) { String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(","); engine.setEnabledCipherSuites(sslCipherList); } SSLSession session = engine.getSession(); inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize()); outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize()); outboundAppData.flip(); outboundNetData = new MiniSSLBuffer(session.getPacketBufferSize()); return this; } @JRubyMethod public IRubyObject inject(IRubyObject arg) { try { byte[] bytes = arg.convertToString().getBytes(); inboundNetData.put(bytes); return this; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } private enum SSLOperation { WRAP, UNWRAP } private SSLEngineResult doOp(SSLOperation sslOp, MiniSSLBuffer src, MiniSSLBuffer dst) throws SSLException { SSLEngineResult res = null; boolean retryOp = true; while (retryOp) { switch (sslOp) { case WRAP: res = engine.wrap(src.getRawBuffer(), dst.getRawBuffer()); break; case UNWRAP: res = engine.unwrap(src.getRawBuffer(), dst.getRawBuffer()); break; default: throw new IllegalStateException("Unknown SSLOperation: " + sslOp); } switch (res.getStatus()) { case BUFFER_OVERFLOW: // increase the buffer size to accommodate the overflowing data int newSize = Math.max(engine.getSession().getPacketBufferSize(), engine.getSession().getApplicationBufferSize()); dst.resize(newSize + dst.position()); // retry the operation retryOp = true; break; case BUFFER_UNDERFLOW: // need to wait for more data to come in before we retry retryOp = false; break; default: // other cases are OK and CLOSED. We're done here. retryOp = false; } } // after each op, run any delegated tasks if needed if(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { Runnable runnable; while ((runnable = engine.getDelegatedTask()) != null) { runnable.run(); } } return res; } @JRubyMethod public IRubyObject read() throws Exception { try { inboundNetData.flip(); if(!inboundNetData.hasRemaining()) { return getRuntime().getNil(); } MiniSSLBuffer inboundAppData = new MiniSSLBuffer(engine.getSession().getApplicationBufferSize()); doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData); HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); boolean done = false; while (!done) { switch (handshakeStatus) { case NEED_WRAP: doOp(SSLOperation.WRAP, inboundAppData, outboundNetData); break; case NEED_UNWRAP: SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData); if (res.getStatus() == Status.BUFFER_UNDERFLOW) { // need more data before we can shake more hands done = true; } break; default: done = true; } handshakeStatus = engine.getHandshakeStatus(); } if (inboundNetData.hasRemaining()) { inboundNetData.compact(); } else { inboundNetData.clear(); } ByteList appDataByteList = inboundAppData.asByteList(); if (appDataByteList == null) { return getRuntime().getNil(); } RubyString str = getRuntime().newString(""); str.setValue(appDataByteList); return str; } catch (Exception e) { throw getRuntime().newEOFError(e.getMessage()); } } @JRubyMethod public IRubyObject write(IRubyObject arg) { try { byte[] bls = arg.convertToString().getBytes(); outboundAppData = new MiniSSLBuffer(bls); return getRuntime().newFixnum(bls.length); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } @JRubyMethod public IRubyObject extract() throws SSLException { try { ByteList dataByteList = outboundNetData.asByteList(); if (dataByteList != null) { RubyString str = getRuntime().newString(""); str.setValue(dataByteList); return str; } if (!outboundAppData.hasRemaining()) { return getRuntime().getNil(); } outboundNetData.clear(); doOp(SSLOperation.WRAP, outboundAppData, outboundNetData); dataByteList = outboundNetData.asByteList(); if (dataByteList == null) { return getRuntime().getNil(); } RubyString str = getRuntime().newString(""); str.setValue(dataByteList); return str; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } @JRubyMethod public IRubyObject peercert() throws CertificateEncodingException { try { return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded()); } catch (SSLPeerUnverifiedException ex) { return getRuntime().getNil(); } } } puma-3.12.4/ext/puma_http11/puma_http11.c000066400000000000000000000315701362626474300200400ustar00rootroot00000000000000/** * Copyright (c) 2005 Zed A. Shaw * You can redistribute it and/or modify it under the same terms as Ruby. * License 3-clause BSD */ #define RSTRING_NOT_MODIFIED 1 #include "ruby.h" #include "ext_help.h" #include #include #include "http11_parser.h" #ifndef MANAGED_STRINGS #ifndef RSTRING_PTR #define RSTRING_PTR(s) (RSTRING(s)->ptr) #endif #ifndef RSTRING_LEN #define RSTRING_LEN(s) (RSTRING(s)->len) #endif #define rb_extract_chars(e, sz) (*sz = RSTRING_LEN(e), RSTRING_PTR(e)) #define rb_free_chars(e) /* nothing */ #endif static VALUE eHttpParserError; #define HTTP_PREFIX "HTTP_" #define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1) static VALUE global_request_method; static VALUE global_request_uri; static VALUE global_fragment; static VALUE global_query_string; static VALUE global_http_version; static VALUE global_request_path; /** Defines common length and error messages for input length validation. */ #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length (was %d)" /** Validates the max length of given input and throws an HttpParserError exception if over. */ #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); } /** Defines global strings in the init method. */ #define DEF_GLOBAL(N, val) global_##N = rb_str_new2(val); rb_global_variable(&global_##N) /* Defines the maximum allowed lengths for various input elements.*/ 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(QUERY_STRING, (1024 * 10)); DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32))); struct common_field { const size_t len; const char *name; int raw; VALUE value; }; /* * A list of common HTTP headers we expect to receive. * This allows us to avoid repeatedly creating identical string * objects to be used with rb_hash_aset(). */ static struct common_field common_http_fields[] = { # define f(N) { (sizeof(N) - 1), N, 0, Qnil } # define fr(N) { (sizeof(N) - 1), N, 1, Qnil } f("ACCEPT"), f("ACCEPT_CHARSET"), f("ACCEPT_ENCODING"), f("ACCEPT_LANGUAGE"), f("ALLOW"), f("AUTHORIZATION"), f("CACHE_CONTROL"), f("CONNECTION"), f("CONTENT_ENCODING"), fr("CONTENT_LENGTH"), fr("CONTENT_TYPE"), f("COOKIE"), f("DATE"), f("EXPECT"), f("FROM"), f("HOST"), f("IF_MATCH"), f("IF_MODIFIED_SINCE"), f("IF_NONE_MATCH"), f("IF_RANGE"), f("IF_UNMODIFIED_SINCE"), f("KEEP_ALIVE"), /* Firefox sends this */ f("MAX_FORWARDS"), f("PRAGMA"), f("PROXY_AUTHORIZATION"), f("RANGE"), f("REFERER"), f("TE"), f("TRAILER"), f("TRANSFER_ENCODING"), f("UPGRADE"), f("USER_AGENT"), f("VIA"), f("X_FORWARDED_FOR"), /* common for proxies */ f("X_REAL_IP"), /* common for proxies */ f("WARNING") # undef f }; /* * qsort(3) and bsearch(3) improve average performance slightly, but may * not be worth it for lack of portability to certain platforms... */ #if defined(HAVE_QSORT_BSEARCH) /* sort by length, then by name if there's a tie */ static int common_field_cmp(const void *a, const void *b) { struct common_field *cfa = (struct common_field *)a; struct common_field *cfb = (struct common_field *)b; signed long diff = cfa->len - cfb->len; return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len); } #endif /* HAVE_QSORT_BSEARCH */ static void init_common_fields(void) { unsigned i; struct common_field *cf = common_http_fields; char tmp[256]; /* MAX_FIELD_NAME_LENGTH */ memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN); for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) { if(cf->raw) { cf->value = rb_str_new(cf->name, cf->len); } else { memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1); cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len); } rb_global_variable(&cf->value); } #if defined(HAVE_QSORT_BSEARCH) qsort(common_http_fields, ARRAY_SIZE(common_http_fields), sizeof(struct common_field), common_field_cmp); #endif /* HAVE_QSORT_BSEARCH */ } static VALUE find_common_field_value(const char *field, size_t flen) { #if defined(HAVE_QSORT_BSEARCH) struct common_field key; struct common_field *found; key.name = field; key.len = (signed long)flen; found = (struct common_field *)bsearch(&key, common_http_fields, ARRAY_SIZE(common_http_fields), sizeof(struct common_field), common_field_cmp); return found ? found->value : Qnil; #else /* !HAVE_QSORT_BSEARCH */ unsigned i; struct common_field *cf = common_http_fields; for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) { if (cf->len == flen && !memcmp(cf->name, field, flen)) return cf->value; } return Qnil; #endif /* !HAVE_QSORT_BSEARCH */ } void http_field(puma_parser* hp, const char *field, size_t flen, const char *value, size_t vlen) { VALUE f = Qnil; VALUE v; VALIDATE_MAX_LENGTH(flen, FIELD_NAME); VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE); f = find_common_field_value(field, flen); if (f == Qnil) { /* * We got a strange header that we don't have a memoized value for. * Fallback to creating a new string to use as a hash key. */ size_t new_size = HTTP_PREFIX_LEN + flen; assert(new_size < BUFFER_LEN); memcpy(hp->buf, HTTP_PREFIX, HTTP_PREFIX_LEN); memcpy(hp->buf + HTTP_PREFIX_LEN, field, flen); f = rb_str_new(hp->buf, new_size); } /* check for duplicate header */ v = rb_hash_aref(hp->request, f); if (v == Qnil) { v = rb_str_new(value, vlen); rb_hash_aset(hp->request, f, v); } else { /* if duplicate header, normalize to comma-separated values */ rb_str_cat2(v, ", "); rb_str_cat(v, value, vlen); } } void request_method(puma_parser* hp, const char *at, size_t length) { VALUE val = Qnil; val = rb_str_new(at, length); rb_hash_aset(hp->request, global_request_method, val); } void request_uri(puma_parser* hp, const char *at, size_t length) { VALUE val = Qnil; VALIDATE_MAX_LENGTH(length, REQUEST_URI); val = rb_str_new(at, length); rb_hash_aset(hp->request, global_request_uri, val); } void fragment(puma_parser* hp, const char *at, size_t length) { VALUE val = Qnil; VALIDATE_MAX_LENGTH(length, FRAGMENT); val = rb_str_new(at, length); rb_hash_aset(hp->request, global_fragment, val); } void request_path(puma_parser* hp, const char *at, size_t length) { VALUE val = Qnil; VALIDATE_MAX_LENGTH(length, REQUEST_PATH); val = rb_str_new(at, length); rb_hash_aset(hp->request, global_request_path, val); } void query_string(puma_parser* hp, const char *at, size_t length) { VALUE val = Qnil; VALIDATE_MAX_LENGTH(length, QUERY_STRING); val = rb_str_new(at, length); rb_hash_aset(hp->request, global_query_string, val); } void http_version(puma_parser* hp, const char *at, size_t length) { VALUE val = rb_str_new(at, length); rb_hash_aset(hp->request, global_http_version, val); } /** Finalizes the request header to have a bunch of stuff that's needed. */ void header_done(puma_parser* hp, const char *at, size_t length) { hp->body = rb_str_new(at, length); } void HttpParser_free(void *data) { TRACE(); if(data) { xfree(data); } } void HttpParser_mark(puma_parser* hp) { if(hp->request) rb_gc_mark(hp->request); if(hp->body) rb_gc_mark(hp->body); } VALUE HttpParser_alloc(VALUE klass) { puma_parser *hp = ALLOC_N(puma_parser, 1); TRACE(); hp->http_field = http_field; hp->request_method = request_method; hp->request_uri = request_uri; hp->fragment = fragment; hp->request_path = request_path; hp->query_string = query_string; hp->http_version = http_version; hp->header_done = header_done; hp->request = Qnil; puma_parser_init(hp); return Data_Wrap_Struct(klass, HttpParser_mark, HttpParser_free, hp); } /** * call-seq: * parser.new -> parser * * Creates a new parser. */ VALUE HttpParser_init(VALUE self) { puma_parser *http = NULL; DATA_GET(self, puma_parser, http); puma_parser_init(http); return self; } /** * call-seq: * parser.reset -> nil * * Resets the parser to it's initial state so that you can reuse it * rather than making new ones. */ VALUE HttpParser_reset(VALUE self) { puma_parser *http = NULL; DATA_GET(self, puma_parser, http); puma_parser_init(http); return Qnil; } /** * call-seq: * parser.finish -> true/false * * Finishes a parser early which could put in a "good" or bad state. * You should call reset after finish it or bad things will happen. */ VALUE HttpParser_finish(VALUE self) { puma_parser *http = NULL; DATA_GET(self, puma_parser, http); puma_parser_finish(http); return puma_parser_is_finished(http) ? Qtrue : Qfalse; } /** * call-seq: * parser.execute(req_hash, data, start) -> Integer * * Takes a Hash and a String of data, parses the String of data filling in the Hash * returning an Integer to indicate how much of the data has been read. No matter * what the return value, you should call HttpParser#finished? and HttpParser#error? * to figure out if it's done parsing or there was an error. * * This function now throws an exception when there is a parsing error. This makes * the logic for working with the parser much easier. You can still test for an * error, but now you need to wrap the parser with an exception handling block. * * The third argument allows for parsing a partial request and then continuing * the parsing from that position. It needs all of the original data as well * so you have to append to the data buffer as you read. */ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start) { puma_parser *http = NULL; int from = 0; char *dptr = NULL; long dlen = 0; DATA_GET(self, puma_parser, http); from = FIX2INT(start); dptr = rb_extract_chars(data, &dlen); if(from >= dlen) { rb_free_chars(dptr); rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end."); } else { http->request = req_hash; puma_parser_execute(http, dptr, dlen, from); rb_free_chars(dptr); VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER); if(puma_parser_has_error(http)) { rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails."); } else { return INT2FIX(puma_parser_nread(http)); } } } /** * call-seq: * parser.error? -> true/false * * Tells you whether the parser is in an error state. */ VALUE HttpParser_has_error(VALUE self) { puma_parser *http = NULL; DATA_GET(self, puma_parser, http); return puma_parser_has_error(http) ? Qtrue : Qfalse; } /** * call-seq: * parser.finished? -> true/false * * Tells you whether the parser is finished or not and in a good state. */ VALUE HttpParser_is_finished(VALUE self) { puma_parser *http = NULL; DATA_GET(self, puma_parser, http); return puma_parser_is_finished(http) ? Qtrue : Qfalse; } /** * call-seq: * parser.nread -> Integer * * Returns the amount of data processed so far during this processing cycle. It is * set to 0 on initialize or reset calls and is incremented each time execute is called. */ VALUE HttpParser_nread(VALUE self) { puma_parser *http = NULL; DATA_GET(self, puma_parser, http); return INT2FIX(http->nread); } /** * call-seq: * parser.body -> nil or String * * If the request included a body, returns it. */ VALUE HttpParser_body(VALUE self) { puma_parser *http = NULL; DATA_GET(self, puma_parser, http); return http->body; } void Init_io_buffer(VALUE puma); void Init_mini_ssl(VALUE mod); void Init_puma_http11() { VALUE mPuma = rb_define_module("Puma"); VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject); DEF_GLOBAL(request_method, "REQUEST_METHOD"); DEF_GLOBAL(request_uri, "REQUEST_URI"); DEF_GLOBAL(fragment, "FRAGMENT"); DEF_GLOBAL(query_string, "QUERY_STRING"); DEF_GLOBAL(http_version, "HTTP_VERSION"); DEF_GLOBAL(request_path, "REQUEST_PATH"); eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError); rb_global_variable(&eHttpParserError); rb_define_alloc_func(cHttpParser, HttpParser_alloc); rb_define_method(cHttpParser, "initialize", HttpParser_init, 0); rb_define_method(cHttpParser, "reset", HttpParser_reset, 0); rb_define_method(cHttpParser, "finish", HttpParser_finish, 0); rb_define_method(cHttpParser, "execute", HttpParser_execute, 3); rb_define_method(cHttpParser, "error?", HttpParser_has_error, 0); rb_define_method(cHttpParser, "finished?", HttpParser_is_finished, 0); rb_define_method(cHttpParser, "nread", HttpParser_nread, 0); rb_define_method(cHttpParser, "body", HttpParser_body, 0); init_common_fields(); Init_io_buffer(mPuma); Init_mini_ssl(mPuma); } puma-3.12.4/lib/000077500000000000000000000000001362626474300133265ustar00rootroot00000000000000puma-3.12.4/lib/puma.rb000066400000000000000000000005511362626474300146160ustar00rootroot00000000000000# Standard libraries require 'socket' require 'tempfile' require 'time' require 'etc' require 'uri' require 'stringio' require 'thread' module Puma autoload :Const, 'puma/const' autoload :Server, 'puma/server' autoload :Launcher, 'puma/launcher' def self.stats_object=(val) @get_stats = val end def self.stats @get_stats.stats end end puma-3.12.4/lib/puma/000077500000000000000000000000001362626474300142705ustar00rootroot00000000000000puma-3.12.4/lib/puma/accept_nonblock.rb000066400000000000000000000007461362626474300177500ustar00rootroot00000000000000require 'openssl' module OpenSSL module SSL class SSLServer unless public_method_defined? :accept_nonblock def accept_nonblock sock = @svr.accept_nonblock begin ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx) ssl.sync_close = true ssl.accept if @start_immediately ssl rescue SSLError => ex sock.close raise ex end end end end end end puma-3.12.4/lib/puma/app/000077500000000000000000000000001362626474300150505ustar00rootroot00000000000000puma-3.12.4/lib/puma/app/status.rb000066400000000000000000000036561362626474300167320ustar00rootroot00000000000000module Puma module App class Status def initialize(cli) @cli = cli @auth_token = nil end OK_STATUS = '{ "status": "ok" }'.freeze attr_accessor :auth_token def authenticate(env) return true unless @auth_token env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}") end def rack_response(status, body, content_type='application/json') headers = { 'Content-Type' => content_type, 'Content-Length' => body.bytesize.to_s } [status, headers, [body]] end def call(env) unless authenticate(env) return rack_response(403, 'Invalid auth token', 'text/plain') end case env['PATH_INFO'] when /\/stop$/ @cli.stop return rack_response(200, OK_STATUS) when /\/halt$/ @cli.halt return rack_response(200, OK_STATUS) when /\/restart$/ @cli.restart return rack_response(200, OK_STATUS) when /\/phased-restart$/ if !@cli.phased_restart return rack_response(404, '{ "error": "phased restart not available" }') else return rack_response(200, OK_STATUS) end when /\/reload-worker-directory$/ if !@cli.send(:reload_worker_directory) return rack_response(404, '{ "error": "reload_worker_directory not available" }') else return rack_response(200, OK_STATUS) end when /\/gc$/ GC.start return rack_response(200, OK_STATUS) when /\/gc-stats$/ json = "{" + GC.stat.map { |k, v| "\"#{k}\": #{v}" }.join(",") + "}" return rack_response(200, json) when /\/stats$/ return rack_response(200, @cli.stats) else rack_response 404, "Unsupported action", 'text/plain' end end end end end puma-3.12.4/lib/puma/binder.rb000066400000000000000000000257121362626474300160670ustar00rootroot00000000000000# frozen_string_literal: true require 'uri' require 'socket' require 'puma/const' require 'puma/util' module Puma class Binder include Puma::Const RACK_VERSION = [1,3].freeze def initialize(events) @events = events @listeners = [] @inherited_fds = {} @activated_sockets = {} @unix_paths = [] @proto_env = { "rack.version".freeze => RACK_VERSION, "rack.errors".freeze => events.stderr, "rack.multithread".freeze => true, "rack.multiprocess".freeze => false, "rack.run_once".freeze => false, "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "", # I'd like to set a default CONTENT_TYPE here but some things # depend on their not being a default set and inferring # it from the content. And so if i set it here, it won't # infer properly. "QUERY_STRING".freeze => "", SERVER_PROTOCOL => HTTP_11, SERVER_SOFTWARE => PUMA_SERVER_STRING, GATEWAY_INTERFACE => CGI_VER } @envs = {} @ios = [] end attr_reader :listeners, :ios def env(sock) @envs.fetch(sock, @proto_env) end def close @ios.each { |i| i.close } @unix_paths.each { |i| File.unlink i } end def import_from_env remove = [] 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' end end remove.each do |k| ENV.delete k end end def parse(binds, logger) binds.each do |str| uri = URI.parse str case uri.scheme when "tcp" if fd = @inherited_fds.delete(str) io = inherit_tcp_listener uri.host, uri.port, fd logger.log "* Inherited #{str}" elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ]) io = inherit_tcp_listener uri.host, uri.port, sock logger.log "* Activated #{str}" else params = Util.parse_query uri.query opt = params.key?('low_latency') bak = params.fetch('backlog', 1024).to_i io = add_tcp_listener uri.host, uri.port, opt, bak logger.log "* Listening on #{str}" end @listeners << [str, io] if io when "unix" path = "#{uri.host}#{uri.path}".gsub("%20", " ") if fd = @inherited_fds.delete(str) io = inherit_unix_listener path, fd logger.log "* Inherited #{str}" elsif sock = @activated_sockets.delete([ :unix, path ]) io = inherit_unix_listener path, sock logger.log "* Activated #{str}" else umask = nil mode = nil backlog = 1024 if uri.query params = Util.parse_query uri.query if u = params['umask'] # Use Integer() to respect the 0 prefix as octal umask = Integer(u) end if u = params['mode'] mode = Integer('0'+u) end if u = params['backlog'] backlog = Integer(u) end end io = add_unix_listener path, umask, mode, backlog logger.log "* Listening on #{str}" end @listeners << [str, io] when "ssl" params = Util.parse_query uri.query require 'puma/minissl' MiniSSL.check ctx = MiniSSL::Context.new if defined?(JRUBY_VERSION) unless params['keystore'] @events.error "Please specify the Java keystore via 'keystore='" end ctx.keystore = params['keystore'] unless params['keystore-pass'] @events.error "Please specify the Java keystore password via 'keystore-pass='" end ctx.keystore_pass = params['keystore-pass'] ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list'] else unless params['key'] @events.error "Please specify the SSL key via 'key='" end ctx.key = params['key'] unless params['cert'] @events.error "Please specify the SSL cert via 'cert='" end ctx.cert = params['cert'] if ['peer', 'force_peer'].include?(params['verify_mode']) unless params['ca'] @events.error "Please specify the SSL ca via 'ca='" end end ctx.ca = params['ca'] if params['ca'] ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter'] end if params['verify_mode'] ctx.verify_mode = case params['verify_mode'] when "peer" MiniSSL::VERIFY_PEER when "force_peer" MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT when "none" MiniSSL::VERIFY_NONE else @events.error "Please specify a valid verify_mode=" MiniSSL::VERIFY_NONE end end if fd = @inherited_fds.delete(str) logger.log "* Inherited #{str}" io = inherit_ssl_listener fd, ctx elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ]) io = inherit_ssl_listener sock, ctx logger.log "* Activated #{str}" else io = add_ssl_listener uri.host, uri.port, ctx logger.log "* Listening on #{str}" end @listeners << [str, io] if io else logger.error "Invalid URI: #{str}" end end # If we inherited fds but didn't use them (because of a # configuration change), then be sure to close them. @inherited_fds.each do |str, fd| logger.log "* Closing unused inherited connection: #{str}" begin IO.for_fd(fd).close rescue SystemCallError end # We have to unlink a unix socket path that's not being used uri = URI.parse str if uri.scheme == "unix" path = "#{uri.host}#{uri.path}" File.unlink path end end # Also close any unused activated sockets @activated_sockets.each do |key, sock| logger.log "* Closing unused activated socket: #{key.join ':'}" begin sock.close rescue SystemCallError end # We have to unlink a unix socket path that's not being used File.unlink key[1] if key[0] == :unix 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. # # +backlog+ indicates how many unaccepted connections the kernel should # allow to accumulate before returning connection refused. # def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024) if host == "localhost" loopback_addresses.each do |addr| add_tcp_listener addr, port, optimize_for_latency, backlog end return end host = host[1..-2] if host and host[0..0] == '[' s = TCPServer.new(host, port) if optimize_for_latency s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) end s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true) s.listen backlog @connected_port = s.addr[1] @ios << s s end attr_reader :connected_port def inherit_tcp_listener(host, port, fd) if fd.kind_of? TCPServer s = fd else s = TCPServer.for_fd(fd) end @ios << s s end def add_ssl_listener(host, port, ctx, optimize_for_latency=true, backlog=1024) require 'puma/minissl' MiniSSL.check if host == "localhost" loopback_addresses.each do |addr| add_ssl_listener addr, port, ctx, optimize_for_latency, backlog end return end host = host[1..-2] if host[0..0] == '[' s = TCPServer.new(host, port) if optimize_for_latency s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) end s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true) s.listen backlog ssl = MiniSSL::Server.new s, ctx env = @proto_env.dup env[HTTPS_KEY] = HTTPS @envs[ssl] = env @ios << ssl s end def inherit_ssl_listener(fd, ctx) require 'puma/minissl' MiniSSL.check if fd.kind_of? TCPServer s = fd else s = TCPServer.for_fd(fd) end ssl = MiniSSL::Server.new(s, ctx) env = @proto_env.dup env[HTTPS_KEY] = HTTPS @envs[ssl] = env @ios << ssl s end # Tell the server to listen on +path+ as a UNIX domain socket. # def add_unix_listener(path, umask=nil, mode=nil, backlog=1024) @unix_paths << path # Let anyone connect by default umask ||= 0 begin old_mask = File.umask(umask) if File.exist? path begin old = UNIXSocket.new path rescue SystemCallError, IOError File.unlink path else old.close raise "There is already a server bound to: #{path}" end end s = UNIXServer.new(path) s.listen backlog @ios << s ensure File.umask old_mask end if mode File.chmod mode, path end env = @proto_env.dup env[REMOTE_ADDR] = "127.0.0.1" @envs[s] = env s end def inherit_unix_listener(path, fd) @unix_paths << path if fd.kind_of? TCPServer s = fd else s = UNIXServer.for_fd fd end @ios << s env = @proto_env.dup env[REMOTE_ADDR] = "127.0.0.1" @envs[s] = env s end end end puma-3.12.4/lib/puma/cli.rb000066400000000000000000000150261362626474300153700ustar00rootroot00000000000000# frozen_string_literal: true require 'optparse' require 'uri' require 'puma' require 'puma/configuration' require 'puma/launcher' require 'puma/const' require 'puma/events' module Puma class << self # The CLI exports its Puma::Configuration object here to allow # apps to pick it up. An app needs to use it conditionally though # since it is not set if the app is launched via another # mechanism than the CLI class. attr_accessor :cli_config end # Handles invoke a Puma::Server in a command line style. # class CLI KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE # Create a new CLI object using +argv+ as the command line # arguments. # # +stdout+ and +stderr+ can be set to IO-like objects which # this object will report status on. # def initialize(argv, events=Events.stdio) @debug = false @argv = argv.dup @events = events @conf = nil @stdout = nil @stderr = nil @append = false @control_url = nil @control_options = {} setup_options begin @parser.parse! @argv if file = @argv.shift @conf.configure do |user_config, file_config| file_config.rackup file end end rescue UnsupportedOption exit 1 end @conf.configure do |user_config, file_config| if @stdout || @stderr user_config.stdout_redirect @stdout, @stderr, @append end if @control_url user_config.activate_control_app @control_url, @control_options end end @launcher = Puma::Launcher.new(@conf, :events => @events, :argv => argv) end attr_reader :launcher # Parse the options, load the rackup, start the server and wait # for it to finish. # def run @launcher.run end private def unsupported(str) @events.error(str) raise UnsupportedOption end def configure_control_url(command_line_arg) if command_line_arg @control_url = command_line_arg elsif Puma.jruby? unsupported "No default url available on JRuby" end end # Build the OptionParser object to handle the available options. # def setup_options @conf = Configuration.new do |user_config, file_config| @parser = OptionParser.new do |o| o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg| user_config.bind arg end o.on "-C", "--config PATH", "Load PATH as a config file" do |arg| file_config.load arg end o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg| configure_control_url(arg) end # alias --control-url for backwards-compatibility o.on "--control URL", "DEPRECATED alias for --control-url" do |arg| configure_control_url(arg) end o.on "--control-token TOKEN", "The token to use as authentication for the control server" do |arg| @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 o.on "--dir DIR", "Change to DIR before starting" do |d| user_config.directory d end o.on "-e", "--environment ENVIRONMENT", "The environment to run the Rack app on (default development)" do |arg| user_config.environment arg end o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg| $LOAD_PATH.unshift(*arg.split(':')) end o.on "-p", "--port PORT", "Define the TCP port to bind to", "Use -b for more advanced options" do |arg| user_config.bind "tcp://#{Configuration::DefaultTCPHost}:#{arg}" end o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg| user_config.pidfile arg end o.on "--preload", "Preload the app. Cluster mode only" do user_config.preload_app! end o.on "--prune-bundler", "Prune out the bundler env if possible" do user_config.prune_bundler end o.on "-q", "--quiet", "Do not log requests internally (default true)" do user_config.quiet end o.on "-v", "--log-requests", "Log requests as they occur" do user_config.log_requests end o.on "-R", "--restart-cmd CMD", "The puma command to run during a hot restart", "Default: inferred" do |cmd| user_config.restart_command cmd end o.on "-S", "--state PATH", "Where to store the state details" do |arg| user_config.state_path arg end o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg| min, max = arg.split(":") if max user_config.threads min, max else user_config.threads min, min 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 o.on "-V", "--version", "Print the version information" do puts "puma version #{Puma::Const::VERSION}" exit 0 end o.on "-w", "--workers COUNT", "Activate cluster mode: How many worker processes to create" do |arg| user_config.workers arg end o.on "--tag NAME", "Additional text to display in process listing" do |arg| user_config.tag arg end o.on "--redirect-stdout FILE", "Redirect STDOUT to a specific file" do |arg| @stdout = arg.to_s end o.on "--redirect-stderr FILE", "Redirect STDERR to a specific file" do |arg| @stderr = arg.to_s end o.on "--[no-]redirect-append", "Append to redirected files" do |val| @append = val end o.banner = "puma " o.on_tail "-h", "--help", "Show help" do $stdout.puts o exit 0 end end end end end end puma-3.12.4/lib/puma/client.rb000066400000000000000000000245721362626474300161050ustar00rootroot00000000000000# frozen_string_literal: true class IO # We need to use this for a jruby work around on both 1.8 and 1.9. # So this either creates the constant (on 1.8), or harmlessly # reopens it (on 1.9). module WaitReadable end end require 'puma/detect' require 'puma/delegation' require 'tempfile' if Puma::IS_JRUBY # We have to work around some OpenSSL buffer/io-readiness bugs # so we pull it in regardless of if the user is binding # to an SSL socket require 'openssl' end module Puma class ConnectionError < RuntimeError; end # An instance of this class represents a unique request from a client. # For example a web request from a browser or from CURL. This # # An instance of `Puma::Client` can be used as if it were an IO object # for example it is passed into `IO.select` inside of the `Puma::Reactor`. # This is accomplished by the `to_io` method which gets called on any # non-IO objects being used with the IO api such as `IO.select. # # Instances of this class are responsible for knowing if # the header and body are fully buffered via the `try_to_finish` method. # They can be used to "time out" a response via the `timeout_at` reader. class Client include Puma::Const extend Puma::Delegation def initialize(io, env=nil) @io = io @to_io = io.to_io @proto_env = env if !env @env = nil else @env = env.dup end @parser = HttpParser.new @parsed_bytes = 0 @read_header = true @ready = false @body = nil @buffer = nil @tempfile = nil @timeout_at = nil @requests_served = 0 @hijacked = false @peerip = nil @remote_addr_header = nil end attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked, :tempfile attr_writer :peerip attr_accessor :remote_addr_header forward :closed?, :@io def inspect "#" end # For the hijack protocol (allows us to just put the Client object # into the env) def call @hijacked = true env[HIJACK_IO] ||= @io end def in_data_phase !@read_header end def set_timeout(val) @timeout_at = Time.now + val end def reset(fast_check=true) @parser.reset @read_header = true @env = @proto_env.dup @body = nil @tempfile = nil @parsed_bytes = 0 @ready = false if @buffer @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes) if @parser.finished? return setup_body elsif @parsed_bytes >= MAX_HEADER raise HttpParserError, "HEADER is longer than allowed, aborting client early." end return false elsif fast_check && IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT) return try_to_finish end end def close begin @io.close rescue IOError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue end end # The object used for a request with no body. All requests with # no body share this one object since it has no state. EmptyBody = NullIO.new def setup_chunked_body(body) @chunked_body = true @partial_part_left = 0 @prev_chunk = "" @body = Tempfile.new(Const::PUMA_TMP_BASE) @body.binmode @tempfile = @body return decode_chunk(body) end def decode_chunk(chunk) if @partial_part_left > 0 if @partial_part_left <= chunk.size @body << chunk[0..(@partial_part_left-3)] # skip the \r\n chunk = chunk[@partial_part_left..-1] else @body << chunk @partial_part_left -= chunk.size return false end end if @prev_chunk.empty? io = StringIO.new(chunk) else io = StringIO.new(@prev_chunk+chunk) @prev_chunk = "" end while !io.eof? line = io.gets if line.end_with?("\r\n") len = line.strip.to_i(16) if len == 0 @body.rewind rest = io.read rest = rest[2..-1] if rest.start_with?("\r\n") @buffer = rest.empty? ? nil : rest @requests_served += 1 @ready = true return true end len += 2 part = io.read(len) unless part @partial_part_left = len next end got = part.size case when got == len @body << part[0..-3] # to skip the ending \r\n when got <= len - 2 @body << part @partial_part_left = len - part.size when got == len - 1 # edge where we get just \r but not \n @body << part[0..-2] @partial_part_left = len - part.size end else @prev_chunk = line return false end end return false end def read_chunked_body while true begin chunk = @io.read_nonblock(4096) rescue Errno::EAGAIN return false rescue SystemCallError, IOError raise ConnectionError, "Connection error detected during read" end # No chunk means a closed socket unless chunk @body.close @buffer = nil @requests_served += 1 @ready = true raise EOFError end return true if decode_chunk(chunk) end end def setup_body if @env[HTTP_EXPECT] == CONTINUE # TODO allow a hook here to check the headers before # going forward @io << HTTP_11_100 @io.flush end @read_header = false body = @parser.body te = @env[TRANSFER_ENCODING2] if te && CHUNKED.casecmp(te) == 0 return setup_chunked_body(body) end @chunked_body = false cl = @env[CONTENT_LENGTH] unless cl @buffer = body.empty? ? nil : body @body = EmptyBody @requests_served += 1 @ready = true return true end remain = cl.to_i - body.bytesize if remain <= 0 @body = StringIO.new(body) @buffer = nil @requests_served += 1 @ready = true return true end if remain > MAX_BODY @body = Tempfile.new(Const::PUMA_TMP_BASE) @body.binmode @tempfile = @body else # The body[0,0] trick is to get an empty string in the same # encoding as body. @body = StringIO.new body[0,0] end @body.write body @body_remain = remain return false end def try_to_finish return read_body unless @read_header begin data = @io.read_nonblock(CHUNK_SIZE) rescue Errno::EAGAIN return false rescue SystemCallError, IOError raise ConnectionError, "Connection error detected during read" end # No data means a closed socket unless data @buffer = nil @requests_served += 1 @ready = true raise EOFError end if @buffer @buffer << data else @buffer = data end @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes) if @parser.finished? return setup_body elsif @parsed_bytes >= MAX_HEADER raise HttpParserError, "HEADER is longer than allowed, aborting client early." end false end if IS_JRUBY def jruby_start_try_to_finish return read_body unless @read_header begin data = @io.sysread_nonblock(CHUNK_SIZE) rescue OpenSSL::SSL::SSLError => e return false if e.kind_of? IO::WaitReadable raise e end # No data means a closed socket unless data @buffer = nil @requests_served += 1 @ready = true raise EOFError end if @buffer @buffer << data else @buffer = data end @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes) if @parser.finished? return setup_body elsif @parsed_bytes >= MAX_HEADER raise HttpParserError, "HEADER is longer than allowed, aborting client early." end false end def eagerly_finish return true if @ready if @io.kind_of? OpenSSL::SSL::SSLSocket return true if jruby_start_try_to_finish end return false unless IO.select([@to_io], nil, nil, 0) try_to_finish end else def eagerly_finish return true if @ready return false unless IO.select([@to_io], nil, nil, 0) try_to_finish end end # IS_JRUBY def finish return true if @ready until try_to_finish IO.select([@to_io], nil, nil) end true end def read_body if @chunked_body return read_chunked_body end # Read an odd sized chunk so we can read even sized ones # after this remain = @body_remain if remain > CHUNK_SIZE want = CHUNK_SIZE else want = remain end begin chunk = @io.read_nonblock(want) rescue Errno::EAGAIN return false rescue SystemCallError, IOError raise ConnectionError, "Connection error detected during read" end # No chunk means a closed socket unless chunk @body.close @buffer = nil @requests_served += 1 @ready = true raise EOFError end remain -= @body.write(chunk) if remain <= 0 @body.rewind @buffer = nil @requests_served += 1 @ready = true return true end @body_remain = remain false end def write_400 begin @io << ERROR_400_RESPONSE rescue StandardError end end def write_408 begin @io << ERROR_408_RESPONSE rescue StandardError end end def write_500 begin @io << ERROR_500_RESPONSE rescue StandardError end end def peerip return @peerip if @peerip if @remote_addr_header hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first @peerip = hdr return hdr end @peerip ||= @io.peeraddr.last end end end puma-3.12.4/lib/puma/cluster.rb000066400000000000000000000320661362626474300163050ustar00rootroot00000000000000# frozen_string_literal: true require 'puma/runner' require 'puma/util' require 'puma/plugin' require 'time' module Puma # This class is instantiated by the `Puma::Launcher` and used # to boot and serve a Ruby application when puma "workers" are needed # i.e. when using multi-processes. For example `$ puma -w 5` # # At the core of this class is running an instance of `Puma::Server` which # gets created via the `start_server` method from the `Puma::Runner` class # that this inherits from. # # An instance of this class will spawn the number of processes passed in # via the `spawn_workers` method call. Each worker will have it's own # instance of a `Puma::Server`. class Cluster < Runner WORKER_CHECK_INTERVAL = 5 def initialize(cli, events) super cli, events @phase = 0 @workers = [] @next_check = nil @phased_state = :idle @phased_restart = false end def stop_workers log "- Gracefully shutting down workers..." @workers.each { |x| x.term } begin @workers.each { |w| Process.waitpid(w.pid) } rescue Interrupt log "! Cancelled waiting for workers" end end def start_phased_restart @phase += 1 log "- Starting phased worker restart, phase: #{@phase}" # Be sure to change the directory again before loading # the app. This way we can pick up new code. dir = @launcher.restart_dir log "+ Changing to #{dir}" Dir.chdir dir end def redirect_io super @workers.each { |x| x.hup } end class Worker def initialize(idx, pid, phase, options) @index = idx @pid = pid @phase = phase @stage = :started @signal = "TERM" @options = options @first_term_sent = nil @last_checkin = Time.now @last_status = '{}' @dead = false end attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status def booted? @stage == :booted end def boot! @last_checkin = Time.now @stage = :booted end def dead? @dead end def dead! @dead = true end def ping!(status) @last_checkin = Time.now @last_status = status end def ping_timeout?(which) Time.now - @last_checkin > which end def term begin if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout] @signal = "KILL" else @first_term_sent ||= Time.now end Process.kill @signal, @pid rescue Errno::ESRCH end end def kill Process.kill "KILL", @pid rescue Errno::ESRCH end def hup Process.kill "HUP", @pid rescue Errno::ESRCH end end def spawn_workers diff = @options[:workers] - @workers.size return if diff < 1 master = Process.pid diff.times do idx = next_worker_index @launcher.config.run_hooks :before_worker_fork, idx pid = fork { worker(idx, master) } if !pid log "! Complete inability to spawn new workers detected" log "! Seppuku is the only choice." exit! 1 end debug "Spawned worker: #{pid}" @workers << Worker.new(idx, pid, @phase, @options) @launcher.config.run_hooks :after_worker_fork, idx end if diff > 0 @phased_state = :idle end end def cull_workers diff = @workers.size - @options[:workers] return if diff < 1 debug "Culling #{diff.inspect} workers" workers_to_cull = @workers[-diff,diff] debug "Workers to cull: #{workers_to_cull.inspect}" workers_to_cull.each do |worker| log "- Worker #{worker.index} (pid: #{worker.pid}) terminating" worker.term end end def next_worker_index all_positions = 0...@options[:workers] occupied_positions = @workers.map { |w| w.index } available_positions = all_positions.to_a - occupied_positions available_positions.first end def all_workers_booted? @workers.count { |w| !w.booted? } == 0 end def check_workers(force=false) return if !force && @next_check && @next_check >= Time.now @next_check = Time.now + WORKER_CHECK_INTERVAL any = false @workers.each do |w| next if !w.booted? && !w.ping_timeout?(@options[:worker_boot_timeout]) if w.ping_timeout?(@options[:worker_timeout]) log "! Terminating timed out worker: #{w.pid}" w.kill any = true end end # If we killed any timed out workers, try to catch them # during this loop by giving the kernel time to kill them. sleep 1 if any while @workers.any? pid = Process.waitpid(-1, Process::WNOHANG) break unless pid @workers.delete_if { |w| w.pid == pid } end @workers.delete_if(&:dead?) cull_workers spawn_workers if all_workers_booted? # If we're running at proper capacity, check to see if # we need to phase any workers out (which will restart # in the right phase). # w = @workers.find { |x| x.phase != @phase } if w if @phased_state == :idle @phased_state = :waiting log "- Stopping #{w.pid} for phased upgrade..." end w.term log "- #{w.signal} sent to #{w.pid}..." end end end def wakeup! return unless @wakeup begin @wakeup.write "!" unless @wakeup.closed? rescue SystemCallError, IOError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue end end def worker(index, master) title = "puma: cluster worker #{index}: #{master}" title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty? $0 = title Signal.trap "SIGINT", "IGNORE" @workers = [] @master_read.close @suicide_pipe.close Thread.new do IO.select [@check_pipe] log "! Detected parent died, dying" exit! 1 end # If we're not running under a Bundler context, then # report the info about the context we will be using if !ENV['BUNDLE_GEMFILE'] if File.exist?("Gemfile") log "+ Gemfile in context: #{File.expand_path("Gemfile")}" elsif File.exist?("gems.rb") log "+ Gemfile in context: #{File.expand_path("gems.rb")}" end end # Invoke any worker boot hooks so they can get # things in shape before booting the app. @launcher.config.run_hooks :before_worker_boot, index server = start_server Signal.trap "SIGTERM" do server.stop end begin @worker_write << "b#{Process.pid}\n" rescue SystemCallError, IOError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue STDERR.puts "Master seems to have exited, exiting." return end Thread.new(@worker_write) do |io| base_payload = "p#{Process.pid}" while true sleep WORKER_CHECK_INTERVAL begin b = server.backlog || 0 r = server.running || 0 t = server.pool_capacity || 0 m = server.max_threads || 0 payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n! io << payload rescue IOError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue break end end end server.run.join # 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 ensure @worker_write << "t#{Process.pid}\n" rescue nil @worker_write.close end def restart @restart = true stop end def phased_restart return false if @options[:preload_app] @phased_restart = true wakeup! true end def stop @status = :stop wakeup! end def stop_blocked @status = :stop if @status == :run wakeup! @control.stop(true) if @control Process.waitall end def halt @status = :halt wakeup! end def reload_worker_directory dir = @launcher.restart_dir log "+ Changing to #{dir}" Dir.chdir dir end def stats old_worker_count = @workers.count { |w| w.phase != @phase } booted_worker_count = @workers.count { |w| w.booted? } worker_status = '[' + @workers.map { |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']' %Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }! end def preload? @options[:preload_app] end # We do this in a separate method to keep the lambda scope # of the signals handlers as small as possible. def setup_signals Signal.trap "SIGCHLD" do wakeup! end Signal.trap "TTIN" do @options[:workers] += 1 wakeup! end Signal.trap "TTOU" do @options[:workers] -= 1 if @options[:workers] >= 2 wakeup! end master_pid = Process.pid Signal.trap "SIGTERM" do # The worker installs their own SIGTERM when booted. # Until then, this is run by the worker and the worker # should just exit if they get it. if Process.pid != master_pid log "Early termination of worker" exit! 0 else stop_workers stop raise SignalException, "SIGTERM" end end end def run @status = :run output_header "cluster" log "* Process workers: #{@options[:workers]}" before = Thread.list if preload? log "* Preloading application" load_and_bind after = Thread.list if after.size > before.size threads = (after - before) if threads.first.respond_to? :backtrace log "! WARNING: Detected #{after.size-before.size} Thread(s) started in app boot:" threads.each do |t| log "! #{t.inspect} - #{t.backtrace ? t.backtrace.first : ''}" end else log "! WARNING: Detected #{after.size-before.size} Thread(s) started in app boot" end end else log "* Phased restart available" unless @launcher.config.app_configured? error "No application configured, nothing to run" exit 1 end @launcher.binder.parse @options[:binds], self end read, @wakeup = Puma::Util.pipe setup_signals # Used by the workers to detect if the master process dies. # If select says that @check_pipe is ready, it's because the # master has exited and @suicide_pipe has been automatically # closed. # @check_pipe, @suicide_pipe = Puma::Util.pipe if daemon? log "* Daemonizing..." Process.daemon(true) else log "Use Ctrl-C to stop" end redirect_io Plugins.fire_background @launcher.write_state start_control @master_read, @worker_write = read, @wakeup @launcher.config.run_hooks :before_fork, nil spawn_workers Signal.trap "SIGINT" do stop end @launcher.events.fire_on_booted! begin force_check = false while @status == :run begin if @phased_restart start_phased_restart @phased_restart = false end check_workers force_check force_check = false res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL) if res req = read.read_nonblock(1) next if !req || req == "!" result = read.gets pid = result.to_i if w = @workers.find { |x| x.pid == pid } case req when "b" w.boot! log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}" force_check = true when "t" w.dead! force_check = true when "p" w.ping!(result.sub(/^\d+/,'').chomp) end else log "! Out-of-sync worker list, no #{pid} worker" end end rescue Interrupt @status = :stop end end stop_workers unless @status == :halt ensure @check_pipe.close @suicide_pipe.close read.close @wakeup.close end end end end puma-3.12.4/lib/puma/commonlogger.rb000066400000000000000000000063131362626474300173100ustar00rootroot00000000000000# frozen_string_literal: true module Puma # Rack::CommonLogger forwards every request to the given +app+, and # logs a line in the # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common] # to the +logger+. # # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is # an instance of Rack::NullLogger. # # +logger+ can be any class, including the standard library Logger, and is # expected to have either +write+ or +<<+ method, which accepts the CommonLogger::FORMAT. # According to the SPEC, the error stream must also respond to +puts+ # (which takes a single argument that responds to +to_s+), and +flush+ # (which is called without arguments in order to make the error appear for # sure) class CommonLogger # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common # # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 - # # %{%s - %s [%s] "%s %s%s %s" %d %s\n} % FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n} CONTENT_LENGTH = 'Content-Length'.freeze PATH_INFO = 'PATH_INFO'.freeze QUERY_STRING = 'QUERY_STRING'.freeze REQUEST_METHOD = 'REQUEST_METHOD'.freeze def initialize(app, logger=nil) @app = app @logger = logger end def call(env) began_at = Time.now status, header, body = @app.call(env) header = Util::HeaderHash.new(header) # If we've been hijacked, then output a special line if env['rack.hijack_io'] log_hijacking(env, 'HIJACK', header, began_at) else ary = env['rack.after_reply'] ary << lambda { log(env, status, header, began_at) } end [status, header, body] end private def log_hijacking(env, status, header, began_at) now = Time.now msg = HIJACK_FORMAT % [ env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-", env["REMOTE_USER"] || "-", now.strftime("%d/%b/%Y %H:%M:%S"), env[REQUEST_METHOD], env[PATH_INFO], env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}", env["HTTP_VERSION"], now - began_at ] write(msg) end def log(env, status, header, began_at) now = Time.now length = extract_content_length(header) msg = FORMAT % [ env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-", env["REMOTE_USER"] || "-", now.strftime("%d/%b/%Y:%H:%M:%S %z"), env[REQUEST_METHOD], env[PATH_INFO], env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}", env["HTTP_VERSION"], status.to_s[0..3], length, now - began_at ] write(msg) end def write(msg) logger = @logger || env['rack.errors'] # Standard library logger doesn't support write but it supports << which actually # calls to write on the log device without formatting if logger.respond_to?(:write) logger.write(msg) else logger << msg end end def extract_content_length(headers) value = headers[CONTENT_LENGTH] or return '-' value.to_s == '0' ? '-' : value end end end puma-3.12.4/lib/puma/compat.rb000066400000000000000000000005061362626474300161010ustar00rootroot00000000000000# Provides code to work properly on 1.8 and 1.9 class String unless method_defined? :bytesize alias_method :bytesize, :size end unless method_defined? :byteslice def byteslice(*arg) enc = self.encoding self.dup.force_encoding(Encoding::ASCII_8BIT).slice(*arg).force_encoding(enc) end end end puma-3.12.4/lib/puma/configuration.rb000066400000000000000000000227711362626474300174750ustar00rootroot00000000000000# frozen_string_literal: true require 'puma/rack/builder' require 'puma/plugin' require 'puma/const' module Puma module ConfigDefault DefaultRackup = "config.ru" DefaultTCPHost = "0.0.0.0" DefaultTCPPort = 9292 DefaultWorkerTimeout = 60 DefaultWorkerShutdownTimeout = 30 end # A class used for storing "leveled" configuration options. # # In this class any "user" specified options take precedence over any # "file" specified options, take precedence over any "default" options. # # User input is prefered over "defaults": # user_options = { foo: "bar" } # default_options = { foo: "zoo" } # options = UserFileDefaultOptions.new(user_options, default_options) # puts options[:foo] # # => "bar" # # All values can be accessed via `all_of` # # puts options.all_of(:foo) # # => ["bar", "zoo"] # # A "file" option can be set. This config will be prefered over "default" options # but will defer to any available "user" specified options. # # user_options = { foo: "bar" } # default_options = { rackup: "zoo.rb" } # options = UserFileDefaultOptions.new(user_options, default_options) # options.file_options[:rackup] = "sup.rb" # puts options[:rackup] # # => "sup.rb" # # The "default" options can be set via procs. These are resolved during runtime # via calls to `finalize_values` class UserFileDefaultOptions def initialize(user_options, default_options) @user_options = user_options @file_options = {} @default_options = default_options end attr_reader :user_options, :file_options, :default_options def [](key) return user_options[key] if user_options.key?(key) return file_options[key] if file_options.key?(key) return default_options[key] if default_options.key?(key) end def []=(key, value) user_options[key] = value end def fetch(key, default_value = nil) self[key] || default_value end def all_of(key) user = user_options[key] file = file_options[key] default = default_options[key] user = [user] unless user.is_a?(Array) file = [file] unless file.is_a?(Array) default = [default] unless default.is_a?(Array) user.compact! file.compact! default.compact! user + file + default end def finalize_values @default_options.each do |k,v| if v.respond_to? :call @default_options[k] = v.call end end end end # The main configuration class of Puma. # # It can be initialized with a set of "user" options and "default" options. # Defaults will be merged with `Configuration.puma_default_options`. # # This class works together with 2 main other classes the `UserFileDefaultOptions` # which stores configuration options in order so the precedence is that user # set configuration wins over "file" based configuration wins over "default" # configuration. These configurations are set via the `DSL` class. This # class powers the Puma config file syntax and does double duty as a configuration # DSL used by the `Puma::CLI` and Puma rack handler. # # It also handles loading plugins. # # > Note: `:port` and `:host` are not valid keys. By they time they make it to the # configuration options they are expected to be incorporated into a `:binds` key. # Under the hood the DSL maps `port` and `host` calls to `:binds` # # config = Configuration.new({}) do |user_config, file_config, default_config| # user_config.port 3003 # end # config.load # puts config.options[:port] # # => 3003 # # It is expected that `load` is called on the configuration instance after setting # config. This method expands any values in `config_file` and puts them into the # correct configuration option hash. # # Once all configuration is complete it is expected that `clamp` will be called # on the instance. This will expand any procs stored under "default" values. This # is done because an environment variable may have been modified while loading # configuration files. class Configuration include ConfigDefault def initialize(user_options={}, default_options = {}, &block) default_options = self.puma_default_options.merge(default_options) @options = UserFileDefaultOptions.new(user_options, default_options) @plugins = PluginLoader.new @user_dsl = DSL.new(@options.user_options, self) @file_dsl = DSL.new(@options.file_options, self) @default_dsl = DSL.new(@options.default_options, self) if block configure(&block) end end attr_reader :options, :plugins def configure yield @user_dsl, @file_dsl, @default_dsl ensure @user_dsl._offer_plugins @file_dsl._offer_plugins @default_dsl._offer_plugins end def initialize_copy(other) @conf = nil @cli_options = nil @options = @options.dup end def flatten dup.flatten! end def flatten! @options = @options.flatten self end def puma_default_options { :min_threads => 0, :max_threads => 16, :log_requests => false, :debug => false, :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"], :workers => 0, :daemon => false, :mode => :http, :worker_timeout => DefaultWorkerTimeout, :worker_boot_timeout => DefaultWorkerTimeout, :worker_shutdown_timeout => DefaultWorkerShutdownTimeout, :remote_address => :socket, :tag => method(:infer_tag), :environment => -> { ENV['RACK_ENV'] || "development" }, :rackup => DefaultRackup, :logger => STDOUT, :persistent_timeout => Const::PERSISTENT_TIMEOUT, :first_data_timeout => Const::FIRST_DATA_TIMEOUT } end def load config_files.each { |config_file| @file_dsl._load_from(config_file) } @options end def config_files files = @options.all_of(:config_files) return [] if files == ['-'] return files if files.any? first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f| File.exist?(f) end [first_default_file] end # Call once all configuration (included from rackup files) # is loaded to flesh out any defaults def clamp @options.finalize_values end # Injects the Configuration object into the env class ConfigMiddleware def initialize(config, app) @config = config @app = app end def call(env) env[Const::PUMA_CONFIG] = @config @app.call(env) end end # Indicate if there is a properly configured app # def app_configured? @options[:app] || File.exist?(rackup) end def rackup @options[:rackup] end # Load the specified rackup file, pull options from # the rackup file, and set @app. # 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] found = CommonLogger.new(found, logger) end ConfigMiddleware.new(self, found) end # Return which environment we're running in def environment @options[:environment] end def environment_str environment.respond_to?(:call) ? environment.call : environment end def load_plugin(name) @plugins.create name end def run_hooks(key, arg) @options.all_of(key).each { |b| b.call arg } end def self.temp_path require 'tmpdir' t = (Time.now.to_f * 1000).to_i "#{Dir.tmpdir}/puma-status-#{t}-#{$$}" end private def infer_tag File.basename(Dir.getwd) end # Load and use the normal Rack builder if we can, otherwise # fallback to our minimal version. def rack_builder # Load bundler now if we can so that we can pickup rack from # a Gemfile if ENV.key? 'PUMA_BUNDLER_PRUNED' begin require 'bundler/setup' rescue LoadError end end begin require 'rack' require 'rack/builder' rescue LoadError # ok, use builtin version return Puma::Rack::Builder else return ::Rack::Builder end end def load_rackup raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup) rack_app, rack_options = rack_builder.parse_file(rackup) @options.file_options.merge!(rack_options) config_ru_binds = [] rack_options.each do |k, v| config_ru_binds << v if k.to_s.start_with?("bind") end @options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty? rack_app end def self.random_token begin require 'openssl' rescue LoadError end count = 16 bytes = nil if defined? OpenSSL::Random bytes = OpenSSL::Random.random_bytes(count) elsif File.exist?("/dev/urandom") File.open('/dev/urandom') { |f| bytes = f.read(count) } end if bytes token = "".dup bytes.each_byte { |b| token << b.to_s(16) } else token = (0..count).to_a.map { rand(255).to_s(16) }.join end return token end end end require 'puma/dsl' puma-3.12.4/lib/puma/const.rb000066400000000000000000000176301362626474300157520ustar00rootroot00000000000000#encoding: utf-8 # frozen_string_literal: true module Puma class UnsupportedOption < RuntimeError end # Every standard HTTP code mapped to the appropriate message. These are # used so frequently that they are placed directly in Puma for easy # access rather than Puma::Const itself. # Every standard HTTP code mapped to the appropriate message. # Generated with: # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"' HTTP_STATUS_CODES = { 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', 208 => 'Already Reported', 226 => 'IM Used', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Payload Too Large', 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m A Teapot', 421 => 'Misdirected Request', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 510 => 'Not Extended', 511 => 'Network Authentication Required' } # For some HTTP status codes the client only expects headers. # STATUS_WITH_NO_ENTITY_BODY = { 204 => true, 205 => true, 304 => true } # Frequently used constants when constructing requests or responses. Many times # the constant just refers to a string with the same contents. Using these constants # gave about a 3% to 10% performance improvement over using the strings directly. # # The constants are frozen because Hash#[]= when called with a String key dups # the String UNLESS the String is frozen. This saves us therefore 2 object # allocations when creating the env hash later. # # While Puma does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT, # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or # too taxing on performance. module Const PUMA_VERSION = VERSION = "3.12.4".freeze CODE_NAME = "Llamas in Pajamas".freeze PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze FAST_TRACK_KA_TIMEOUT = 0.2 # The default number of seconds for another request within a persistent # session. PERSISTENT_TIMEOUT = 20 # The default number of seconds to wait until we get the first data # for the request FIRST_DATA_TIMEOUT = 30 # How long to wait when getting some write blocking on the socket when # sending data back WRITE_TIMEOUT = 10 # How many requests to attempt inline before sending a client back to # the reactor to be subject to normal ordering. The idea here is that # we amortize the cost of going back to the reactor for a well behaved # but very "greedy" client across 10 requests. This prevents a not # well behaved client from monopolizing the thread forever. MAX_FAST_INLINE = 10 # The original URI requested by the client. REQUEST_URI= 'REQUEST_URI'.freeze REQUEST_PATH = 'REQUEST_PATH'.freeze QUERY_STRING = 'QUERY_STRING'.freeze PATH_INFO = 'PATH_INFO'.freeze PUMA_TMP_BASE = "puma".freeze # Indicate that we couldn't parse the request ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff. ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze # The standard empty 408 response for requests that timed out. ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze CONTENT_LENGTH = "CONTENT_LENGTH".freeze # Indicate that there was an internal error, obviously. ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze # A common header for indicating the server is too busy. Not used yet. ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze # The basic max request size we'll try to read. CHUNK_SIZE = 16 * 1024 # This is the maximum header that is allowed before a client is booted. The parser detects # this, but we'd also like to do this as well. MAX_HEADER = 1024 * (80 + 32) # Maximum request body size before it is moved out of memory and into a tempfile for reading. MAX_BODY = MAX_HEADER REQUEST_METHOD = "REQUEST_METHOD".freeze HEAD = "HEAD".freeze # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32) LINE_END = "\r\n".freeze REMOTE_ADDR = "REMOTE_ADDR".freeze HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze SERVER_NAME = "SERVER_NAME".freeze SERVER_PORT = "SERVER_PORT".freeze HTTP_HOST = "HTTP_HOST".freeze PORT_80 = "80".freeze 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 SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze CGI_VER = "CGI/1.2".freeze STOP_COMMAND = "?".freeze HALT_COMMAND = "!".freeze RESTART_COMMAND = "R".freeze RACK_INPUT = "rack.input".freeze RACK_URL_SCHEME = "rack.url_scheme".freeze RACK_AFTER_REPLY = "rack.after_reply".freeze PUMA_SOCKET = "puma.socket".freeze PUMA_CONFIG = "puma.config".freeze PUMA_PEERCERT = "puma.peercert".freeze HTTP = "http".freeze HTTPS = "https".freeze HTTPS_KEY = "HTTPS".freeze HTTP_VERSION = "HTTP_VERSION".freeze HTTP_CONNECTION = "HTTP_CONNECTION".freeze HTTP_EXPECT = "HTTP_EXPECT".freeze CONTINUE = "100-continue".freeze HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze CLOSE = "close".freeze KEEP_ALIVE = "keep-alive".freeze CONTENT_LENGTH2 = "content-length".freeze CONTENT_LENGTH_S = "Content-Length: ".freeze TRANSFER_ENCODING = "transfer-encoding".freeze TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze CONNECTION_CLOSE = "Connection: close\r\n".freeze CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze CLOSE_CHUNKED = "0\r\n\r\n".freeze CHUNKED = "chunked".freeze COLON = ": ".freeze NEWLINE = "\n".freeze HTTP_INJECTION_REGEX = /[\r\n]/.freeze HIJACK_P = "rack.hijack?".freeze HIJACK = "rack.hijack".freeze HIJACK_IO = "rack.hijack_io".freeze EARLY_HINTS = "rack.early_hints".freeze end end puma-3.12.4/lib/puma/control_cli.rb000066400000000000000000000146761362626474300171420ustar00rootroot00000000000000# frozen_string_literal: true require 'optparse' require_relative 'state_file' require_relative 'const' require_relative 'detect' require_relative 'configuration' require 'uri' require 'socket' module Puma class ControlCLI COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats} def initialize(argv, stdout=STDOUT, stderr=STDERR) @state = nil @quiet = false @pidfile = nil @pid = nil @control_url = nil @control_auth_token = nil @config_file = nil @command = nil @argv = argv.dup @stdout = stdout @stderr = stderr @cli_options = {} opts = OptionParser.new do |o| o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})" o.on "-S", "--state PATH", "Where the state file to use is" do |arg| @state = arg end o.on "-Q", "--quiet", "Not display messages" do |arg| @quiet = true end o.on "-P", "--pidfile PATH", "Pid file" do |arg| @pidfile = arg end o.on "-p", "--pid PID", "Pid" do |arg| @pid = arg.to_i end o.on "-C", "--control-url URL", "The bind url to use for the control server" do |arg| @control_url = arg end o.on "-T", "--control-token TOKEN", "The token to use as authentication for the control server" do |arg| @control_auth_token = arg end o.on "-F", "--config-file PATH", "Puma config script" do |arg| @config_file = arg end o.on_tail("-H", "--help", "Show this message") do @stdout.puts o exit end o.on_tail("-V", "--version", "Show version") do puts Const::PUMA_VERSION exit end end opts.order!(argv) { |a| opts.terminate a } opts.parse! @command = argv.shift unless @config_file == '-' if @config_file.nil? and File.exist?('config/puma.rb') @config_file = 'config/puma.rb' end if @config_file config = Puma::Configuration.new({ config_files: [@config_file] }, {}) config.load @state ||= config.options[:state] @control_url ||= config.options[:control_url] @control_auth_token ||= config.options[:control_auth_token] @pidfile ||= config.options[:pidfile] end end # check present of command unless @command raise "Available commands: #{COMMANDS.join(", ")}" end unless COMMANDS.include? @command raise "Invalid command: #{@command}" end rescue => e @stdout.puts e.message @stdout.puts e.backtrace exit 1 end def message(msg) @stdout.puts msg unless @quiet end def prepare_configuration if @state unless File.exist? @state raise "State file not found: #{@state}" end sf = Puma::StateFile.new sf.load @state @control_url = sf.control_url @control_auth_token = sf.control_auth_token @pid = sf.pid elsif @pidfile # get pid from pid_file @pid = File.open(@pidfile).gets.to_i end end def send_request uri = URI.parse @control_url # create server object by scheme server = case uri.scheme when "tcp" TCPSocket.new uri.host, uri.port when "unix" UNIXSocket.new "#{uri.host}#{uri.path}" else raise "Invalid scheme: #{uri.scheme}" end if @command == "status" message "Puma is started" else url = "/#{@command}" if @control_auth_token url = url + "?token=#{@control_auth_token}" end server << "GET #{url} HTTP/1.0\r\n\r\n" unless data = server.read raise "Server closed connection before responding" end response = data.split("\r\n") if response.empty? raise "Server sent empty response" end (@http,@code,@message) = response.first.split(" ",3) if @code == "403" raise "Unauthorized access to server (wrong auth token)" elsif @code == "404" raise "Command error: #{response.last}" elsif @code != "200" raise "Bad response from server: #{@code}" end message "Command #{@command} sent success" message response.last if @command == "stats" || @command == "gc-stats" end ensure server.close if server && !server.closed? end def send_signal unless @pid raise "Neither pid nor control url available" end begin case @command when "restart" Process.kill "SIGUSR2", @pid when "halt" Process.kill "QUIT", @pid when "stop" Process.kill "SIGTERM", @pid when "stats" puts "Stats not available via pid only" return when "reload-worker-directory" puts "reload-worker-directory not available via pid only" return when "phased-restart" Process.kill "SIGUSR1", @pid else return end rescue SystemCallError if @command == "restart" start else raise "No pid '#{@pid}' found" end end message "Command #{@command} sent success" end def run return start if @command == "start" prepare_configuration if Puma.windows? send_request else @control_url ? send_request : send_signal end rescue => e message e.message message e.backtrace exit 1 end private def start require 'puma/cli' run_args = [] run_args += ["-S", @state] if @state run_args += ["-q"] if @quiet run_args += ["--pidfile", @pidfile] if @pidfile run_args += ["--control-url", @control_url] if @control_url run_args += ["--control-token", @control_auth_token] if @control_auth_token run_args += ["-C", @config_file] if @config_file events = Puma::Events.new @stdout, @stderr # replace $0 because puma use it to generate restart command puma_cmd = $0.gsub(/pumactl$/, 'puma') $0 = puma_cmd if File.exist?(puma_cmd) cli = Puma::CLI.new run_args, events cli.run end end end puma-3.12.4/lib/puma/convenient.rb000066400000000000000000000006441362626474300167710ustar00rootroot00000000000000# frozen_string_literal: true require 'puma/launcher' require 'puma/configuration' module Puma def self.run(opts={}) cfg = Puma::Configuration.new do |user_config| if port = opts[:port] user_config.port port end user_config.quiet yield c end cfg.clamp events = Puma::Events.null launcher = Puma::Launcher.new cfg, :events => events launcher.run end end puma-3.12.4/lib/puma/daemon_ext.rb000066400000000000000000000013251362626474300167410ustar00rootroot00000000000000# frozen_string_literal: true module Process # This overrides the default version because it is broken if it # exists. if respond_to? :daemon class << self remove_method :daemon end end def self.daemon(nochdir=false, noclose=false) exit if fork # Parent exits, child continues. Process.setsid # Become session leader. exit if fork # Zap session leader. See [1]. Dir.chdir "/" unless nochdir # Release old working directory. if !noclose STDIN.reopen File.open("/dev/null", "r") null_out = File.open "/dev/null", "w" STDOUT.reopen null_out STDERR.reopen null_out end 0 end end puma-3.12.4/lib/puma/delegation.rb000066400000000000000000000003501362626474300167260ustar00rootroot00000000000000# frozen_string_literal: true module Puma module Delegation def forward(what, who) module_eval <<-CODE def #{what}(*args, &block) #{who}.#{what}(*args, &block) end CODE end end end puma-3.12.4/lib/puma/detect.rb000066400000000000000000000003311362626474300160620ustar00rootroot00000000000000# frozen_string_literal: true module Puma IS_JRUBY = defined?(JRUBY_VERSION) def self.jruby? IS_JRUBY end IS_WINDOWS = RUBY_PLATFORM =~ /mswin|ming|cygwin/ def self.windows? IS_WINDOWS end end puma-3.12.4/lib/puma/dsl.rb000066400000000000000000000370251362626474300154060ustar00rootroot00000000000000# frozen_string_literal: true module Puma # The methods that are available for use inside the config file. # These same methods are used in Puma cli and the rack handler # internally. # # Used manually (via CLI class): # # config = Configuration.new({}) do |user_config| # user_config.port 3001 # end # config.load # # puts config.options[:binds] # "tcp://127.0.0.1:3001" # # Used to load file: # # $ cat puma_config.rb # port 3002 # # config = Configuration.new(config_file: "puma_config.rb") # config.load # # puts config.options[:binds] # # => "tcp://127.0.0.1:3002" # # Detailed docs can be found in `examples/config.rb` class DSL include ConfigDefault def initialize(options, config) @config = config @options = options @plugins = [] end def _load_from(path) if path @path = path instance_eval(File.read(path), path, 1) end ensure _offer_plugins end def _offer_plugins @plugins.each do |o| if o.respond_to? :config @options.shift o.config self end end @plugins.clear end def set_default_host(host) @options[:default_host] = host end def default_host @options[:default_host] || Configuration::DefaultTCPHost end def inject(&blk) instance_eval(&blk) end def get(key,default=nil) @options[key.to_sym] || default end # Load the named plugin for use by this configuration # def plugin(name) @plugins << @config.load_plugin(name) end # Use +obj+ or +block+ as the Rack app. This allows a config file to # be the app itself. # def app(obj=nil, &block) obj ||= block raise "Provide either a #call'able or a block" unless obj @options[:app] = obj end # Start the Puma control rack app on +url+. This app can be communicated # with to control the main server. # def activate_control_app(url="auto", opts={}) if url == "auto" path = Configuration.temp_path @options[:control_url] = "unix://#{path}" @options[:control_url_temp] = path else @options[:control_url] = url end if opts[:no_token] auth_token = :none else auth_token = opts[:auth_token] auth_token ||= Configuration.random_token end @options[:control_auth_token] = auth_token @options[:control_url_umask] = opts[:umask] if opts[:umask] end # Load additional configuration from a file # Files get loaded later via Configuration#load def load(file) @options[:config_files] ||= [] @options[:config_files] << file end # Adds a binding for the server to +url+. tcp://, unix://, and ssl:// are the only accepted # protocols. Use query parameters within the url to specify options. # # @note multiple urls can be bound to, calling `bind` does not overwrite previous bindings. # # @example Explicitly the socket backlog depth (default is 1024) # bind('unix:///var/run/puma.sock?backlog=2048') # # @example Set up ssl cert # bind('ssl://127.0.0.1:9292?key=key.key&cert=cert.pem') # # @example Prefer low-latency over higher throughput (via `Socket::TCP_NODELAY`) # bind('tcp://0.0.0.0:9292?low_latency=true') # # @example Set socket permissions # bind('unix:///var/run/puma.sock?umask=0111') def bind(url) @options[:binds] ||= [] @options[:binds] << url end def clear_binds! @options[:binds] = [] end # Define the TCP port to bind to. Use +bind+ for more advanced options. # def port(port, host=nil) host ||= default_host bind "tcp://#{host}:#{port}" end # Define how long persistent connections can be idle before puma closes # them # def persistent_timeout(seconds) @options[:persistent_timeout] = Integer(seconds) end # Define how long the tcp socket stays open, if no data has been received # def first_data_timeout(seconds) @options[:first_data_timeout] = Integer(seconds) end # Work around leaky apps that leave garbage in Thread locals # across requests # def clean_thread_locals(which=true) @options[:clean_thread_locals] = which end # Daemonize the server into the background. Highly suggest that # this be combined with +pidfile+ and +stdout_redirect+. 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 # looking and waits for the requests to finish. def drain_on_shutdown(which=true) @options[:drain_on_shutdown] = which end # Set the environment in which the Rack's app will run. def environment(environment) @options[:environment] = environment end # How long to wait for threads to stop when shutting them # down. Defaults to :forever. Specifying :immediately will cause # Puma to kill the threads immediately. Otherwise the value # is the number of seconds to wait. # # Puma always waits a few seconds after killing a thread for it to try # to finish up it's work, even in :immediately mode. def force_shutdown_after(val=:forever) i = case val when :forever -1 when :immediately 0 else Integer(val) end @options[:force_shutdown_after] = i end # Code to run before doing a restart. This code should # close logfiles, database connections, etc. # # This can be called multiple times to add code each time. # def on_restart(&block) @options[:on_restart] ||= [] @options[:on_restart] << block end # Command to use to restart puma. This should be just how to # load puma itself (ie. 'ruby -Ilib bin/puma'), not the arguments # to puma, as those are the same as the original process. # def restart_command(cmd) @options[:restart_cmd] = cmd.to_s end # Store the pid of the server in the file at +path+. def pidfile(path) @options[:pidfile] = path.to_s end # Disable request logging. # def quiet(which=true) @options[:log_requests] = !which end # Enable request logging # def log_requests(which=true) @options[:log_requests] = which end # Show debugging info # def debug @options[:debug] = true end # Load +path+ as a rackup file. # def rackup(path) @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 # Redirect STDOUT and STDERR to files specified. def stdout_redirect(stdout=nil, stderr=nil, append=false) @options[:redirect_stdout] = stdout @options[:redirect_stderr] = stderr @options[:redirect_append] = append end # Configure +min+ to be the minimum number of threads to use to answer # requests and +max+ the maximum. # def threads(min, max) min = Integer(min) max = Integer(max) if min > max raise "The minimum (#{min}) number of threads must be less than or equal to the max (#{max})" end if max < 1 raise "The maximum number of threads (#{max}) must be greater than 0" end @options[:min_threads] = min @options[:max_threads] = max end def ssl_bind(host, port, opts) verify = opts.fetch(:verify_mode, 'none') if defined?(JRUBY_VERSION) keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}" bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}" else bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&verify_mode=#{verify}" end end # Use +path+ as the file to store the server info state. This is # used by pumactl to query and control the server. # def state_path(path) @options[:state] = path.to_s end # *Cluster mode only* How many worker processes to run. # def workers(count) @options[:workers] = count.to_i end # *Cluster mode only* Code to run immediately before master process # forks workers (once on boot). These hooks can block if necessary # to wait for background operations unknown to puma to finish before # the process terminates. # This can be used to close any connections to remote servers (database, redis, ...) # that were opened when preloading the code # # This can be called multiple times to add hooks. # def before_fork(&block) @options[:before_fork] ||= [] @options[:before_fork] << block end # *Cluster mode only* Code to run in a worker when it boots to setup # the process before booting the app. # # This can be called multiple times to add hooks. # def on_worker_boot(&block) @options[:before_worker_boot] ||= [] @options[:before_worker_boot] << block end # *Cluster mode only* Code to run immediately before a worker shuts # down (after it has finished processing HTTP requests). These hooks # can block if necessary to wait for background operations unknown # to puma to finish before the process terminates. # # This can be called multiple times to add hooks. # def on_worker_shutdown(&block) @options[:before_worker_shutdown] ||= [] @options[:before_worker_shutdown] << block end # *Cluster mode only* Code to run in the master when it is # about to create the worker by forking itself. # # This can be called multiple times to add hooks. # def on_worker_fork(&block) @options[:before_worker_fork] ||= [] @options[:before_worker_fork] << block end # *Cluster mode only* Code to run in the master after it starts # a worker. # # This can be called multiple times to add hooks. # def after_worker_fork(&block) @options[:after_worker_fork] ||= [] @options[:after_worker_fork] = block end alias_method :after_worker_boot, :after_worker_fork # The directory to operate out of. def directory(dir) @options[:directory] = dir.to_s end # DEPRECATED: The directory to operate out of. def worker_directory(dir) $stderr.puts "worker_directory is deprecated. Please use `directory`" directory dir end # Run the app as a raw TCP app instead of an HTTP rack app def tcp_mode @options[:mode] = :tcp end # *Cluster mode only* Preload the application before starting # the workers and setting up the listen ports. This conflicts # with using the phased restart feature, you can't use both. # def preload_app!(answer=true) @options[:preload_app] = answer end # Use +obj+ or +block+ as the low level error handler. This allows a config file to # change the default error on the server. # def lowlevel_error_handler(obj=nil, &block) obj ||= block raise "Provide either a #call'able or a block" unless obj @options[:lowlevel_error_handler] = obj end # This option is used to allow your app and its gems to be # properly reloaded when not using preload. # # When set, if puma detects that it's been invoked in the # context of Bundler, it will cleanup the environment and # re-run itself outside the Bundler environment, but directly # using the files that Bundler has setup. # # This means that puma is now decoupled from your Bundler # context and when each worker loads, it will be loading a # new Bundler context and thus can float around as the release # dictates. def prune_bundler(answer=true) @options[:prune_bundler] = answer end # Additional text to display in process listing def tag(string) @options[:tag] = string.to_s end # *Cluster mode only* Set the timeout for workers in seconds # When set the master process will terminate any workers # that have not checked in within the given +timeout+. # This mitigates hung processes. Default value is 60 seconds. def worker_timeout(timeout) @options[:worker_timeout] = Integer(timeout) end # *Cluster mode only* Set the timeout for workers to boot def worker_boot_timeout(timeout) @options[:worker_boot_timeout] = Integer(timeout) end # *Cluster mode only* Set the timeout for worker shutdown def worker_shutdown_timeout(timeout) @options[:worker_shutdown_timeout] = Integer(timeout) end # When set to true (the default), workers accept all requests # and queue them before passing them to the handlers. # When set to false, each worker process accepts exactly as # many requests as it is configured to simultaneously handle. # # Queueing requests generally improves performance. In some # cases, such as a single threaded application, it may be # better to ensure requests get balanced across workers. # # Note that setting this to false disables HTTP keepalive and # slow clients will occupy a handler thread while the request # is being sent. A reverse proxy, such as nginx, can handle # slow clients and queue requests before they reach puma. def queue_requests(answer=true) @options[:queue_requests] = answer end # When a shutdown is requested, the backtraces of all the # threads will be written to $stdout. This can help figure # out why shutdown is hanging. def shutdown_debug(val=true) @options[:shutdown_debug] = val end # Control how the remote address of the connection is set. This # is configurable because to calculate the true socket peer address # a kernel syscall is required which for very fast rack handlers # slows down the handling significantly. # # There are 4 possible values: # # * :socket (the default) - read the peername from the socket using the # syscall. This is the normal behavior. # * :localhost - set the remote address to "127.0.0.1" # * header: http_header - set the remote address to the value of the # provided http header. For instance: # `set_remote_address header: "X-Real-IP"`. # Only the first word (as separated by spaces or comma) # is used, allowing headers such as X-Forwarded-For # to be used as well. # * Any string - this allows you to hardcode remote address to any value # you wish. Because puma never uses this field anyway, it's # format is entirely in your hands. def set_remote_address(val=:socket) case val when :socket @options[:remote_address] = val when :localhost @options[:remote_address] = :value @options[:remote_address_value] = "127.0.0.1".freeze when String @options[:remote_address] = :value @options[:remote_address_value] = val when Hash if hdr = val[:header] @options[:remote_address] = :header @options[:remote_address_header] = "HTTP_" + hdr.upcase.tr("-", "_") else raise "Invalid value for set_remote_address - #{val.inspect}" end else raise "Invalid value for set_remote_address - #{val}" end end end end puma-3.12.4/lib/puma/events.rb000066400000000000000000000070201362626474300161200ustar00rootroot00000000000000# frozen_string_literal: true require 'puma/const' require "puma/null_io" require 'stringio' module Puma # The default implement of an event sink object used by Server # for when certain kinds of events occur in the life of the server. # # The methods available are the events that the Server fires. # class Events class DefaultFormatter def call(str) str end end class PidFormatter def call(str) "[#{$$}] #{str}" end end include Const # Create an Events object that prints to +stdout+ and +stderr+. # def initialize(stdout, stderr) @formatter = DefaultFormatter.new @stdout = stdout @stderr = stderr @stdout.sync = true @stderr.sync = true @debug = ENV.key? 'PUMA_DEBUG' @hooks = Hash.new { |h,k| h[k] = [] } end attr_reader :stdout, :stderr attr_accessor :formatter # Fire callbacks for the named hook # def fire(hook, *args) @hooks[hook].each { |t| t.call(*args) } end # Register a callback for a given hook # def register(hook, obj=nil, &blk) if obj and blk raise "Specify either an object or a block, not both" end h = obj || blk @hooks[hook] << h h end # Write +str+ to +@stdout+ # def log(str) @stdout.puts format(str) end def write(str) @stdout.write format(str) end def debug(str) log("% #{str}") if @debug end # Write +str+ to +@stderr+ # def error(str) @stderr.puts format("ERROR: #{str}") exit 1 end def format(str) formatter.call(str) end # An HTTP parse error has occurred. # +server+ is the Server object, +env+ the request, and +error+ a # parsing exception. # def parse_error(server, env, error) @stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}\n---\n" end # An SSL error has occurred. # +server+ is the Server object, +peeraddr+ peer address, +peercert+ # any peer certificate (if present), and +error+ an exception object. # def ssl_error(server, peeraddr, peercert, error) subject = peercert ? peercert.subject : nil @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}" end # An unknown error has occurred. # +server+ is the Server object, +error+ an exception object, # +kind+ some additional info, and +env+ the request. # def unknown_error(server, error, kind="Unknown", env=nil) if error.respond_to? :render error.render "#{Time.now}: #{kind} error", @stderr else if env string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ] string_block << error.inspect else string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ] end string_block << error.backtrace @stderr.puts string_block.join("\n") end end def on_booted(&block) register(:on_booted, &block) end def fire_on_booted! fire(:on_booted) end DEFAULT = new(STDOUT, STDERR) # Returns an Events object which writes its status to 2 StringIO # objects. # def self.strings Events.new StringIO.new, StringIO.new end def self.stdio Events.new $stdout, $stderr end def self.null n = NullIO.new Events.new n, n end end end puma-3.12.4/lib/puma/io_buffer.rb000066400000000000000000000002131362626474300165510ustar00rootroot00000000000000# frozen_string_literal: true require 'puma/detect' if Puma.jruby? require 'puma/java_io_buffer' else require 'puma/puma_http11' end puma-3.12.4/lib/puma/java_io_buffer.rb000066400000000000000000000014551362626474300175630ustar00rootroot00000000000000# frozen_string_literal: true require 'java' # Conservative native JRuby/Java implementation of IOBuffer # backed by a ByteArrayOutputStream and conversion between # Ruby String and Java bytes module Puma class JavaIOBuffer < java.io.ByteArrayOutputStream field_reader :buf end class IOBuffer BUF_DEFAULT_SIZE = 4096 def initialize @buf = JavaIOBuffer.new(BUF_DEFAULT_SIZE) end def reset @buf.reset end def <<(str) bytes = str.to_java_bytes @buf.write(bytes, 0, bytes.length) end def append(*strs) strs.each { |s| self << s; } end def to_s String.from_java_bytes @buf.to_byte_array end alias_method :to_str, :to_s def used @buf.size end def capacity @buf.buf.length end end end puma-3.12.4/lib/puma/jruby_restart.rb000066400000000000000000000034011362626474300175120ustar00rootroot00000000000000# frozen_string_literal: true require 'ffi' module Puma module JRubyRestart extend FFI::Library ffi_lib 'c' attach_function :execlp, [:string, :varargs], :int attach_function :chdir, [:string], :int attach_function :fork, [], :int attach_function :exit, [:int], :void attach_function :setsid, [], :int def self.chdir_exec(dir, argv) chdir(dir) cmd = argv.first argv = ([:string] * argv.size).zip(argv).flatten argv << :string argv << nil 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 puma-3.12.4/lib/puma/launcher.rb000066400000000000000000000262031362626474300164210ustar00rootroot00000000000000# frozen_string_literal: true require 'puma/events' require 'puma/detect' require 'puma/cluster' require 'puma/single' require 'puma/const' require 'puma/binder' module Puma # Puma::Launcher is the single entry point for starting a Puma server based on user # configuration. It is responsible for taking user supplied arguments and resolving them # with configuration in `config/puma.rb` or `config/puma/.rb`. # # It is responsible for either launching a cluster of Puma workers or a single # puma server. class Launcher KEYS_NOT_TO_PERSIST_IN_STATE = [ :logger, :lowlevel_error_handler, :before_worker_shutdown, :before_worker_boot, :before_worker_fork, :after_worker_boot, :before_fork, :on_restart ] # Returns an instance of Launcher # # +conf+ A Puma::Configuration object indicating how to run the server. # # +launcher_args+ A Hash that currently has one required key `:events`, # this is expected to hold an object similar to an `Puma::Events.stdio`, # this object will be responsible for broadcasting Puma's internal state # to a logging destination. An optional key `:argv` can be supplied, # this should be an array of strings, these arguments are re-used when # restarting the puma server. # # Examples: # # conf = Puma::Configuration.new do |user_config| # user_config.threads 1, 10 # user_config.app do |env| # [200, {}, ["hello world"]] # end # end # Puma::Launcher.new(conf, events: Puma::Events.stdio).run def initialize(conf, launcher_args={}) @runner = nil @events = launcher_args[:events] || Events::DEFAULT @argv = launcher_args[:argv] || [] @original_argv = @argv.dup @config = conf @binder = Binder.new(@events) @binder.import_from_env @environment = conf.environment # Advertise the Configuration Puma.cli_config = @config if defined?(Puma.cli_config) @config.load @options = @config.options @config.clamp generate_restart_data if clustered? && !Process.respond_to?(:fork) 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? @environment = @options[:environment] if @options[:environment] set_rack_environment if clustered? @events.formatter = Events::PidFormatter.new @options[:logger] = @events @runner = Cluster.new(self, @events) else @runner = Single.new(self, @events) end Puma.stats_object = @runner @status = :run end attr_reader :binder, :events, :config, :options, :restart_dir # Return stats about the server def stats @runner.stats end # Write a state file that can be used by pumactl to control # the server def write_state write_pid path = @options[:state] return unless path require 'puma/state_file' sf = StateFile.new sf.pid = Process.pid sf.control_url = @options[:control_url] sf.control_auth_token = @options[:control_auth_token] sf.save path end # Delete the configured pidfile def delete_pidfile path = @options[:pidfile] File.unlink(path) if path && File.exist?(path) end # If configured, write the pid of the current process out # to a file. def write_pid path = @options[:pidfile] return unless path File.open(path, 'w') { |f| f.puts Process.pid } cur = Process.pid at_exit do delete_pidfile if cur == Process.pid end end # Begin async shutdown of the server def halt @status = :halt @runner.halt end # Begin async shutdown of the server gracefully def stop @status = :stop @runner.stop end # Begin async restart of the server def restart @status = :restart @runner.restart end # Begin a phased restart if supported def phased_restart unless @runner.respond_to?(:phased_restart) and @runner.phased_restart log "* phased-restart called but not available, restarting normally." return restart end true end # Run the server. This blocks until the server is stopped def run previous_env = if defined?(Bundler) env = Bundler::ORIGINAL_ENV.dup # add -rbundler/setup so we load from Gemfile when restarting bundle = "-rbundler/setup" env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle) env else ENV.to_h end @config.clamp @config.plugins.fire_starts self setup_signals set_process_title @runner.run case @status when :halt log "* Stopping immediately!" when :run, :stop graceful_stop when :restart log "* Restarting..." ENV.replace(previous_env) @runner.before_restart restart! when :exit # nothing end end # Return which tcp port the launcher is using, if it's using TCP def connected_port @binder.connected_port end def restart_args cmd = @options[:restart_cmd] if cmd cmd.split(' ') + @original_argv else @restart_argv end end private def reload_worker_directory @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory) end def restart! @config.run_hooks :on_restart, self if Puma.jruby? close_binder_listeners require 'puma/jruby_restart' JRubyRestart.chdir_exec(@restart_dir, restart_args) elsif Puma.windows? close_binder_listeners argv = restart_args Dir.chdir(@restart_dir) Kernel.exec(*argv) else redirects = {:close_others => true} @binder.listeners.each_with_index do |(l, io), i| ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}" redirects[io.to_i] = io.to_i end argv = restart_args Dir.chdir(@restart_dir) argv += [redirects] if RUBY_VERSION >= '1.9' Kernel.exec(*argv) end end def prune_bundler return unless defined?(Bundler) puma = Bundler.rubygems.loaded_specs("puma") dirs = puma.require_paths.map { |x| File.join(puma.full_gem_path, x) } puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') } unless puma_lib_dir log "! Unable to prune Bundler environment, continuing" return end deps = puma.runtime_dependencies.map do |d| spec = Bundler.rubygems.loaded_specs(d.name) "#{d.name}:#{spec.version.to_s}" end log '* Pruning Bundler environment' home = ENV['GEM_HOME'] Bundler.with_clean_env do ENV['GEM_HOME'] = home ENV['PUMA_BUNDLER_PRUNED'] = '1' wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild")) args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv # Ruby 2.0+ defaults to true which breaks socket activation args += [{:close_others => false}] if RUBY_VERSION >= '2.0' Kernel.exec(*args) end end def log(str) @events.log str end def clustered? (@options[:workers] || 0) > 0 end def unsupported(str) @events.error(str) raise UnsupportedOption end def graceful_stop @runner.stop_blocked log "=== puma shutdown: #{Time.now} ===" log "- Goodbye!" end def set_process_title Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title end def title buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})" buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty? buffer end def set_rack_environment @options[:environment] = environment ENV['RACK_ENV'] = environment end def environment @environment end def prune_bundler? @options[:prune_bundler] && clustered? && !@options[:preload_app] end def close_binder_listeners @binder.listeners.each do |l, io| io.close uri = URI.parse(l) next unless uri.scheme == 'unix' File.unlink("#{uri.host}#{uri.path}") end end def generate_restart_data if dir = @options[:directory] @restart_dir = dir elsif Puma.windows? # I guess the value of PWD is garbage on windows so don't bother # using it. @restart_dir = Dir.pwd # Use the same trick as unicorn, namely favor PWD because # it will contain an unresolved symlink, useful for when # the pwd is /data/releases/current. elsif dir = ENV['PWD'] s_env = File.stat(dir) s_pwd = File.stat(Dir.pwd) if s_env.ino == s_pwd.ino and (Puma.jruby? or s_env.dev == s_pwd.dev) @restart_dir = dir end end @restart_dir ||= Dir.pwd # if $0 is a file in the current directory, then restart # it the same, otherwise add -S on there because it was # picked up in PATH. # if File.exist?($0) arg0 = [Gem.ruby, $0] else arg0 = [Gem.ruby, "-S", $0] end # Detect and reinject -Ilib from the command line, used for testing without bundler # cruby has an expanded path, jruby has just "lib" lib = File.expand_path "lib" arg0[1,0] = ["-I", lib] if [lib, "lib"].include?($LOAD_PATH[0]) if defined? Puma::WILD_ARGS @restart_argv = arg0 + Puma::WILD_ARGS + @original_argv else @restart_argv = arg0 + @original_argv end end def setup_signals begin Signal.trap "SIGUSR2" do restart end rescue Exception log "*** SIGUSR2 not implemented, signal based restart unavailable!" end unless Puma.jruby? begin Signal.trap "SIGUSR1" do phased_restart end rescue Exception log "*** SIGUSR1 not implemented, signal based restart unavailable!" end end begin Signal.trap "SIGTERM" do graceful_stop raise SignalException, "SIGTERM" end rescue Exception log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!" end begin Signal.trap "SIGINT" do if Puma.jruby? @status = :exit graceful_stop exit end stop end rescue Exception log "*** SIGINT not implemented, signal based gracefully stopping unavailable!" end begin Signal.trap "SIGHUP" do if @runner.redirected_io? @runner.redirect_io else stop end end rescue Exception log "*** SIGHUP not implemented, signal based logs reopening unavailable!" end end end end puma-3.12.4/lib/puma/minissl.rb000066400000000000000000000142721362626474300163010ustar00rootroot00000000000000# frozen_string_literal: true begin require 'io/wait' rescue LoadError end module Puma module MiniSSL class Socket def initialize(socket, engine) @socket = socket @engine = engine @peercert = nil end def to_io @socket end def closed? @socket.closed? end def readpartial(size) while true output = @engine.read return output if output data = @socket.readpartial(size) @engine.inject(data) output = @engine.read return output if output while neg_data = @engine.extract @socket.write neg_data end end end def engine_read_all output = @engine.read while output and additional_output = @engine.read output << additional_output end output end def read_nonblock(size, *_) # *_ is to deal with keyword args that were added # at some point (and being used in the wild) while true output = engine_read_all return output if output begin data = @socket.read_nonblock(size, exception: false) if data == :wait_readable || data == :wait_writable if @socket.to_io.respond_to?(data) @socket.to_io.__send__(data) elsif data == :wait_readable IO.select([@socket.to_io]) else IO.select(nil, [@socket.to_io]) end elsif !data return nil else break end end while true @engine.inject(data) output = engine_read_all return output if output while neg_data = @engine.extract @socket.write neg_data end end end def write(data) return 0 if data.empty? need = data.bytesize while true wrote = @engine.write data enc = @engine.extract while enc @socket.write enc enc = @engine.extract end need -= wrote return data.bytesize if need == 0 data = data[wrote..-1] end end alias_method :syswrite, :write alias_method :<<, :write # This is a temporary fix to deal with websockets code using # write_nonblock. The problem with implementing it properly # is that it means we'd have to have the ability to rewind # an engine because after we write+extract, the socket # write_nonblock call might raise an exception and later # code would pass the same data in, but the engine would think # it had already written the data in. So for the time being # (and since write blocking is quite rare), go ahead and actually # block in write_nonblock. def write_nonblock(data, *_) write data end def flush @socket.flush end 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 end def should_drop_bytes? @engine.init? || !@engine.shutdown end def close begin # 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 rescue IOError, SystemCallError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue # nothing ensure @socket.close end end def peeraddr @socket.peeraddr end def peercert return @peercert if @peercert raw = @engine.peercert return nil unless raw @peercert = OpenSSL::X509::Certificate.new raw end end if defined?(JRUBY_VERSION) class SSLError < StandardError # Define this for jruby even though it isn't used. end def self.check; end end class Context attr_accessor :verify_mode if defined?(JRUBY_VERSION) # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair attr_reader :keystore attr_accessor :keystore_pass attr_accessor :ssl_cipher_list def keystore=(keystore) raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore @keystore = keystore end def check raise "Keystore not configured" unless @keystore end else # non-jruby Context properties attr_reader :key attr_reader :cert attr_reader :ca attr_accessor :ssl_cipher_filter def key=(key) raise ArgumentError, "No such key file '#{key}'" unless File.exist? key @key = key end def cert=(cert) raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert @cert = cert end def ca=(ca) raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca @ca = ca end def check raise "Key not configured" unless @key raise "Cert not configured" unless @cert end end end VERIFY_NONE = 0 VERIFY_PEER = 1 VERIFY_FAIL_IF_NO_PEER_CERT = 2 class Server def initialize(socket, ctx) @socket = socket @ctx = ctx end def to_io @socket end def accept @ctx.check io = @socket.accept engine = Engine.server @ctx Socket.new io, engine end def accept_nonblock @ctx.check io = @socket.accept_nonblock engine = Engine.server @ctx Socket.new io, engine end def close @socket.close unless @socket.closed? # closed? call is for Windows end end end end puma-3.12.4/lib/puma/null_io.rb000066400000000000000000000011111362626474300162500ustar00rootroot00000000000000# frozen_string_literal: true module Puma # Provides an IO-like object that always appears to contain no data. # Used as the value for rack.input when the request has no body. # class NullIO def gets nil end def each end # Mimics IO#read with no data. # def read(count = nil, _buffer = nil) (count && count > 0) ? nil : "" end def rewind end def close end def size 0 end def eof? true end def sync=(v) end def puts(*ary) end def write(*ary) end end end puma-3.12.4/lib/puma/plugin.rb000066400000000000000000000044361362626474300161220ustar00rootroot00000000000000# frozen_string_literal: true module Puma class UnknownPlugin < RuntimeError; end class PluginLoader def initialize @instances = [] end def create(name) if cls = Plugins.find(name) plugin = cls.new(Plugin) @instances << plugin return plugin end raise UnknownPlugin, "File failed to register properly named plugin" end def fire_starts(launcher) @instances.each do |i| if i.respond_to? :start i.start(launcher) end end end end class PluginRegistry def initialize @plugins = {} @background = [] end def register(name, cls) @plugins[name] = cls end def find(name) name = name.to_s if cls = @plugins[name] return cls end begin require "puma/plugin/#{name}" rescue LoadError raise UnknownPlugin, "Unable to find plugin: #{name}" end if cls = @plugins[name] return cls end raise UnknownPlugin, "file failed to register a plugin" end def add_background(blk) @background << blk end def fire_background @background.each do |b| Thread.new(&b) end end end Plugins = PluginRegistry.new class Plugin # Matches # "C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb:3:in `'" # AS # C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb CALLER_FILE = / \A # start of string .+ # file path (one or more characters) (?= # stop previous match when :\d+ # a colon is followed by one or more digits :in # followed by a colon followed by in ) /x def self.extract_name(ary) path = ary.first[CALLER_FILE] m = %r!puma/plugin/([^/]*)\.rb$!.match(path) return m[1] end def self.create(&blk) name = extract_name(caller) cls = Class.new(self) cls.class_eval(&blk) Plugins.register name, cls end def initialize(loader) @loader = loader end def in_background(&blk) Plugins.add_background blk end def workers_supported? return false if Puma.jruby? || Puma.windows? true end end end puma-3.12.4/lib/puma/plugin/000077500000000000000000000000001362626474300155665ustar00rootroot00000000000000puma-3.12.4/lib/puma/plugin/tmp_restart.rb000066400000000000000000000013001362626474300204510ustar00rootroot00000000000000require 'puma/plugin' Puma::Plugin.create do def start(launcher) path = File.join("tmp", "restart.txt") orig = nil # If we can't write to the path, then just don't bother with this plugin begin File.write(path, "") unless File.exist?(path) orig = File.stat(path).mtime rescue SystemCallError return end in_background do while true sleep 2 begin mtime = File.stat(path).mtime rescue SystemCallError # If the file has disappeared, assume that means don't restart else if mtime > orig launcher.restart break end end end end end end puma-3.12.4/lib/puma/rack/000077500000000000000000000000001362626474300152105ustar00rootroot00000000000000puma-3.12.4/lib/puma/rack/backports/000077500000000000000000000000001362626474300172005ustar00rootroot00000000000000puma-3.12.4/lib/puma/rack/backports/uri/000077500000000000000000000000001362626474300177775ustar00rootroot00000000000000puma-3.12.4/lib/puma/rack/backports/uri/common_193.rb000066400000000000000000000012461362626474300222130ustar00rootroot00000000000000# :stopdoc: require 'uri/common' # Issue: # http://bugs.ruby-lang.org/issues/5925 # # Relevant commit: # https://github.com/ruby/ruby/commit/edb7cdf1eabaff78dfa5ffedfbc2e91b29fa9ca1 module URI begin 256.times do |i| TBLENCWWWCOMP_[i.chr] = '%%%02X' % i end TBLENCWWWCOMP_[' '] = '+' TBLENCWWWCOMP_.freeze 256.times do |i| h, l = i>>4, i&15 TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr end TBLDECWWWCOMP_['+'] = ' ' TBLDECWWWCOMP_.freeze rescue Exception end end # :startdoc: puma-3.12.4/lib/puma/rack/builder.rb000066400000000000000000000205121362626474300171630ustar00rootroot00000000000000module Puma end module Puma::Rack class Options def parse!(args) options = {} opt_parser = OptionParser.new("", 24, ' ') do |opts| opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]" opts.separator "" opts.separator "Ruby options:" lineno = 1 opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line| eval line, TOPLEVEL_BINDING, "-e", lineno lineno += 1 } opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line| options[:builder] = line } opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") { options[:debug] = true } opts.on("-w", "--warn", "turn warnings on for your script") { options[:warn] = true } opts.on("-q", "--quiet", "turn off logging") { options[:quiet] = true } opts.on("-I", "--include PATH", "specify $LOAD_PATH (may be used more than once)") { |path| (options[:include] ||= []).concat(path.split(":")) } opts.on("-r", "--require LIBRARY", "require the library, before executing your script") { |library| options[:require] = library } opts.separator "" opts.separator "Rack options:" opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s| options[:server] = s } opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host| options[:Host] = host } opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port| options[:Port] = port } opts.on("-O", "--option NAME[=VALUE]", "pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} -s SERVER -h' to get a list of options for SERVER") { |name| name, value = name.split('=', 2) value = true if value.nil? options[name.to_sym] = value } opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e| 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) } opts.separator "" opts.separator "Common options:" opts.on_tail("-h", "-?", "--help", "Show this message") do puts opts puts handler_opts(options) exit end opts.on_tail("--version", "Show version") do puts "Rack #{Rack.version} (Release: #{Rack.release})" exit end end begin opt_parser.parse! args rescue OptionParser::InvalidOption => e warn e.message abort opt_parser.to_s end options[:config] = args.last if args.last options end def handler_opts(options) begin info = [] server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options) if server && server.respond_to?(:valid_options) info << "" info << "Server-specific options for #{server.name}:" has_options = false server.valid_options.each do |name, description| next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own. info << " -O %-21s %s" % [name, description] has_options = true end return "" if !has_options end info.join("\n") rescue NameError return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options" end end end # Rack::Builder implements a small DSL to iteratively construct Rack # applications. # # Example: # # require 'rack/lobster' # app = Rack::Builder.new do # use Rack::CommonLogger # use Rack::ShowExceptions # map "/lobster" do # use Rack::Lint # run Rack::Lobster.new # end # end # # run app # # Or # # app = Rack::Builder.app do # use Rack::CommonLogger # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] } # end # # run app # # +use+ adds middleware to the stack, +run+ dispatches to an application. # You can use +map+ to construct a Rack::URLMap in a convenient way. class Builder def self.parse_file(config, opts = Options.new) options = {} if config =~ /\.ru$/ cfgfile = ::File.read(config) if cfgfile[/^#\\(.*)/] && opts options = opts.parse! $1.split(/\s+/) end cfgfile.sub!(/^__END__\n.*\Z/m, '') app = new_from_string cfgfile, config else require config app = Object.const_get(::File.basename(config, '.rb').capitalize) end return app, options end def self.new_from_string(builder_script, file="(rackup)") eval "Puma::Rack::Builder.new {\n" + builder_script + "\n}.to_app", TOPLEVEL_BINDING, file, 0 end def initialize(default_app = nil,&block) @use, @map, @run, @warmup = [], nil, default_app, nil # Conditionally load rack now, so that any rack middlewares, # etc are available. begin require 'rack' rescue LoadError end instance_eval(&block) if block_given? end def self.app(default_app = nil, &block) self.new(default_app, &block).to_app end # Specifies middleware to use in a stack. # # class Middleware # def initialize(app) # @app = app # end # # def call(env) # env["rack.some_header"] = "setting an example" # @app.call(env) # end # end # # use Middleware # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] } # # All requests through to this application will first be processed by the middleware class. # The +call+ method in this example sets an additional environment key which then can be # referenced in the application if required. def use(middleware, *args, &block) if @map mapping, @map = @map, nil @use << proc { |app| generate_map app, mapping } end @use << proc { |app| middleware.new(app, *args, &block) } end # Takes an argument that is an object that responds to #call and returns a Rack response. # The simplest form of this is a lambda object: # # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] } # # However this could also be a class: # # class Heartbeat # def self.call(env) # [200, { "Content-Type" => "text/plain" }, ["OK"]] # end # end # # run Heartbeat def run(app) @run = app end # Takes a lambda or block that is used to warm-up the application. # # warmup do |app| # client = Rack::MockRequest.new(app) # client.get('/') # end # # use SomeMiddleware # run MyApp def warmup(prc=nil, &block) @warmup = prc || block end # Creates a route within the application. # # Rack::Builder.app do # map '/' do # run Heartbeat # end # end # # The +use+ method can also be used here to specify middleware to run under a specific path: # # Rack::Builder.app do # map '/' do # use Middleware # run Heartbeat # end # end # # This example includes a piece of middleware which will run before requests hit +Heartbeat+. # def map(path, &block) @map ||= {} @map[path] = block end def to_app app = @map ? generate_map(@run, @map) : @run fail "missing run or map statement" unless app app = @use.reverse.inject(app) { |a,e| e[a] } @warmup.call(app) if @warmup app end def call(env) to_app.call(env) end private def generate_map(default_app, mapping) require 'puma/rack/urlmap' mapped = default_app ? {'/' => default_app} : {} mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app } URLMap.new(mapped) end end end puma-3.12.4/lib/puma/rack/urlmap.rb000066400000000000000000000050711362626474300170400ustar00rootroot00000000000000module Puma::Rack # Rack::URLMap takes a hash mapping urls or paths to apps, and # dispatches accordingly. Support for HTTP/1.1 host names exists if # the URLs start with http:// or https://. # # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part # relevant for dispatch is in the SCRIPT_NAME, and the rest in the # PATH_INFO. This should be taken care of when you need to # reconstruct the URL in order to create links. # # URLMap dispatches in such a way that the longest paths are tried # first, since they are most specific. class URLMap NEGATIVE_INFINITY = -1.0 / 0.0 INFINITY = 1.0 / 0.0 def initialize(map = {}) remap(map) end def remap(map) @mapping = map.map { |location, app| if location =~ %r{\Ahttps?://(.*?)(/.*)} host, location = $1, $2 else host = nil end unless location[0] == ?/ raise ArgumentError, "paths need to start with /" end location = location.chomp('/') match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n') [host, location, match, app] }.sort_by do |(host, location, _, _)| [host ? -host.size : INFINITY, -location.size] end end def call(env) path = env['PATH_INFO'] script_name = env['SCRIPT_NAME'] http_host = env['HTTP_HOST'] server_name = env['SERVER_NAME'] server_port = env['SERVER_PORT'] is_same_server = casecmp?(http_host, server_name) || casecmp?(http_host, "#{server_name}:#{server_port}") @mapping.each do |host, location, match, app| unless casecmp?(http_host, host) \ || casecmp?(server_name, host) \ || (!host && is_same_server) next end next unless m = match.match(path.to_s) rest = m[1] next unless !rest || rest.empty? || rest[0] == ?/ env['SCRIPT_NAME'] = (script_name + location) env['PATH_INFO'] = rest return app.call(env) end [404, {'Content-Type' => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]] ensure env['PATH_INFO'] = path env['SCRIPT_NAME'] = script_name end private def casecmp?(v1, v2) # if both nil, or they're the same string return true if v1 == v2 # if either are nil... (but they're not the same) return false if v1.nil? return false if v2.nil? # otherwise check they're not case-insensitive the same v1.casecmp(v2).zero? end end end puma-3.12.4/lib/puma/rack_default.rb000066400000000000000000000001651362626474300172430ustar00rootroot00000000000000require 'rack/handler/puma' module Rack::Handler def self.default(options = {}) Rack::Handler::Puma end end puma-3.12.4/lib/puma/reactor.rb000066400000000000000000000313471362626474300162640ustar00rootroot00000000000000# frozen_string_literal: true require 'puma/util' require 'puma/minissl' module Puma # Internal Docs, Not a public interface. # # The Reactor object is responsible for ensuring that a request has been # completely received before it starts to be processed. This may be known as read buffering. # If read buffering is not done, and no other read buffering is performed (such as by an application server # such as nginx) then the application would be subject to a slow client attack. # # Each Puma "worker" process has its own Reactor. For example if you start puma with `$ puma -w 5` then # it will have 5 workers and each worker will have it's own reactor. # # For a graphical representation of how the reactor works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline). # # ## Reactor Flow # # A request comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance. # The reactor stores the request in an array and calls `IO.select` on the array in a loop. # # When the request is written to by the client then the `IO.select` will "wake up" and # return the references to any objects that caused it to "wake". The reactor # then loops through each of these request objects, and sees if they're complete. If they # have a full header and body then the reactor passes the request to a thread pool. # Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request. # # If the request is not complete, then it stays in the array, and the next time any # data is written to that socket reference, then the loop is woken up and it is checked for completeness again. # # A detailed example is given in the docs for `run_internal` which is where the bulk # of this logic lives. class Reactor DefaultSleepFor = 5 # Creates an instance of Puma::Reactor # # The `server` argument is an instance of `Puma::Server` # this is used to write a response for "low level errors" # when there is an exception inside of the reactor. # # The `app_pool` is an instance of `Puma::ThreadPool`. # Once a request is fully formed (header and body are received) # it will be passed to the `app_pool`. def initialize(server, app_pool) @server = server @events = server.events @app_pool = app_pool @mutex = Mutex.new # Read / Write pipes to wake up internal while loop @ready, @trigger = Puma::Util.pipe @input = [] @sleep_for = DefaultSleepFor @timeouts = [] @sockets = [@ready] end private # Until a request is added via the `add` method this method will internally # loop, waiting on the `sockets` array objects. The only object in this # array at first is the `@ready` IO object, which is the read end of a pipe # connected to `@trigger` object. When `@trigger` is written to, then the loop # will break on `IO.select` and return an array. # # ## When a request is added: # # When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array. # Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`. # # When that happens, the internal loop stops blocking at `IO.select` and returns a reference # to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`. # When `@trigger` is written-to, the loop "wakes" and the `ready` # variable returns an array of arrays that looks like `[[#], [], []]` where the # first IO object is the `@ready` object. This first array `[#]` # is saved as a `reads` variable. # # The `reads` variable is iterated through. In the case that the object # is the same as the `@ready` input pipe, then we know that there was a `trigger` event. # # If there was a trigger event, then one byte of `@ready` is read into memory. In the case of the first request, # the reactor sees that it's a `"*"` value and the reactor adds the contents of `@input` into the `sockets` array. # The while then loop continues to iterate again, but now the `sockets` array contains a `Puma::Client` instance in addition # to the `@ready` IO object. For example: `[#, #]`. # # Since the `Puma::Client` in this example has data that has not been read yet, # the `IO.select` is immediately able to "wake" and read from the `Puma::Client`. At this point the # `ready` output looks like this: `[[#], [], []]`. # # Each element in the first entry is iterated over. The `Puma::Client` object is not # the `@ready` pipe, so the reactor checks to see if it has the fully header and body with # the `Puma::Client#try_to_finish` method. If the full request has been sent, # then the request is passed off to the `@app_pool` thread pool so that a "worker thread" # can pick up the request and begin to execute application logic. This is done # via `@app_pool << c`. The `Puma::Client` is then removed from the `sockets` array. # # If the request body is not present then nothing will happen, and the loop will iterate # again. When the client sends more data to the socket the `Puma::Client` object will # wake up the `IO.select` and it can again be checked to see if it's ready to be # passed to the thread pool. # # ## Time Out Case # # In addition to being woken via a write to one of the sockets the `IO.select` will # periodically "time out" of the sleep. One of the functions of this is to check for # any requests that have "timed out". At the end of the loop it's checked to see if # the first element in the `@timeout` array has exceed it's allowed time. If so, # the client object is removed from the timeout aray, a 408 response is written. # Then it's connection is closed, and the object is removed from the `sockets` array # that watches for new data. # # This behavior loops until all the objects that have timed out have been removed. # # Once all the timeouts have been processed, the next duration of the `IO.select` sleep # will be set to be equal to the amount of time it will take for the next timeout to occur. # This calculation happens in `calculate_sleep`. def run_internal sockets = @sockets while true begin ready = IO.select sockets, nil, nil, @sleep_for rescue IOError => e Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue if sockets.any? { |socket| socket.closed? } STDERR.puts "Error in select: #{e.message} (#{e.class})" STDERR.puts e.backtrace sockets = sockets.reject { |socket| socket.closed? } retry else raise end end if ready and reads = ready[0] reads.each do |c| if c == @ready @mutex.synchronize do case @ready.read(1) when "*" sockets += @input @input.clear when "c" sockets.delete_if do |s| if s == @ready false else s.close true end end when "!" return end end else # We have to be sure to remove it from the timeout # list or we'll accidentally close the socket when # it's in use! if c.timeout_at @mutex.synchronize do @timeouts.delete c end end begin if c.try_to_finish @app_pool << c sockets.delete c end # Don't report these to the lowlevel_error handler, otherwise # will be flooding them with errors when persistent connections # are closed. rescue ConnectionError c.write_500 c.close sockets.delete c # SSL handshake failure rescue MiniSSL::SSLError => e @server.lowlevel_error(e, c.env) ssl_socket = c.io addr = ssl_socket.peeraddr.last cert = ssl_socket.peercert c.close sockets.delete c @events.ssl_error @server, addr, cert, e # The client doesn't know HTTP well rescue HttpParserError => e @server.lowlevel_error(e, c.env) c.write_400 c.close sockets.delete c @events.parse_error @server, c.env, e rescue StandardError => e @server.lowlevel_error(e, c.env) c.write_500 c.close sockets.delete c end end end end unless @timeouts.empty? @mutex.synchronize do now = Time.now while @timeouts.first.timeout_at < now c = @timeouts.shift c.write_408 if c.in_data_phase c.close sockets.delete c break if @timeouts.empty? end calculate_sleep end end end end public def run run_internal ensure @trigger.close @ready.close end def run_in_thread @thread = Thread.new do begin run_internal rescue StandardError => e STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})" STDERR.puts e.backtrace retry ensure @trigger.close @ready.close end end end # The `calculate_sleep` sets the value that the `IO.select` will # sleep for in the main reactor loop when no sockets are being written to. # # The values kept in `@timeouts` are sorted so that the first timeout # comes first in the array. When there are no timeouts the default timeout is used. # # Otherwise a sleep value is set that is the same as the amount of time it # would take for the first element to time out. # # If that value is in the past, then a sleep value of zero is used. def calculate_sleep if @timeouts.empty? @sleep_for = DefaultSleepFor else diff = @timeouts.first.timeout_at.to_f - Time.now.to_f if diff < 0.0 @sleep_for = 0 else @sleep_for = diff end end end # This method adds a connection to the reactor # # Typically called by `Puma::Server` the value passed in # is usually a `Puma::Client` object that responds like an IO # object. # # The main body of the reactor loop is in `run_internal` and it # will sleep on `IO.select`. When a new connection is added to the # reactor it cannot be added directly to the `sockets` aray, because # the `IO.select` will not be watching for it yet. # # Instead what needs to happen is that `IO.select` needs to be woken up, # the contents of `@input` added to the `sockets` array, and then # another call to `IO.select` needs to happen. Since the `Puma::Client` # object can be read immediately, it does not block, but instead returns # right away. # # This behavior is accomplished by writing to `@trigger` which wakes up # the `IO.select` and then there is logic to detect the value of `*`, # pull the contents from `@input` and add them to the sockets array. # # If the object passed in has a timeout value in `timeout_at` then # it is added to a `@timeouts` array. This array is then re-arranged # so that the first element to timeout will be at the front of the # array. Then a value to sleep for is derived in the call to `calculate_sleep` def add(c) @mutex.synchronize do @input << c @trigger << "*" if c.timeout_at @timeouts << c @timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at } calculate_sleep end end end # Close all watched sockets and clear them from being watched def clear! begin @trigger << "c" rescue IOError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue end end def shutdown begin @trigger << "!" rescue IOError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue end @thread.join end end end puma-3.12.4/lib/puma/runner.rb000066400000000000000000000104221362626474300161250ustar00rootroot00000000000000# frozen_string_literal: true require 'puma/server' require 'puma/const' module Puma # Generic class that is used by `Puma::Cluster` and `Puma::Single` to # serve requests. This class spawns a new instance of `Puma::Server` via # a call to `start_server`. class Runner def initialize(cli, events) @launcher = cli @events = events @options = cli.options @app = nil @control = nil end def daemon? @options[:daemon] end def development? @options[:environment] == "development" end def test? @options[:environment] == "test" end def log(str) @events.log str end def before_restart @control.stop(true) if @control end def error(str) @events.error str end def debug(str) @events.log "- #{str}" if @options[:debug] end def start_control str = @options[:control_url] return unless str require 'puma/app/status' uri = URI.parse str app = Puma::App::Status.new @launcher if token = @options[:control_auth_token] app.auth_token = token unless token.empty? or token == :none end control = Puma::Server.new app, @launcher.events control.min_threads = 0 control.max_threads = 1 case uri.scheme when "tcp" log "* Starting control server on #{str}" control.add_tcp_listener uri.host, uri.port when "unix" log "* Starting control server on #{str}" path = "#{uri.host}#{uri.path}" mask = @options[:control_url_umask] control.add_unix_listener path, mask else error "Invalid control URI: #{str}" end control.run @control = control end def ruby_engine if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}" else if defined?(RUBY_ENGINE_VERSION) "#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}" else "#{RUBY_ENGINE} #{RUBY_VERSION}" end end end def output_header(mode) min_t = @options[:min_threads] max_t = @options[:max_threads] log "Puma starting in #{mode} mode..." 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? @options[:redirect_stdout] || @options[:redirect_stderr] end def redirect_io stdout = @options[:redirect_stdout] stderr = @options[:redirect_stderr] append = @options[:redirect_append] if stdout unless Dir.exist?(File.dirname(stdout)) raise "Cannot redirect STDOUT to #{stdout}" end STDOUT.reopen stdout, (append ? "a" : "w") STDOUT.sync = true STDOUT.puts "=== puma startup: #{Time.now} ===" end if stderr unless Dir.exist?(File.dirname(stderr)) raise "Cannot redirect STDERR to #{stderr}" end STDERR.reopen stderr, (append ? "a" : "w") STDERR.sync = true STDERR.puts "=== puma startup: #{Time.now} ===" end end def load_and_bind unless @launcher.config.app_configured? error "No application configured, nothing to run" exit 1 end # Load the app before we daemonize. begin @app = @launcher.config.app rescue Exception => e log "! Unable to load application: #{e.class}: #{e.message}" raise e end @launcher.binder.parse @options[:binds], self end def app @app ||= @launcher.config.app end def start_server min_t = @options[:min_threads] max_t = @options[:max_threads] server = Puma::Server.new app, @launcher.events, @options server.min_threads = min_t 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 unless development? || test? server.leak_stack_on_error = false end server end end end puma-3.12.4/lib/puma/server.rb000066400000000000000000000677151362626474300161430ustar00rootroot00000000000000# frozen_string_literal: true require 'stringio' require 'puma/thread_pool' require 'puma/const' require 'puma/events' require 'puma/null_io' require 'puma/compat' require 'puma/reactor' require 'puma/client' require 'puma/binder' require 'puma/delegation' require 'puma/accept_nonblock' require 'puma/util' require 'puma/puma_http11' unless Puma.const_defined? "IOBuffer" require 'puma/io_buffer' end require 'socket' module Puma # The HTTP Server itself. Serves out a single Rack app. # # This class is used by the `Puma::Single` and `Puma::Cluster` classes # to generate one or more `Puma::Server` instances capable of handling requests. # Each Puma process will contain one `Puma::Server` instacne. # # The `Puma::Server` instance pulls requests from the socket, adds them to a # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`. # # Each `Puma::Server` will have one reactor and one thread pool. class Server include Puma::Const extend Puma::Delegation attr_reader :thread attr_reader :events attr_accessor :app attr_accessor :min_threads attr_accessor :max_threads attr_accessor :persistent_timeout attr_accessor :auto_trim_time attr_accessor :reaping_time attr_accessor :first_data_timeout # Create a server for the rack app +app+. # # +events+ is an object which will be called when certain error events occur # to be handled. See Puma::Events for the list of current methods to implement. # # Server#run returns a thread that you can join on to wait for the server # to do its work. # def initialize(app, events=Events.stdio, options={}) @app = app @events = events @check, @notify = Puma::Util.pipe @status = :stop @min_threads = 0 @max_threads = 16 @auto_trim_time = 30 @reaping_time = 1 @thread = nil @thread_pool = nil @early_hints = nil @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT) @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT) @binder = Binder.new(events) @own_binder = true @leak_stack_on_error = true @options = options @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests] ENV['RACK_ENV'] ||= "development" @mode = :http @precheck_closing = true end attr_accessor :binder, :leak_stack_on_error, :early_hints forward :add_tcp_listener, :@binder forward :add_ssl_listener, :@binder forward :add_unix_listener, :@binder forward :connected_port, :@binder def inherit_binder(bind) @binder = bind @own_binder = false 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. # if RUBY_PLATFORM =~ /linux/ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze # 6 == Socket::IPPROTO_TCP # 3 == TCP_CORK # 1/0 == turn on/off def cork_socket(socket) begin socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket rescue IOError, SystemCallError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue end end def uncork_socket(socket) begin socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket rescue IOError, SystemCallError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue end end def closed_socket?(socket) return false unless socket.kind_of? TCPSocket return false unless @precheck_closing begin tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO) rescue IOError, SystemCallError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue @precheck_closing = false false else state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0] # TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11 (state >= 6 && state <= 9) || state == 11 end end else def cork_socket(socket) end def uncork_socket(socket) end def closed_socket?(socket) false end end def backlog @thread_pool and @thread_pool.backlog end def running @thread_pool and @thread_pool.spawned end # This number represents the number of requests that # the server is capable of taking right now. # # For example if the number is 5 then it means # there are 5 threads sitting idle ready to take # a request. If one request comes in, then the # value would be 4 until it finishes processing. def pool_capacity @thread_pool and @thread_pool.pool_capacity end # Lopez Mode == raw tcp apps def run_lopez_mode(background=true) @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 { handle_servers_lopez_mode } 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 @notify.close if @status != :restart and @own_binder @binder.close end end @events.fire :state, :done end # Runs the server. # # If +background+ is true (the default) then a thread is spun # up in the background to handle requests. Otherwise requests # are handled synchronously. # def run(background=true) BasicSocket.do_not_reverse_lookup = true @events.fire :state, :booting @status = :run if @mode == :tcp return run_lopez_mode(background) end queue_requests = @queue_requests @thread_pool = ThreadPool.new(@min_threads, @max_threads, IOBuffer) do |client, buffer| # Advertise this server into the thread Thread.current[ThreadLocalKey] = self process_now = false begin if queue_requests process_now = client.eagerly_finish else client.finish process_now = true end rescue MiniSSL::SSLError => e ssl_socket = client.io addr = ssl_socket.peeraddr.last cert = ssl_socket.peercert client.close @events.ssl_error self, addr, cert, e rescue HttpParserError => e client.write_400 client.close @events.parse_error self, client.env, e rescue ConnectionError, EOFError client.close else if process_now process_client client, buffer else client.set_timeout @first_data_timeout @reactor.add client end end end @thread_pool.clean_thread_locals = @options[:clean_thread_locals] if queue_requests @reactor = Reactor.new self, @thread_pool @reactor.run_in_thread end if @reaping_time @thread_pool.auto_reap!(@reaping_time) end if @auto_trim_time @thread_pool.auto_trim!(@auto_trim_time) end @events.fire :state, :running if background @thread = Thread.new { handle_servers } return @thread else handle_servers end end def handle_servers begin check = @check sockets = [check] + @binder.ios pool = @thread_pool queue_requests = @queue_requests remote_addr_value = nil remote_addr_header = nil case @options[:remote_address] when :value remote_addr_value = @options[:remote_address_value] when :header remote_addr_header = @options[:remote_address_header] end 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, @binder.env(sock) if remote_addr_value client.peerip = remote_addr_value elsif remote_addr_header client.remote_addr_header = remote_addr_header end pool << client pool.wait_until_not_full 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 if queue_requests @reactor.clear! @reactor.shutdown end rescue Exception => e STDERR.puts "Exception handling servers: #{e.message} (#{e.class})" STDERR.puts e.backtrace ensure @check.close @notify.close if @status != :restart and @own_binder @binder.close end end @events.fire :state, :done end # :nodoc: def handle_check cmd = @check.read(1) case cmd when STOP_COMMAND @status = :stop return true when HALT_COMMAND @status = :halt return true when RESTART_COMMAND @status = :restart return true end return false end # Given a connection on +client+, handle the incoming requests. # # This method support HTTP Keep-Alive so it may, depending on if the client # indicates that it supports keep alive, wait for another request before # returning. # def process_client(client, buffer) begin clean_thread_locals = @options[:clean_thread_locals] close_socket = true requests = 0 while true case handle_request(client, buffer) when false return when :async close_socket = false return when true return unless @queue_requests buffer.reset ThreadPool.clean_thread_locals if clean_thread_locals requests += 1 check_for_more_data = @status == :run if requests >= MAX_FAST_INLINE # This will mean that reset will only try to use the data it already # has buffered and won't try to read more data. What this means is that # every client, independent of their request speed, gets treated like a slow # one once every MAX_FAST_INLINE requests. check_for_more_data = false end unless client.reset(check_for_more_data) close_socket = false client.set_timeout @persistent_timeout @reactor.add client return end end end # The client disconnected while we were reading data rescue ConnectionError # Swallow them. The ensure tries to close +client+ down # SSL handshake error rescue MiniSSL::SSLError => e lowlevel_error(e, client.env) ssl_socket = client.io addr = ssl_socket.peeraddr.last cert = ssl_socket.peercert close_socket = true @events.ssl_error self, addr, cert, e # The client doesn't know HTTP well rescue HttpParserError => e lowlevel_error(e, client.env) client.write_400 @events.parse_error self, client.env, e # Server error rescue StandardError => e lowlevel_error(e, client.env) client.write_500 @events.unknown_error self, e, "Read" ensure buffer.reset begin client.close if close_socket rescue IOError, SystemCallError Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue # Already closed rescue StandardError => e @events.unknown_error self, e, "Client" end end end # Given a Hash +env+ for the request read from +client+, add # and fixup keys to comply with Rack's env guidelines. # def normalize_env(env, client) if host = env[HTTP_HOST] if colon = host.index(":") env[SERVER_NAME] = host[0, colon] env[SERVER_PORT] = host[colon+1, host.bytesize] else env[SERVER_NAME] = host env[SERVER_PORT] = default_server_port(env) end else env[SERVER_NAME] = LOCALHOST env[SERVER_PORT] = default_server_port(env) end unless env[REQUEST_PATH] # it might be a dumbass full host request header uri = URI.parse(env[REQUEST_URI]) env[REQUEST_PATH] = uri.path raise "No REQUEST PATH" unless env[REQUEST_PATH] # A nil env value will cause a LintError (and fatal errors elsewhere), # so only set the env value if there actually is a value. env[QUERY_STRING] = uri.query if uri.query end env[PATH_INFO] = env[REQUEST_PATH] # From http://www.ietf.org/rfc/rfc3875 : # "Script authors should be aware that the REMOTE_ADDR and # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9) # may not identify the ultimate source of the request. # They identify the client for the immediate request to the # server; that client may be a proxy, gateway, or other # intermediary acting on behalf of the actual source client." # unless env.key?(REMOTE_ADDR) begin addr = client.peerip rescue Errno::ENOTCONN # Client disconnects can result in an inability to get the # peeraddr from the socket; default to localhost. addr = LOCALHOST_IP end # Set unix socket addrs to localhost addr = LOCALHOST_IP if addr.empty? env[REMOTE_ADDR] = addr end end def default_server_port(env) return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https' env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80 end # Given the request +env+ from +client+ and a partial request body # in +body+, finish reading the body if there is one and invoke # the rack app. Then construct the response and write it back to # +client+ # # +cl+ is the previously fetched Content-Length header if there # was one. This is an optimization to keep from having to look # it up again. # def handle_request(req, lines) env = req.env client = req.io return false if closed_socket?(client) normalize_env env, req env[PUMA_SOCKET] = client if env[HTTPS_KEY] && client.peercert env[PUMA_PEERCERT] = client.peercert end env[HIJACK_P] = true env[HIJACK] = req body = req.body head = env[REQUEST_METHOD] == HEAD env[RACK_INPUT] = body env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP if @early_hints env[EARLY_HINTS] = lambda { |headers| fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze headers.each_pair do |k, vs| if vs.respond_to?(:to_s) && !vs.to_s.empty? vs.to_s.split(NEWLINE).each do |v| next if possible_header_injection?(v) fast_write client, "#{k}: #{v}\r\n" end else fast_write client, "#{k}: #{vs}\r\n" end end fast_write client, "\r\n".freeze } end # A rack extension. If the app writes #call'ables to this # array, we will invoke them when the request is done. # after_reply = env[RACK_AFTER_REPLY] = [] begin begin status, headers, res_body = @app.call(env) return :async if req.hijacked status = status.to_i if status == -1 unless headers.empty? and res_body == [] raise "async response must have empty headers and body" end 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"] rescue Exception => e @events.unknown_error self, e, "Rack app", env status, headers, res_body = lowlevel_error(e, env) end content_length = nil no_body = head if res_body.kind_of? Array and res_body.size == 1 content_length = res_body[0].bytesize end cork_socket client line_ending = LINE_END colon = COLON http_11 = if env[HTTP_VERSION] == 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 # the response header. # if status == 200 lines << HTTP_11_200 else lines.append "HTTP/1.1 ", status.to_s, " ", fetch_status_code(status), line_ending 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 # if status == 200 lines << HTTP_10_200 else lines.append "HTTP/1.0 ", status.to_s, " ", fetch_status_code(status), line_ending no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status] end false end response_hijack = nil headers.each do |k, vs| case k.downcase when CONTENT_LENGTH2 next if possible_header_injection?(vs) content_length = vs next when TRANSFER_ENCODING allow_chunked = false content_length = nil when HIJACK response_hijack = vs next end if vs.respond_to?(:to_s) && !vs.to_s.empty? vs.to_s.split(NEWLINE).each do |v| next if possible_header_injection?(v) lines.append k, colon, v, line_ending end else lines.append k, colon, line_ending end end if include_keepalive_header lines << CONNECTION_KEEP_ALIVE elsif http_11 && !keep_alive lines << CONNECTION_CLOSE end if no_body if content_length and status != 204 lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending end lines << line_ending fast_write client, lines.to_s return keep_alive end if content_length lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending chunked = false elsif !response_hijack and allow_chunked lines << TRANSFER_ENCODING_CHUNKED chunked = true end lines << line_ending fast_write client, lines.to_s if response_hijack response_hijack.call client return :async end begin res_body.each do |part| next if part.bytesize.zero? if chunked fast_write client, part.bytesize.to_s(16) fast_write client, line_ending fast_write client, part fast_write client, line_ending else fast_write client, part end client.flush end if chunked fast_write client, CLOSE_CHUNKED client.flush end rescue SystemCallError, IOError raise ConnectionError, "Connection error detected during write" end ensure uncork_socket client body.close req.tempfile.unlink if req.tempfile res_body.close if res_body.respond_to? :close after_reply.each { |o| o.call } end return keep_alive end def fetch_status_code(status) HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' } end private :fetch_status_code # Given the request +env+ from +client+ and the partial body +body+ # plus a potential Content-Length value +cl+, finish reading # the body and return it. # # If the body is larger than MAX_BODY, a Tempfile object is used # for the body, otherwise a StringIO is used. # def read_body(env, client, body, cl) content_length = cl.to_i remain = content_length - body.bytesize return StringIO.new(body) if remain <= 0 # Use a Tempfile if there is a lot of data left if remain > MAX_BODY stream = Tempfile.new(Const::PUMA_TMP_BASE) stream.binmode else # The body[0,0] trick is to get an empty string in the same # encoding as body. stream = StringIO.new body[0,0] end stream.write body # Read an odd sized chunk so we can read even sized ones # after this chunk = client.readpartial(remain % CHUNK_SIZE) # No chunk means a closed socket unless chunk stream.close return nil end remain -= stream.write(chunk) # Raed the rest of the chunks while remain > 0 chunk = client.readpartial(CHUNK_SIZE) unless chunk stream.close return nil end remain -= stream.write(chunk) end stream.rewind return stream end # A fallback rack response if +@app+ raises as exception. # def lowlevel_error(e, env) if handler = @options[:lowlevel_error_handler] if handler.arity == 1 return handler.call(e) else return handler.call(e, env) end end if @leak_stack_on_error [500, {}, ["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"]] end end # Wait for all outstanding requests to finish. # def graceful_shutdown if @options[:shutdown_debug] threads = Thread.list total = threads.size pid = Process.pid $stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n" threads.each_with_index do |t,i| $stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n" $stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n" end $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n" end if @options[:drain_on_shutdown] count = 0 while true ios = IO.select @binder.ios, nil, nil, 0 break unless ios ios.first.each do |sock| begin if io = sock.accept_nonblock count += 1 client = Client.new io, @binder.env(sock) @thread_pool << client end rescue SystemCallError end end end @events.debug "Drained #{count} additional connections." end if @thread_pool if timeout = @options[:force_shutdown_after] @thread_pool.shutdown timeout.to_i else @thread_pool.shutdown end end end def notify_safely(message) begin @notify << message rescue IOError # The server, in another thread, is shutting down Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue rescue RuntimeError => e # Temporary workaround for https://bugs.ruby-lang.org/issues/13239 if e.message.include?('IOError') Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue else raise e end end end private :notify_safely # Stops the acceptor thread and then causes the worker threads to finish # off the request queue before finally exiting. def stop(sync=false) notify_safely(STOP_COMMAND) @thread.join if @thread && sync end def halt(sync=false) notify_safely(HALT_COMMAND) @thread.join if @thread && sync end def begin_restart notify_safely(RESTART_COMMAND) end def fast_write(io, str) n = 0 while true begin n = io.syswrite str rescue Errno::EAGAIN, Errno::EWOULDBLOCK if !IO.select(nil, [io], nil, WRITE_TIMEOUT) raise ConnectionError, "Socket timeout writing data" end retry rescue Errno::EPIPE, SystemCallError, IOError raise ConnectionError, "Socket timeout writing data" end return if n == str.bytesize str = str.byteslice(n..-1) end end private :fast_write ThreadLocalKey = :puma_server def self.current Thread.current[ThreadLocalKey] end def shutting_down? @status == :stop || @status == :restart end def possible_header_injection?(header_value) HTTP_INJECTION_REGEX =~ header_value.to_s end private :possible_header_injection? end end puma-3.12.4/lib/puma/single.rb000066400000000000000000000053171362626474300161040ustar00rootroot00000000000000# frozen_string_literal: true require 'puma/runner' require 'puma/detect' require 'puma/plugin' module Puma # This class is instantiated by the `Puma::Launcher` and used # to boot and serve a Ruby application when no puma "workers" are needed # i.e. only using "threaded" mode. For example `$ puma -t 1:5` # # At the core of this class is running an instance of `Puma::Server` which # gets created via the `start_server` method from the `Puma::Runner` class # that this inherits from. class Single < Runner def stats b = @server.backlog || 0 r = @server.running || 0 t = @server.pool_capacity || 0 m = @server.max_threads || 0 %Q!{ "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }! end def restart @server.begin_restart end def stop @server.stop false end def halt @server.halt end def stop_blocked log "- Gracefully stopping, waiting for requests to finish" @control.stop(true) if @control @server.stop(true) 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 Plugins.fire_background @launcher.write_state start_control @server = server = start_server unless daemon? log "Use Ctrl-C to stop" redirect_io end @launcher.events.fire_on_booted! begin server.run.join rescue Interrupt # Swallow it end end end end puma-3.12.4/lib/puma/state_file.rb000066400000000000000000000007461362626474300167430ustar00rootroot00000000000000# frozen_string_literal: true require 'yaml' module Puma class StateFile def initialize @options = {} end def save(path) File.write path, YAML.dump(@options) end def load(path) @options = YAML.load File.read(path) end FIELDS = %w!control_url control_auth_token pid! FIELDS.each do |f| define_method f do @options[f] end define_method "#{f}=" do |v| @options[f] = v end end end end puma-3.12.4/lib/puma/tcp_logger.rb000066400000000000000000000014501362626474300167420ustar00rootroot00000000000000# 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 puma-3.12.4/lib/puma/thread_pool.rb000066400000000000000000000214621362626474300171220ustar00rootroot00000000000000# frozen_string_literal: true require 'thread' module Puma # Internal Docs for A simple thread pool management object. # # Each Puma "worker" has a thread pool to process requests. # # First a connection to a client is made in `Puma::Server`. It is wrapped in a # `Puma::Client` instance and then passed to the `Puma::Reactor` to ensure # the whole request is buffered into memory. Once the request is ready, it is passed into # a thread pool via the `Puma::ThreadPool#<<` operator where it is stored in a `@todo` array. # # Each thread in the pool has an internal loop where it pulls a request from the `@todo` array # and proceses it. class ThreadPool class ForceShutdown < RuntimeError end # How long, after raising the ForceShutdown of a thread during # forced shutdown mode, to wait for the thread to try and finish # up its work before leaving the thread to die on the vine. SHUTDOWN_GRACE_TIME = 5 # seconds # Maintain a minimum of +min+ and maximum of +max+ threads # in the pool. # # The block passed is the work that will be performed in each # thread. # def initialize(min, max, *extra, &block) @not_empty = ConditionVariable.new @not_full = ConditionVariable.new @mutex = Mutex.new @todo = [] @spawned = 0 @waiting = 0 @min = Integer(min) @max = Integer(max) @block = block @extra = extra @shutdown = false @trim_requested = 0 @workers = [] @auto_trim = nil @reaper = nil @mutex.synchronize do @min.times { spawn_thread } end @clean_thread_locals = false end attr_reader :spawned, :trim_requested, :waiting attr_accessor :clean_thread_locals def self.clean_thread_locals Thread.current.keys.each do |key| # rubocop: disable Performance/HashEachMethods Thread.current[key] = nil unless key == :__recursive_key__ end end # How many objects have yet to be processed by the pool? # def backlog @mutex.synchronize { @todo.size } end def pool_capacity waiting + (@max - spawned) end # :nodoc: # # Must be called with @mutex held! # def spawn_thread @spawned += 1 th = Thread.new(@spawned) do |spawned| # Thread name is new in Ruby 2.3 Thread.current.name = 'puma %03i' % spawned if Thread.current.respond_to?(:name=) todo = @todo block = @block mutex = @mutex not_empty = @not_empty not_full = @not_full extra = @extra.map { |i| i.new } while true work = nil continue = true mutex.synchronize do while todo.empty? if @trim_requested > 0 @trim_requested -= 1 continue = false not_full.signal break end if @shutdown continue = false break end @waiting += 1 not_full.signal not_empty.wait mutex @waiting -= 1 end work = todo.shift if continue end break unless continue if @clean_thread_locals ThreadPool.clean_thread_locals end begin block.call(work, *extra) rescue Exception => e STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})" end end mutex.synchronize do @spawned -= 1 @workers.delete th end end @workers << th th end private :spawn_thread # Add +work+ to the todo list for a Thread to pickup and process. def <<(work) @mutex.synchronize do if @shutdown raise "Unable to add work while shutting down" end @todo << work if @waiting < @todo.size and @spawned < @max spawn_thread end @not_empty.signal end end # This method is used by `Puma::Server` to let the server know when # the thread pool can pull more requests from the socket and # pass to the reactor. # # The general idea is that the thread pool can only work on a fixed # number of requests at the same time. If it is already processing that # number of requests then it is at capacity. If another Puma process has # spare capacity, then the request can be left on the socket so the other # worker can pick it up and process it. # # For example: if there are 5 threads, but only 4 working on # requests, this method will not wait and the `Puma::Server` # can pull a request right away. # # If there are 5 threads and all 5 of them are busy, then it will # pause here, and wait until the `not_full` condition variable is # signaled, usually this indicates that a request has been processed. # # It's important to note that even though the server might accept another # request, it might not be added to the `@todo` array right away. # For example if a slow client has only sent a header, but not a body # then the `@todo` array would stay the same size as the reactor works # to try to buffer the request. In tha scenario the next call to this # method would not block and another request would be added into the reactor # by the server. This would continue until a fully bufferend request # makes it through the reactor and can then be processed by the thread pool. def wait_until_not_full @mutex.synchronize do while true return if @shutdown # If we can still spin up new threads and there # is work queued that cannot be handled by waiting # threads, then accept more work until we would # spin up the max number of threads. return if @todo.size - @waiting < @max - @spawned @not_full.wait @mutex end end end # If too many threads are in the pool, tell one to finish go ahead # and exit. If +force+ is true, then a trim request is requested # even if all threads are being utilized. # def trim(force=false) @mutex.synchronize do if (force or @waiting > 0) and @spawned - @trim_requested > @min @trim_requested += 1 @not_empty.signal end end end # If there are dead threads in the pool make them go away while decreasing # spawned counter so that new healthy threads could be created again. def reap @mutex.synchronize do dead_workers = @workers.reject(&:alive?) dead_workers.each do |worker| worker.kill @spawned -= 1 end @workers.delete_if do |w| dead_workers.include?(w) end end end class AutoTrim def initialize(pool, timeout) @pool = pool @timeout = timeout @running = false end def start! @running = true @thread = Thread.new do while @running @pool.trim sleep @timeout end end end def stop @running = false @thread.wakeup end end def auto_trim!(timeout=30) @auto_trim = AutoTrim.new(self, timeout) @auto_trim.start! end class Reaper def initialize(pool, timeout) @pool = pool @timeout = timeout @running = false end def start! @running = true @thread = Thread.new do while @running @pool.reap sleep @timeout end end end def stop @running = false @thread.wakeup end end def auto_reap!(timeout=5) @reaper = Reaper.new(self, timeout) @reaper.start! end # Tell all threads in the pool to exit and wait for them to finish. # def shutdown(timeout=-1) threads = @mutex.synchronize do @shutdown = true @not_empty.broadcast @not_full.broadcast @auto_trim.stop if @auto_trim @reaper.stop if @reaper # dup workers so that we join them all safely @workers.dup end if timeout == -1 # Wait for threads to finish without force shutdown. threads.each(&:join) else # Wait for threads to finish after n attempts (+timeout+). # If threads are still running, it will forcefully kill them. timeout.times do threads.delete_if do |t| t.join 1 end if threads.empty? break else sleep 1 end end threads.each do |t| t.raise ForceShutdown end threads.each do |t| t.join SHUTDOWN_GRACE_TIME end end @spawned = 0 @workers = [] end end end puma-3.12.4/lib/puma/util.rb000066400000000000000000000060321362626474300155730ustar00rootroot00000000000000# frozen_string_literal: true major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i } if major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125 require 'puma/rack/backports/uri/common_193' else require 'uri/common' end module Puma module Util module_function def pipe IO.pipe end # Unescapes a URI escaped string with +encoding+. +encoding+ will be the # target encoding of the string returned, and it defaults to UTF-8 if defined?(::Encoding) def unescape(s, encoding = Encoding::UTF_8) URI.decode_www_form_component(s, encoding) end else def unescape(s, encoding = nil) URI.decode_www_form_component(s, encoding) end end module_function :unescape DEFAULT_SEP = /[&;] */n # Stolen from Mongrel, with some small modifications: # Parses a query string by breaking it up at the '&' # and ';' characters. You can also use this to parse # cookies by changing the characters used in the second # parameter (which defaults to '&;'). def parse_query(qs, d = nil, &unescaper) unescaper ||= method(:unescape) params = {} (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p| next if p.empty? k, v = p.split('=', 2).map(&unescaper) if cur = params[k] if cur.class == Array params[k] << v else params[k] = [cur, v] end else params[k] = v end end return params end # A case-insensitive Hash that preserves the original case of a # header when set. class HeaderHash < Hash def self.new(hash={}) HeaderHash === hash ? hash : super(hash) end def initialize(hash={}) super() @names = {} hash.each { |k, v| self[k] = v } end def each super do |k, v| yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v) end end def to_hash hash = {} each { |k,v| hash[k] = v } hash end def [](k) super(k) || super(@names[k.downcase]) end def []=(k, v) canonical = k.downcase delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary @names[k] = @names[canonical] = k super k, v end def delete(k) canonical = k.downcase result = super @names.delete(canonical) @names.delete_if { |name,| name.downcase == canonical } result end def include?(k) @names.include?(k) || @names.include?(k.downcase) end alias_method :has_key?, :include? alias_method :member?, :include? alias_method :key?, :include? def merge!(other) other.each { |k, v| self[k] = v } self end def merge(other) hash = dup hash.merge! other end def replace(other) clear other.each { |k, v| self[k] = v } self end end end end puma-3.12.4/lib/rack/000077500000000000000000000000001362626474300142465ustar00rootroot00000000000000puma-3.12.4/lib/rack/handler/000077500000000000000000000000001362626474300156635ustar00rootroot00000000000000puma-3.12.4/lib/rack/handler/puma.rb000066400000000000000000000066711362626474300171640ustar00rootroot00000000000000require 'rack/handler' module Rack module Handler module Puma DEFAULT_OPTIONS = { :Verbose => false, :Silent => false } def self.config(app, options = {}) require 'puma' require 'puma/configuration' require 'puma/events' require 'puma/launcher' default_options = DEFAULT_OPTIONS.dup # Libraries pass in values such as :Port and there is no way to determine # if it is a default provided by the library or a special value provided # by the user. A special key `user_supplied_options` can be passed. This # contains an array of all explicitly defined user options. We then # know that all other values are defaults if user_supplied_options = options.delete(:user_supplied_options) (options.keys - user_supplied_options).each do |k| default_options[k] = options.delete(k) end end conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config| user_config.quiet if options.delete(:Verbose) app = Rack::CommonLogger.new(app, STDOUT) end if options[:environment] user_config.environment options[:environment] end if options[:Threads] min, max = options.delete(:Threads).split(':', 2) user_config.threads min, max end if options[:Host] || options[:Port] host = options[:Host] || default_options[:Host] port = options[:Port] || default_options[:Port] self.set_host_port_to_config(host, port, user_config) end if default_options[:Host] file_config.set_default_host(default_options[:Host]) end self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config) user_config.app app end conf end def self.run(app, options = {}) conf = self.config(app, options) events = options.delete(:Silent) ? ::Puma::Events.strings : ::Puma::Events.stdio launcher = ::Puma::Launcher.new(conf, :events => events) yield launcher if block_given? begin launcher.run rescue Interrupt puts "* Gracefully stopping, waiting for requests to finish" launcher.stop puts "* Goodbye!" end end def self.valid_options { "Host=HOST" => "Hostname to listen on (default: localhost)", "Port=PORT" => "Port to listen on (default: 8080)", "Threads=MIN:MAX" => "min:max threads to use (default 0:16)", "Verbose" => "Don't report each request (default: false)" } end private def self.set_host_port_to_config(host, port, config) config.clear_binds! if host || port if host && (host[0,1] == '.' || host[0,1] == '/') config.bind "unix://#{host}" elsif host && host =~ /^ssl:\/\// uri = URI.parse(host) uri.port ||= port || ::Puma::Configuration::DefaultTCPPort config.bind uri.to_s else if host port ||= ::Puma::Configuration::DefaultTCPPort end if port host ||= ::Puma::Configuration::DefaultTCPHost config.port port, host end end end end register :puma, Puma end end puma-3.12.4/puma.gemspec000066400000000000000000000022501362626474300150660ustar00rootroot00000000000000# -*- encoding: utf-8 -*- # This is only used when puma is a git dep from Bundler, keep in sync with Rakefile version = File.read(File.expand_path("../lib/puma/const.rb", __FILE__))[/VERSION = "(\d+\.\d+\.\d+)"/, 1] || raise Gem::Specification.new do |s| s.name = "puma" s.version = version s.authors = ["Evan Phoenix"] s.description = "Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications. Puma is intended for use in both development and production environments. It's great for highly concurrent Ruby implementations such as Rubinius and JRuby as well as as providing process worker support to support CRuby well." s.summary = "Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications" s.email = ["evan@phx.io"] s.executables = ["puma", "pumactl"] s.extensions = ["ext/puma_http11/extconf.rb"] s.metadata["msys2_mingw_dependencies"] = "openssl" s.files = `git ls-files -- bin docs ext lib tools`.split("\n") + %w[History.md LICENSE README.md] s.homepage = "http://puma.io" s.license = "BSD-3-Clause" s.required_ruby_version = Gem::Requirement.new(">= 2.2") end puma-3.12.4/test/000077500000000000000000000000001362626474300135375ustar00rootroot00000000000000puma-3.12.4/test/config/000077500000000000000000000000001362626474300150045ustar00rootroot00000000000000puma-3.12.4/test/config/ab_rs.rb000066400000000000000000000006341362626474300164220ustar00rootroot00000000000000url = ARGV.shift count = (ARGV.shift || 1000).to_i STDOUT.sync = true 1.upto(5) do |i| print "#{i}: " str = `ab -n #{count} -c #{i} #{url} 2>/dev/null` rs = /Requests per second:\s+([\d.]+)\s/.match(str) puts rs[1] end puts "Keep Alive:" 1.upto(5) do |i| print "#{i}: " str = `ab -n #{count} -k -c #{i} #{url} 2>/dev/null` rs = /Requests per second:\s+([\d.]+)\s/.match(str) puts rs[1] end puma-3.12.4/test/config/app.rb000066400000000000000000000002211362626474300161040ustar00rootroot00000000000000port ENV['PORT'] if ENV['PORT'] app do |env| [200, {}, ["embedded app"]] end lowlevel_error_handler do |err| [200, {}, ["error page"]] end puma-3.12.4/test/config/plugin.rb000066400000000000000000000000241362626474300166230ustar00rootroot00000000000000plugin :tmp_restart puma-3.12.4/test/config/settings.rb000066400000000000000000000000271362626474300171700ustar00rootroot00000000000000port 3000 threads 3, 5 puma-3.12.4/test/config/simple.rb000066400000000000000000000000701362626474300166170ustar00rootroot00000000000000pidfile "/tmp/jruby.pid" switch_user "daemon", "daemon" puma-3.12.4/test/config/ssl_config.rb000066400000000000000000000003051362626474300174550ustar00rootroot00000000000000key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__ ssl_bind "0.0.0.0", 9292, :cert => cert, :key => key puma-3.12.4/test/config/state_file_testing_config.rb000066400000000000000000000004331362626474300225320ustar00rootroot00000000000000pidfile "t3-pid" workers 3 on_worker_boot do |index| File.open("t3-worker-#{index}-pid", "w") { |f| f.puts Process.pid } end before_fork { 1 } on_worker_shutdown { 1 } on_worker_boot { 1 } on_worker_fork { 1 } on_restart { 1 } after_worker_boot { 1 } lowlevel_error_handler { 1 } puma-3.12.4/test/config/with_integer_convert.rb000066400000000000000000000002311362626474300215550ustar00rootroot00000000000000persistent_timeout "6" first_data_timeout "3" workers "2" threads "4", "8" worker_timeout "90" worker_boot_timeout "120" worker_shutdown_timeout "150" puma-3.12.4/test/helper.rb000066400000000000000000000051021362626474300153410ustar00rootroot00000000000000# Copyright (c) 2011 Evan Phoenix # Copyright (c) 2005 Zed A. Shaw if %w(2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1).include? RUBY_VERSION begin require 'stopgap_13632' rescue LoadError end end begin require "bundler/setup" # bundler/setup may not load bundler require "bundler" unless Bundler.const_defined?(:ORIGINAL_ENV) rescue LoadError warn "Failed to load bundler ... this should only happen during package building" end require "net/http" require "timeout" require "minitest/autorun" require "minitest/pride" $LOAD_PATH << File.expand_path("../../lib", __FILE__) Thread.abort_on_exception = true 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) uris.map do |u| response = if u.kind_of? String Net::HTTP.get(URI.parse(u)) else url = URI.parse(u[0]) Net::HTTP.new(url.host, url.port).start {|h| h.request(u[1]) } end assert response, "Didn't get a response: #{u}" response end end module UniquePort @port = 3211 def self.call @port += 1 @port end end 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 } end end if ENV['CI'] Minitest::Test.prepend TimeoutEveryTestCase require 'minitest/retry' Minitest::Retry.use! end module SkipTestsBasedOnRubyEngine # called with one or more params, like skip_on :jruby, :windows # optional suffix kwarg is appended to the skip message # optional suffix bt should generally not used def skip_on(*engs, suffix: '', bt: caller) skip_msg = false engs.each do |eng| skip_msg = case eng when :jruby then "Skipped on JRuby#{suffix}" if Puma.jruby? when :windows then "Skipped on Windows#{suffix}" if Puma.windows? when :appveyor then "Skipped on Appveyor#{suffix}" if ENV["APPVEYOR"] when :ci then "Skipped on ENV['CI']#{suffix}" if ENV["CI"] else false end skip skip_msg, bt if skip_msg end end # called with only one param def skip_unless(eng, bt: caller) skip_msg = case eng when :jruby then "Skip unless JRuby" unless Puma.jruby? when :windows then "Skip unless Windows" unless Puma.windows? else false end skip skip_msg, bt if skip_msg end end Minitest::Test.include SkipTestsBasedOnRubyEngine puma-3.12.4/test/rackup/000077500000000000000000000000001362626474300150245ustar00rootroot00000000000000puma-3.12.4/test/rackup/hello-bind.ru000066400000000000000000000001561362626474300174130ustar00rootroot00000000000000#\ -O bind=tcp://127.0.0.1:9292 run lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello World"]] } puma-3.12.4/test/rackup/hello-delay.ru000066400000000000000000000001301362626474300175650ustar00rootroot00000000000000sleep 10 run lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello World"]] } puma-3.12.4/test/rackup/hello-env.ru000066400000000000000000000001661362626474300172700ustar00rootroot00000000000000ENV["RAND"] ||= rand.to_s run lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello RAND #{ENV["RAND"]}"]] } puma-3.12.4/test/rackup/hello-map.ru000066400000000000000000000001421362626474300172470ustar00rootroot00000000000000map "/foo" do run lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello World"]] } end puma-3.12.4/test/rackup/hello-post.ru000066400000000000000000000001641362626474300174630ustar00rootroot00000000000000run lambda { |env| p :body => env['rack.input'].read [200, {"Content-Type" => "text/plain"}, ["Hello World"]] } puma-3.12.4/test/rackup/hello-stuck.ru000066400000000000000000000001301362626474300176200ustar00rootroot00000000000000run lambda { |env| sleep 60; [200, {"Content-Type" => "text/plain"}, ["Hello World"]] } puma-3.12.4/test/rackup/hello-tcp.ru000066400000000000000000000001561362626474300172650ustar00rootroot00000000000000run lambda { |env, socket| p :here socket.puts "Sockets for the low, low price of free!" socket.close } puma-3.12.4/test/rackup/hello.ru000066400000000000000000000001161362626474300164750ustar00rootroot00000000000000run lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello World"]] } puma-3.12.4/test/rackup/hijack.ru000066400000000000000000000001511362626474300166220ustar00rootroot00000000000000 run lambda { |env| io = env['rack.hijack'].call io.puts "HTTP/1.1 200\r\n\r\nBLAH" [-1, {}, []] } puma-3.12.4/test/rackup/hijack2.ru000066400000000000000000000001611362626474300167050ustar00rootroot00000000000000run lambda { |env| body = lambda { |io| io.puts "BLAH\n"; io.close } [200, { 'rack.hijack' => body }, []] } puma-3.12.4/test/rackup/lobster.ru000066400000000000000000000001071362626474300170440ustar00rootroot00000000000000require 'rack/lobster' use Rack::ShowExceptions run Rack::Lobster.new puma-3.12.4/test/rackup/many_long_headers.ru000066400000000000000000000003021362626474300210450ustar00rootroot00000000000000require 'securerandom' long_header_hash = {} 30.times do |i| long_header_hash["X-My-Header-#{i}"] = SecureRandom.hex(1000) end run lambda { |env| [200, long_header_hash, ["Hello World"]] } puma-3.12.4/test/rackup/realistic_response.ru000066400000000000000000000003741362626474300212750ustar00rootroot00000000000000require 'securerandom' long_header_hash = {} 25.times do |i| long_header_hash["X-My-Header-#{i}"] = SecureRandom.hex(25) end response = SecureRandom.hex(100_000) # A 100kb document run lambda { |env| [200, long_header_hash.dup, [response.dup]] } puma-3.12.4/test/rackup/slow.ru000066400000000000000000000001071362626474300163560ustar00rootroot00000000000000run lambda { |env| 30000000.times { } [200, {}, ["Hello World"]] } puma-3.12.4/test/shell/000077500000000000000000000000001362626474300146465ustar00rootroot00000000000000puma-3.12.4/test/shell/run.rb000066400000000000000000000002551362626474300160010ustar00rootroot00000000000000results = %w[t1 t2 t3].map do |test| system("ruby -rrubygems test/shell/#{test}.rb ") # > /dev/null 2>&1 end if results.any? { |r| r != true } exit 1 else exit 0 end puma-3.12.4/test/shell/t1.rb000066400000000000000000000005531362626474300155220ustar00rootroot00000000000000system "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 log = File.read("t1-stdout") File.unlink "t1-stdout" if File.file? "t1-stdout" File.unlink "t1-pid" if File.file? "t1-pid" if log =~ %r!GET / HTTP/1\.1! exit 0 else exit 1 end puma-3.12.4/test/shell/t1_conf.rb000066400000000000000000000000721362626474300165230ustar00rootroot00000000000000log_requests stdout_redirect "t1-stdout" pidfile "t1-pid" puma-3.12.4/test/shell/t2.rb000066400000000000000000000005571362626474300155270ustar00rootroot00000000000000system "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 stop" sleep 1 log = File.read("t2-stdout") File.unlink "t2-stdout" if File.file? "t2-stdout" if log =~ %r(GET / HTTP/1\.1) && !File.file?("t2-pid") exit 0 else exit 1 end puma-3.12.4/test/shell/t2_conf.rb000066400000000000000000000002451362626474300165260ustar00rootroot00000000000000log_requests stdout_redirect "t2-stdout" pidfile "t2-pid" bind "tcp://0.0.0.0:10103" rackup File.expand_path('../rackup/hello.ru', File.dirname(__FILE__)) daemonize puma-3.12.4/test/shell/t3.rb000066400000000000000000000013131362626474300155170ustar00rootroot00000000000000system "ruby -rrubygems -Ilib bin/puma -p 10102 -C test/shell/t3_conf.rb test/rackup/hello.ru &" sleep 5 worker_pid_was_present = File.file? "t3-worker-2-pid" system "kill `cat 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`" File.unlink "t3-pid" if File.file? "t3-pid" File.unlink "t3-worker-0-pid" if File.file? "t3-worker-0-pid" File.unlink "t3-worker-1-pid" if File.file? "t3-worker-1-pid" File.unlink "t3-worker-2-pid" if File.file? "t3-worker-2-pid" File.unlink "t3-worker-3-pid" if File.file? "t3-worker-3-pid" if worker_pid_was_present and worker_index_within_number_of_workers exit 0 else exit 1 end puma-3.12.4/test/shell/t3_conf.rb000066400000000000000000000001771362626474300165330ustar00rootroot00000000000000pidfile "t3-pid" workers 3 on_worker_boot do |index| File.open("t3-worker-#{index}-pid", "w") { |f| f.puts Process.pid } end puma-3.12.4/test/test_app_status.rb000066400000000000000000000032301362626474300173040ustar00rootroot00000000000000require_relative "helper" require "puma/app/status" require "rack" class TestAppStatus < Minitest::Test class FakeServer def initialize @status = :running @backlog = 0 @running = 0 end attr_reader :status attr_accessor :backlog, :running def stop @status = :stop end def halt @status = :halt end def stats "{}" end end def setup @server = FakeServer.new @app = Puma::App::Status.new(@server) @app.auth_token = nil end def lint(uri) app = Rack::Lint.new @app mock_env = Rack::MockRequest.env_for uri app.call mock_env end def test_bad_token @app.auth_token = "abcdef" status, _, _ = lint('/whatever') assert_equal 403, status end def test_good_token @app.auth_token = "abcdef" status, _, _ = lint('/whatever?token=abcdef') assert_equal 404, status end def test_unsupported status, _, _ = lint('/not-real') assert_equal 404, status end def test_stop status, _ , app = lint('/stop') assert_equal :stop, @server.status assert_equal 200, status assert_equal ['{ "status": "ok" }'], app.enum_for.to_a end def test_halt status, _ , app = lint('/halt') assert_equal :halt, @server.status assert_equal 200, status assert_equal ['{ "status": "ok" }'], app.enum_for.to_a end def test_stats @server.backlog = 1 @server.running = 9 status, _ , app = lint('/stats') assert_equal 200, status assert_equal ['{}'], app.enum_for.to_a end def test_alternate_location status, _ , _ = lint('__alternatE_location_/stats') assert_equal 200, status end end puma-3.12.4/test/test_binder.rb000066400000000000000000000034531362626474300163730ustar00rootroot00000000000000require_relative "helper" require "puma/binder" require "puma/puma_http11" class TestBinder < Minitest::Test def setup @events = Puma::Events.null @binder = Puma::Binder.new(@events) end def test_localhost_addresses_dont_alter_listeners_for_tcp_addresses skip_on :jruby @binder.parse(["tcp://localhost:10001"], @events) assert_equal [], @binder.listeners end def test_localhost_addresses_dont_alter_listeners_for_ssl_addresses skip_on :jruby key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__ @binder.parse(["ssl://localhost:10002?key=#{key}&cert=#{cert}"], @events) assert_equal [], @binder.listeners end def test_binder_parses_ssl_cipher_filter skip_on :jruby key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__ ssl_cipher_filter = "AES@STRENGTH" @binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}&ssl_cipher_filter=#{ssl_cipher_filter}"], @events) ssl = @binder.instance_variable_get(:@ios)[0] ctx = ssl.instance_variable_get(:@ctx) assert_equal(ssl_cipher_filter, ctx.ssl_cipher_filter) end def test_binder_parses_jruby_ssl_options skip_unless :jruby keystore = File.expand_path "../../examples/puma/keystore.jks", __FILE__ ssl_cipher_list = "TLS_DHE_RSA_WITH_DES_CBC_SHA,TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA" @binder.parse(["ssl://0.0.0.0:8080?keystore=#{keystore}&keystore-pass=&ssl_cipher_list=#{ssl_cipher_list}"], @events) ssl= @binder.instance_variable_get(:@ios)[0] ctx = ssl.instance_variable_get(:@ctx) assert_equal(keystore, ctx.keystore) assert_equal(ssl_cipher_list, ctx.ssl_cipher_list) end end puma-3.12.4/test/test_cli.rb000066400000000000000000000162271362626474300157020ustar00rootroot00000000000000require_relative "helper" require "puma/cli" class TestCLI < Minitest::Test def setup @environment = 'production' @tmp_file = Tempfile.new("puma-test") @tmp_path = @tmp_file.path @tmp_file.close! @tmp_path2 = "#{@tmp_path}2" File.unlink @tmp_path if File.exist? @tmp_path File.unlink @tmp_path2 if File.exist? @tmp_path2 @wait, @ready = IO.pipe @events = Puma::Events.strings @events.on_booted { @ready << "!" } end def wait_booted @wait.sysread 1 end def teardown File.unlink @tmp_path if File.exist? @tmp_path File.unlink @tmp_path2 if File.exist? @tmp_path2 @wait.close @ready.close end def test_pid_file cli = Puma::CLI.new ["--pidfile", @tmp_path] cli.launcher.write_pid assert_equal File.read(@tmp_path).strip.to_i, Process.pid end def test_control_for_tcp url = "tcp://127.0.0.1:9877/" cli = Puma::CLI.new ["-b", "tcp://127.0.0.1:9876", "--control", url, "--control-token", "", "test/rackup/lobster.ru"], @events t = Thread.new do Thread.current.abort_on_exception = true cli.run end wait_booted s = TCPSocket.new "127.0.0.1", 9877 s << "GET /stats HTTP/1.0\r\n\r\n" body = s.read assert_equal '{ "backlog": 0, "running": 0, "pool_capacity": 16, "max_threads": 16 }', body.split(/\r?\n/).last assert_equal '{ "backlog": 0, "running": 0, "pool_capacity": 16, "max_threads": 16 }', Puma.stats cli.launcher.stop t.join end def test_control_clustered skip_on :jruby, :windows, suffix: " - Puma::Binder::UNIXServer is not defined" url = "unix://#{@tmp_path}" cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}", "-t", "2:2", "-w", "2", "--control", url, "--control-token", "", "test/rackup/lobster.ru"], @events t = Thread.new { cli.run } t.abort_on_exception = true wait_booted sleep 2 s = UNIXSocket.new @tmp_path s << "GET /stats HTTP/1.0\r\n\r\n" body = s.read require 'json' status = JSON.parse(body.split("\n").last) assert_equal 2, status["workers"] # wait until the first status ping has come through sleep 6 s = UNIXSocket.new @tmp_path s << "GET /stats HTTP/1.0\r\n\r\n" body = s.read assert_match(/\{ "workers": 2, "phase": 0, "booted_workers": 2, "old_workers": 0, "worker_status": \[\{ "pid": \d+, "index": 0, "phase": 0, "booted": true, "last_checkin": "[^"]+", "last_status": \{ "backlog":0, "running":2, "pool_capacity":2, "max_threads": 2 \} \},\{ "pid": \d+, "index": 1, "phase": 0, "booted": true, "last_checkin": "[^"]+", "last_status": \{ "backlog":0, "running":2, "pool_capacity":2, "max_threads": 2 \} \}\] \}/, body.split("\r\n").last) cli.launcher.stop t.join end def test_control skip_on :jruby, :windows, suffix: " - Puma::Binder::UNIXServer is not defined" url = "unix://#{@tmp_path}" cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}", "--control", url, "--control-token", "", "test/rackup/lobster.ru"], @events t = Thread.new { cli.run } t.abort_on_exception = true wait_booted s = UNIXSocket.new @tmp_path s << "GET /stats HTTP/1.0\r\n\r\n" body = s.read assert_equal '{ "backlog": 0, "running": 0, "pool_capacity": 16, "max_threads": 16 }', body.split("\r\n").last cli.launcher.stop t.join end def test_control_stop skip_on :jruby, :windows, suffix: " - Puma::Binder::UNIXServer is not defined" url = "unix://#{@tmp_path}" cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}", "--control", url, "--control-token", "", "test/rackup/lobster.ru"], @events t = Thread.new { cli.run } t.abort_on_exception = true wait_booted s = UNIXSocket.new @tmp_path s << "GET /stop HTTP/1.0\r\n\r\n" body = s.read assert_equal '{ "status": "ok" }', body.split("\r\n").last t.join end def test_control_gc_stats skip_on :jruby, :windows, suffix: " - Puma::Binder::UNIXServer is not defined" url = "unix://#{@tmp_path}" cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}", "--control", url, "--control-token", "", "test/rackup/lobster.ru"], @events t = Thread.new { cli.run } t.abort_on_exception = true wait_booted s = UNIXSocket.new @tmp_path s << "GET /gc-stats HTTP/1.0\r\n\r\n" body = s.read s.close lines = body.split("\r\n") json_line = lines.detect { |l| l[0] == "{" } pairs = json_line.scan(/\"[^\"]+\": [^,]+/) gc_stats = {} pairs.each do |p| p =~ /\"([^\"]+)\": ([^,]+)/ || raise("Can't parse #{p.inspect}!") gc_stats[$1] = $2 end gc_count_before = gc_stats["count"].to_i s = UNIXSocket.new @tmp_path s << "GET /gc HTTP/1.0\r\n\r\n" body = s.read # Ignored s.close s = UNIXSocket.new @tmp_path s << "GET /gc-stats HTTP/1.0\r\n\r\n" body = s.read s.close lines = body.split("\r\n") json_line = lines.detect { |l| l[0] == "{" } pairs = json_line.scan(/\"[^\"]+\": [^,]+/) gc_stats = {} pairs.each do |p| p =~ /\"([^\"]+)\": ([^,]+)/ || raise("Can't parse #{p.inspect}!") gc_stats[$1] = $2 end gc_count_after = gc_stats["count"].to_i # Hitting the /gc route should increment the count by 1 assert_equal gc_count_before + 1, gc_count_after cli.launcher.stop t.join end def test_tmp_control skip_on :jruby url = "tcp://127.0.0.1:8232" cli = Puma::CLI.new ["--state", @tmp_path, "--control", "auto"] cli.launcher.write_state data = YAML.load File.read(@tmp_path) assert_equal Process.pid, data["pid"] url = data["control_url"] m = %r!unix://(.*)!.match(url) assert m, "'#{url}' is not a URL" end def test_state_file_callback_filtering skip_on :jruby, :windows, suffix: " - worker mode not supported" cli = Puma::CLI.new [ "--config", "test/config/state_file_testing_config.rb", "--state", @tmp_path ] cli.launcher.write_state data = YAML.load_file(@tmp_path) keys_not_stripped = data.keys & Puma::CLI::KEYS_NOT_TO_PERSIST_IN_STATE assert_empty keys_not_stripped end def test_state url = "tcp://127.0.0.1:8232" cli = Puma::CLI.new ["--state", @tmp_path, "--control", url] cli.launcher.write_state data = YAML.load File.read(@tmp_path) assert_equal Process.pid, data["pid"] assert_equal url, data["control_url"] end def test_load_path Puma::CLI.new ["--include", 'foo/bar'] assert_equal 'foo/bar', $LOAD_PATH[0] $LOAD_PATH.shift Puma::CLI.new ["--include", 'foo/bar:baz/qux'] assert_equal 'foo/bar', $LOAD_PATH[0] $LOAD_PATH.shift assert_equal 'baz/qux', $LOAD_PATH[0] $LOAD_PATH.shift end def test_environment ENV.delete 'RACK_ENV' Puma::CLI.new ["--environment", @environment] assert_equal ENV['RACK_ENV'], @environment end end puma-3.12.4/test/test_config.rb000066400000000000000000000102401362626474300163650ustar00rootroot00000000000000require_relative "helper" require "puma/configuration" class TestConfigFile < Minitest::Test def setup FileUtils.mkpath("config/puma") File.write("config/puma/fake-env.rb", "") end def test_app_from_rackup conf = Puma::Configuration.new do |c| c.rackup "test/rackup/hello-bind.ru" end conf.load conf.app assert_equal ["tcp://127.0.0.1:9292"], conf.options[:binds] end def test_app_from_app_DSL conf = Puma::Configuration.new do |c| c.load "test/config/app.rb" end conf.load app = conf.app assert_equal [200, {}, ["embedded app"]], app.call({}) end def test_double_bind_port port = (rand(10_000) + 30_000).to_s with_env("PORT" => port) do conf = Puma::Configuration.new do |user_config, file_config, default_config| user_config.bind "tcp://#{Puma::Configuration::DefaultTCPHost}:#{port}" file_config.load "test/config/app.rb" end conf.load assert_equal ["tcp://0.0.0.0:#{port}"], conf.options[:binds] end end def test_lowlevel_error_handler_DSL conf = Puma::Configuration.new do |c| c.load "test/config/app.rb" end conf.load app = conf.options[:lowlevel_error_handler] assert_equal [200, {}, ["error page"]], app.call({}) end def test_allow_users_to_override_default_options conf = Puma::Configuration.new(restart_cmd: 'bin/rails server') assert_equal 'bin/rails server', conf.options[:restart_cmd] end def test_overwrite_options conf = Puma::Configuration.new do |c| c.workers 3 end conf.load assert_equal conf.options[:workers], 3 conf.options[:workers] += 1 assert_equal conf.options[:workers], 4 end def test_explicit_config_files conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) do |c| end conf.load assert_match(/:3000$/, conf.options[:binds].first) end def test_parameters_overwrite_files conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) do |c| c.port 3030 end conf.load assert_match(/:3030$/, conf.options[:binds].first) assert_equal 3, conf.options[:min_threads] assert_equal 5, conf.options[:max_threads] end def test_config_files_default conf = Puma::Configuration.new do end assert_equal [nil], conf.config_files end def test_config_files_with_dash conf = Puma::Configuration.new(config_files: ['-']) do end assert_equal [], conf.config_files end def test_config_files_with_existing_path conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) do end assert_equal ['test/config/settings.rb'], conf.config_files end def test_config_files_with_non_existing_path conf = Puma::Configuration.new(config_files: ['test/config/typo/settings.rb']) do end assert_equal ['test/config/typo/settings.rb'], conf.config_files end def test_config_files_with_rack_env with_env('RACK_ENV' => 'fake-env') do conf = Puma::Configuration.new do end assert_equal ['config/puma/fake-env.rb'], conf.config_files end end def test_config_files_with_specified_environment conf = Puma::Configuration.new do end conf.options[:environment] = 'fake-env' assert_equal ['config/puma/fake-env.rb'], conf.config_files end def test_config_files_with_integer_convert conf = Puma::Configuration.new(config_files: ['test/config/with_integer_convert.rb']) do end conf.load assert_equal 6, conf.options[:persistent_timeout] assert_equal 3, conf.options[:first_data_timeout] assert_equal 2, conf.options[:workers] assert_equal 4, conf.options[:min_threads] assert_equal 8, conf.options[:max_threads] assert_equal 90, conf.options[:worker_timeout] assert_equal 120, conf.options[:worker_boot_timeout] assert_equal 150, conf.options[:worker_shutdown_timeout] end def teardown FileUtils.rm_r("config/puma") end private def with_env(env = {}) original_env = {} env.each do |k, v| original_env[k] = ENV[k] ENV[k] = v end yield ensure original_env.each do |k, v| ENV[k] = v end end end puma-3.12.4/test/test_events.rb000066400000000000000000000056141362626474300164350ustar00rootroot00000000000000require_relative "helper" class TestEvents < Minitest::Test def test_null events = Puma::Events.null assert_instance_of Puma::NullIO, events.stdout assert_instance_of Puma::NullIO, events.stderr assert_equal events.stdout, events.stderr end def test_strings events = Puma::Events.strings assert_instance_of StringIO, events.stdout assert_instance_of StringIO, events.stderr end def test_stdio events = Puma::Events.stdio assert_equal STDOUT, events.stdout assert_equal STDERR, events.stderr end def test_register_callback_with_block res = false events = Puma::Events.null events.register(:exec) { res = true } events.fire(:exec) assert_equal true, res end def test_register_callback_with_object obj = Object.new def obj.res @res || false end def obj.call @res = true end events = Puma::Events.null events.register(:exec, obj) events.fire(:exec) assert_equal true, obj.res end def test_fire_callback_with_multiple_arguments res = [] events = Puma::Events.null events.register(:exec) { |*args| res.concat(args) } events.fire(:exec, :foo, :bar, :baz) assert_equal [:foo, :bar, :baz], res end def test_on_booted_callback res = false events = Puma::Events.null events.on_booted { res = true } events.fire_on_booted! assert res end def test_log_writes_to_stdout out, _ = capture_io do Puma::Events.stdio.log("ready") end assert_equal "ready\n", out end def test_write_writes_to_stdout out, _ = capture_io do Puma::Events.stdio.write("ready") end assert_equal "ready", out end def test_debug_writes_to_stdout_if_env_is_present original_debug, ENV["PUMA_DEBUG"] = ENV["PUMA_DEBUG"], "1" out, _ = capture_io do Puma::Events.stdio.debug("ready") end assert_equal "% ready\n", out ensure ENV["PUMA_DEBUG"] = original_debug end def test_debug_not_write_to_stdout_if_env_is_not_present out, _ = capture_io do Puma::Events.stdio.debug("ready") end assert_empty out end def test_error_writes_to_stderr_and_exits did_exit = false _, err = capture_io do Puma::Events.stdio.error("interrupted") end assert_equal "ERROR: interrupted", err rescue SystemExit did_exit = true ensure assert did_exit end def test_pid_formatter pid = Process.pid out, _ = capture_io do events = Puma::Events.stdio events.formatter = Puma::Events::PidFormatter.new events.write("ready") end assert_equal "[#{ pid }] ready", out end def test_custom_log_formatter custom_formatter = proc { |str| "-> #{ str }" } out, _ = capture_io do events = Puma::Events.stdio events.formatter = custom_formatter events.write("ready") end assert_equal "-> ready", out end end puma-3.12.4/test/test_http10.rb000066400000000000000000000015111362626474300162410ustar00rootroot00000000000000require_relative "helper" require "puma/puma_http11" class Http10ParserTest < Minitest::Test def test_parse_simple parser = Puma::HttpParser.new req = {} http = "GET / HTTP/1.0\r\n\r\n" nread = parser.execute(req, http, 0) assert nread == http.length, "Failed to parse the full HTTP request" assert parser.finished?, "Parser didn't finish" assert !parser.error?, "Parser had error" assert nread == parser.nread, "Number read returned from execute does not match" assert_equal '/', req['REQUEST_PATH'] assert_equal 'HTTP/1.0', req['HTTP_VERSION'] assert_equal '/', req['REQUEST_URI'] assert_equal 'GET', req['REQUEST_METHOD'] assert_nil req['FRAGMENT'] assert_nil req['QUERY_STRING'] parser.reset assert parser.nread == 0, "Number read after reset should be 0" end end puma-3.12.4/test/test_http11.rb000066400000000000000000000126531362626474300162530ustar00rootroot00000000000000# Copyright (c) 2011 Evan Phoenix # Copyright (c) 2005 Zed A. Shaw require_relative "helper" require "puma/puma_http11" class Http11ParserTest < Minitest::Test def test_parse_simple parser = Puma::HttpParser.new req = {} http = "GET /?a=1 HTTP/1.1\r\n\r\n" nread = parser.execute(req, http, 0) assert nread == http.length, "Failed to parse the full HTTP request" assert parser.finished?, "Parser didn't finish" assert !parser.error?, "Parser had error" assert nread == parser.nread, "Number read returned from execute does not match" assert_equal '/', req['REQUEST_PATH'] assert_equal 'HTTP/1.1', req['HTTP_VERSION'] assert_equal '/?a=1', req['REQUEST_URI'] assert_equal 'GET', req['REQUEST_METHOD'] assert_nil req['FRAGMENT'] assert_equal "a=1", req['QUERY_STRING'] parser.reset assert parser.nread == 0, "Number read after reset should be 0" end def test_parse_escaping_in_query parser = Puma::HttpParser.new req = {} http = "GET /admin/users?search=%27%%27 HTTP/1.1\r\n\r\n" nread = parser.execute(req, http, 0) assert nread == http.length, "Failed to parse the full HTTP request" assert parser.finished?, "Parser didn't finish" assert !parser.error?, "Parser had error" assert nread == parser.nread, "Number read returned from execute does not match" assert_equal '/admin/users?search=%27%%27', req['REQUEST_URI'] assert_equal "search=%27%%27", req['QUERY_STRING'] parser.reset assert parser.nread == 0, "Number read after reset should be 0" end def test_parse_absolute_uri parser = Puma::HttpParser.new req = {} http = "GET http://192.168.1.96:3000/api/v1/matches/test?1=1 HTTP/1.1\r\n\r\n" nread = parser.execute(req, http, 0) assert nread == http.length, "Failed to parse the full HTTP request" assert parser.finished?, "Parser didn't finish" assert !parser.error?, "Parser had error" assert nread == parser.nread, "Number read returned from execute does not match" assert_equal "GET", req['REQUEST_METHOD'] assert_equal 'http://192.168.1.96:3000/api/v1/matches/test?1=1', req['REQUEST_URI'] assert_equal 'HTTP/1.1', req['HTTP_VERSION'] assert_nil req['REQUEST_PATH'] assert_nil req['FRAGMENT'] assert_nil req['QUERY_STRING'] parser.reset assert parser.nread == 0, "Number read after reset should be 0" end def test_parse_dumbfuck_headers parser = Puma::HttpParser.new req = {} should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n" nread = parser.execute(req, should_be_good, 0) assert_equal should_be_good.length, nread assert parser.finished? assert !parser.error? end def test_parse_error parser = Puma::HttpParser.new req = {} bad_http = "GET / SsUTF/1.1" error = false begin parser.execute(req, bad_http, 0) rescue error = true end assert error, "failed to throw exception" assert !parser.finished?, "Parser shouldn't be finished" assert parser.error?, "Parser SHOULD have error" end def test_fragment_in_uri parser = Puma::HttpParser.new req = {} get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n" parser.execute(req, get, 0) assert parser.finished? assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI'] assert_equal 'posts-17408', req['FRAGMENT'] end # lame random garbage maker def rand_data(min, max, readable=true) count = min + ((rand(max)+1) *10).to_i res = count.to_s + "/" if readable res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40) else res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20) end return res end def test_max_uri_path_length parser = Puma::HttpParser.new req = {} # Support URI path length to a max of 2048 path = "/" + rand_data(1000, 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) http = "GET #{path} HTTP/1.1\r\n\r\n" assert_raises Puma::HttpParserError do parser.execute(req, http, 0) parser.reset end end def test_horrible_queries parser = Puma::HttpParser.new # then that large header names are caught 10.times do |c| get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n" assert_raises Puma::HttpParserError do parser.execute({}, get, 0) parser.reset end end # then that large mangled field values are caught 10.times do |c| get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n" assert_raises Puma::HttpParserError do parser.execute({}, get, 0) parser.reset end end # then large headers are rejected too get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n" get += "X-Test: test\r\n" * (80 * 1024) assert_raises Puma::HttpParserError do parser.execute({}, get, 0) parser.reset end # finally just that random garbage gets blocked all the time 10.times do |c| get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n" assert_raises Puma::HttpParserError do parser.execute({}, get, 0) parser.reset end end end end puma-3.12.4/test/test_integration.rb000066400000000000000000000140751362626474300174550ustar00rootroot00000000000000require_relative "helper" require "puma/cli" require "puma/control_cli" # These don't run on travis because they're too fragile class TestIntegration < Minitest::Test def setup @state_path = "test/test_puma.state" @bind_path = "test/test_server.sock" @control_path = "test/test_control.sock" @token = "xxyyzz" @tcp_port = 9998 @server = nil @wait, @ready = IO.pipe @events = Puma::Events.strings @events.on_booted { @ready << "!" } end def teardown File.unlink @state_path rescue nil File.unlink @bind_path rescue nil File.unlink @control_path rescue nil @wait.close @ready.close if @server Process.kill "INT", @server.pid begin Process.wait @server.pid rescue Errno::ECHILD end @server.close end end def server(argv) base = "#{Gem.ruby} -Ilib bin/puma" base.prepend("bundle exec ") if defined?(Bundler) cmd = "#{base} -b tcp://127.0.0.1:#{@tcp_port} #{argv}" @server = IO.popen(cmd, "r") wait_for_server_to_boot @server end def start_forked_server(argv) pid = fork do exec "#{Gem.ruby} -I lib/ bin/puma -b tcp://127.0.0.1:#{@tcp_port} #{argv}" end sleep 5 pid end def stop_forked_server(pid) Process.kill(:TERM, pid) sleep 1 Process.wait2(pid) end def restart_server_and_listen(argv) skip_on :windows server(argv) s = connect initial_reply = read_body(s) restart_server(s) [initial_reply, read_body(connect)] end def signal(which) Process.kill which, @server.pid end def wait_booted @wait.sysread 1 end # reuses an existing connection to make sure that works def restart_server(connection) signal :USR2 connection.write "GET / HTTP/1.1\r\n\r\n" # trigger it to start by sending a new request wait_for_server_to_boot end def connect s = TCPSocket.new "localhost", @tcp_port s << "GET / HTTP/1.1\r\n\r\n" true until s.gets == "\r\n" s end def wait_for_server_to_boot true while @server.gets !~ /Ctrl-C/ # wait for server to say it booted end def read_body(connection) Timeout.timeout(10) do loop do response = connection.readpartial(1024) body = response.split("\r\n\r\n", 2).last return body if body && !body.empty? sleep 0.01 end end end def test_stop_via_pumactl skip_on :jruby, :windows conf = Puma::Configuration.new do |c| c.quiet c.state_path @state_path c.bind "unix://#{@bind_path}" c.activate_control_app "unix://#{@control_path}", :auth_token => @token c.rackup "test/rackup/hello.ru" end l = Puma::Launcher.new conf, :events => @events t = Thread.new do Thread.current.abort_on_exception = true l.run end wait_booted s = UNIXSocket.new @bind_path s << "GET / HTTP/1.0\r\n\r\n" assert_equal "Hello World", read_body(s) sout = StringIO.new ccli = Puma::ControlCLI.new %W!-S #{@state_path} stop!, sout ccli.run assert_kind_of Thread, t.join, "server didn't stop" end def test_phased_restart_via_pumactl skip_on :jruby, :windows, :ci, suffix: " - UNIX sockets are not recommended" conf = Puma::Configuration.new do |c| c.quiet c.state_path @state_path c.bind "unix://#{@bind_path}" c.activate_control_app "unix://#{@control_path}", :auth_token => @token c.workers 2 c.worker_shutdown_timeout 1 c.rackup "test/rackup/hello-stuck.ru" end l = Puma::Launcher.new conf, :events => @events t = Thread.new do Thread.current.abort_on_exception = true l.run end wait_booted s = UNIXSocket.new @bind_path s << "GET / HTTP/1.0\r\n\r\n" sout = StringIO.new # Phased restart ccli = Puma::ControlCLI.new ["-S", @state_path, "phased-restart"], sout ccli.run done = false until done @events.stdout.rewind log = @events.stdout.readlines.join("") if log =~ /- Worker \d \(pid: \d+\) booted, phase: 1/ assert_match(/TERM sent/, log) assert_match(/- Worker \d \(pid: \d+\) booted, phase: 1/, log) done = true end end # Stop ccli = Puma::ControlCLI.new ["-S", @state_path, "stop"], sout ccli.run assert_kind_of Thread, t.join, "server didn't stop" end def test_kill_unknown_via_pumactl skip_on :jruby, :windows # we run ls to get a 'safe' pid to pass off as puma in cli stop # do not want to accidently kill a valid other process io = IO.popen("ls") safe_pid = io.pid Process.wait safe_pid sout = StringIO.new e = assert_raises SystemExit do ccli = Puma::ControlCLI.new %W!-p #{safe_pid} stop!, sout ccli.run end sout.rewind assert_match(/No pid '\d+' found/, sout.readlines.join("")) assert_equal(1, e.status) end def test_restart_closes_keepalive_sockets _, new_reply = restart_server_and_listen("-q test/rackup/hello.ru") assert_equal "Hello World", new_reply end def test_restart_closes_keepalive_sockets_workers skip_on :jruby _, new_reply = restart_server_and_listen("-q -w 2 test/rackup/hello.ru") assert_equal "Hello World", new_reply end # It does not share environments between multiple generations, which would break Dotenv def test_restart_restores_environment # jruby has a bug where setting `nil` into the ENV or `delete` do not change the # next workers ENV skip_on :jruby initial_reply, new_reply = restart_server_and_listen("-q test/rackup/hello-env.ru") assert_includes initial_reply, "Hello RAND" assert_includes new_reply, "Hello RAND" refute_equal initial_reply, new_reply end def test_term_signal_exit_code_in_single_mode skip_on :jruby, :windows pid = start_forked_server("test/rackup/hello.ru") _, status = stop_forked_server(pid) assert_equal 15, status end def test_term_signal_exit_code_in_clustered_mode skip_on :jruby, :windows pid = start_forked_server("-w 2 test/rackup/hello.ru") _, status = stop_forked_server(pid) assert_equal 15, status end end puma-3.12.4/test/test_iobuffer.rb000066400000000000000000000014451362626474300167300ustar00rootroot00000000000000require_relative "helper" require "puma/io_buffer" class TestIOBuffer < Minitest::Test attr_accessor :iobuf def setup self.iobuf = Puma::IOBuffer.new end def test_initial_size assert_equal 0, iobuf.used assert iobuf.capacity > 0 end def test_append_op iobuf << "abc" assert_equal "abc", iobuf.to_s iobuf << "123" assert_equal "abc123", iobuf.to_s assert_equal 6, iobuf.used end def test_append expected = "mary had a little lamb" iobuf.append("mary", " ", "had ", "a little", " lamb") assert_equal expected, iobuf.to_s assert_equal expected.length, iobuf.used end def test_reset iobuf << "content" assert_equal "content", iobuf.to_s iobuf.reset assert_equal 0, iobuf.used assert_equal "", iobuf.to_s end end puma-3.12.4/test/test_minissl.rb000066400000000000000000000015451362626474300166060ustar00rootroot00000000000000require_relative "helper" require "puma/minissl" class TestMiniSSL < Minitest::Test if Puma.jruby? def test_raises_with_invalid_keystore_file ctx = Puma::MiniSSL::Context.new exception = assert_raises(ArgumentError) { ctx.keystore = "/no/such/keystore" } assert_equal("No such keystore file '/no/such/keystore'", exception.message) end else def test_raises_with_invalid_key_file ctx = Puma::MiniSSL::Context.new exception = assert_raises(ArgumentError) { ctx.key = "/no/such/key" } assert_equal("No such key file '/no/such/key'", exception.message) end def test_raises_with_invalid_cert_file ctx = Puma::MiniSSL::Context.new exception = assert_raises(ArgumentError) { ctx.cert = "/no/such/cert" } assert_equal("No such cert file '/no/such/cert'", exception.message) end end end puma-3.12.4/test/test_null_io.rb000066400000000000000000000014551362626474300165710ustar00rootroot00000000000000require_relative "helper" require "puma/null_io" class TestNullIO < Minitest::Test attr_accessor :nio def setup self.nio = Puma::NullIO.new end def test_eof_returns_true assert nio.eof? end def test_gets_returns_nil assert_nil nio.gets end def test_each_never_yields nio.each { raise "never yield" } end def test_read_with_no_arguments assert_equal "", nio.read end def test_read_with_nil_length assert_equal "", nio.read(nil) end def test_read_with_zero_length assert_equal "", nio.read(0) end def test_read_with_positive_integer_length assert_nil nio.read(1) end def test_read_with_length_and_buffer buf = "" assert_nil nio.read(1, buf) assert_equal "", buf end def test_size assert_equal 0, nio.size end end puma-3.12.4/test/test_persistent.rb000066400000000000000000000156371362626474300173370ustar00rootroot00000000000000require_relative "helper" class TestPersistent < Minitest::Test def setup @valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n" @close_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n" @http10_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n" @keep_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: Keep-Alive\r\n\r\n" @valid_post = "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nhello" @valid_no_body = "GET / HTTP/1.1\r\nHost: test.com\r\nX-Status: 204\r\nContent-Type: text/plain\r\n\r\n" @headers = { "X-Header" => "Works" } @body = ["Hello"] @inputs = [] @simple = lambda do |env| @inputs << env['rack.input'] status = Integer(env['HTTP_X_STATUS'] || 200) [status, @headers, @body] end @host = "127.0.0.1" @port = UniquePort.call @server = Puma::Server.new @simple @server.add_tcp_listener "127.0.0.1", @port @server.max_threads = 1 @server.run @client = TCPSocket.new "127.0.0.1", @port end def teardown @client.close @server.stop(true) end def lines(count, s=@client) str = "".dup Timeout.timeout(5) do count.times { str << s.gets } end str end def test_one_with_content_length @client << @valid_request sz = @body[0].size.to_s assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) end def test_two_back_to_back @client << @valid_request sz = @body[0].size.to_s assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) @client << @valid_request sz = @body[0].size.to_s assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) end def test_post_then_get @client << @valid_post sz = @body[0].size.to_s assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) @client << @valid_request sz = @body[0].size.to_s assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) end def test_no_body_then_get @client << @valid_no_body assert_equal "HTTP/1.1 204 No Content\r\nX-Header: Works\r\n\r\n", lines(3) @client << @valid_request sz = @body[0].size.to_s assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) end def test_chunked @body << "Chunked" @client << @valid_request assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n7\r\nChunked\r\n0\r\n\r\n", lines(10) end def test_chunked_with_empty_part @body << "" @body << "Chunked" @client << @valid_request assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n7\r\nChunked\r\n0\r\n\r\n", lines(10) end def test_no_chunked_in_http10 @body << "Chunked" @client << @http10_request assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\n\r\n", lines(3) assert_equal "HelloChunked", @client.read end def test_hex str = "This is longer and will be in hex" @body << str @client << @valid_request assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n#{str.size.to_s(16)}\r\n#{str}\r\n0\r\n\r\n", lines(10) end def test_client11_close @client << @close_request sz = @body[0].size.to_s assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nConnection: close\r\nContent-Length: #{sz}\r\n\r\n", lines(5) assert_equal "Hello", @client.read(5) end def test_client10_close @client << @http10_request sz = @body[0].size.to_s assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) end def test_one_with_keep_alive_header @client << @keep_request sz = @body[0].size.to_s assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nConnection: Keep-Alive\r\nContent-Length: #{sz}\r\n\r\n", lines(5) assert_equal "Hello", @client.read(5) end def test_persistent_timeout @server.persistent_timeout = 2 @client << @valid_request sz = @body[0].size.to_s assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) sleep 3 assert_raises EOFError do @client.read_nonblock(1) end end def test_app_sets_content_length @body = ["hello", " world"] @headers['Content-Length'] = "11" @client << @valid_request assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: 11\r\n\r\n", lines(4) assert_equal "hello world", @client.read(11) end def test_allow_app_to_chunk_itself @headers = {'Transfer-Encoding' => "chunked" } @body = ["5\r\nhello\r\n0\r\n\r\n"] @client << @valid_request assert_equal "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n", lines(7) end def test_two_requests_in_one_chunk @server.persistent_timeout = 3 req = @valid_request.to_s req += "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n" @client << req sz = @body[0].size.to_s assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) end def test_second_request_not_in_first_req_body @server.persistent_timeout = 3 req = @valid_request.to_s req += "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n" @client << req sz = @body[0].size.to_s assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4) assert_equal "Hello", @client.read(5) assert_kind_of Puma::NullIO, @inputs[0] assert_kind_of Puma::NullIO, @inputs[1] end def test_keepalive_doesnt_starve_clients sz = @body[0].size.to_s @client << @valid_request c2 = TCPSocket.new @host, @port c2 << @valid_request out = IO.select([c2], nil, nil, 1) assert out, "select returned nil" assert_equal c2, out.first.first assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4, c2) assert_equal "Hello", c2.read(5) end end puma-3.12.4/test/test_puma_server.rb000066400000000000000000000521201362626474300174530ustar00rootroot00000000000000require_relative "helper" class TestPumaServer < Minitest::Test def setup @port = 0 @host = "127.0.0.1" @app = lambda { |env| [200, {}, [env['rack.url_scheme']]] } @events = Puma::Events.new STDOUT, STDERR @server = Puma::Server.new @app, @events end def teardown @server.stop(true) end def server_run(app: @app, early_hints: false) @server.app = app @server.add_tcp_listener @host, @port @server.early_hints = true if early_hints @server.run end def header(sock) header = [] while true line = sock.gets break if line == "\r\n" header << line.strip end header end def send_http_and_read(req) port = @server.connected_port sock = TCPSocket.new @host, port sock << req sock.read end def test_proper_stringio_body data = nil @server.app = proc do |env| data = env['rack.input'].read [200, {}, ["ok"]] end @server.add_tcp_listener @host, @port @server.run fifteen = "1" * 15 sock = TCPSocket.new @host, @server.connected_port sock << "PUT / HTTP/1.0\r\nContent-Length: 30\r\n\r\n#{fifteen}" sleep 0.1 # important so that the previous data is sent as a packet sock << fifteen sock.read assert_equal "#{fifteen}#{fifteen}", data end def test_puma_socket body = "HTTP/1.1 750 Upgraded to Awesome\r\nDone: Yep!\r\n" @server.app = proc do |env| io = env['puma.socket'] io.write body io.close [-1, {}, []] end @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "PUT / HTTP/1.0\r\n\r\nHello" assert_equal body, sock.read end def test_very_large_return giant = "x" * 2056610 @server.app = proc do |env| [200, {}, [giant]] end @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.0\r\n\r\n" while true line = sock.gets break if line == "\r\n" end out = sock.read assert_equal giant.bytesize, out.bytesize end def test_respect_x_forwarded_proto @server.app = proc do |env| [200, {}, [env['SERVER_PORT']]] end @server.add_tcp_listener @host, @port @server.run req = Net::HTTP::Get.new("/") req['HOST'] = "example.com" req['X_FORWARDED_PROTO'] = "https" res = Net::HTTP.start @host, @server.connected_port do |http| http.request(req) end assert_equal "443", res.body end def test_default_server_port @server.app = proc do |env| [200, {}, [env['SERVER_PORT']]] end @server.add_tcp_listener @host, @port @server.run req = Net::HTTP::Get.new("/") req['HOST'] = "example.com" res = Net::HTTP.start @host, @server.connected_port do |http| http.request(req) end assert_equal "80", res.body end def test_HEAD_has_no_body @server.app = proc { |env| [200, {"Foo" => "Bar"}, ["hello"]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "HEAD / HTTP/1.0\r\n\r\n" data = sock.read assert_equal "HTTP/1.0 200 OK\r\nFoo: Bar\r\nContent-Length: 5\r\n\r\n", data end def test_GET_with_empty_body_has_sane_chunking @server.app = proc { |env| [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "HEAD / HTTP/1.0\r\n\r\n" data = sock.read assert_equal "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n", data end def test_early_hints_works @server.app = proc { |env| env['rack.early_hints'].call("Link" => "; rel=preload; as=style\n; rel=preload") [200, { "X-Hello" => "World" }, ["Hello world!"]] } @server.add_tcp_listener @host, @port @server.early_hints = true @server.run sock = TCPSocket.new @host, @server.connected_port sock << "HEAD / HTTP/1.0\r\n\r\n" data = sock.read expected_data = (<; rel=preload; as=style Link: ; rel=preload HTTP/1.0 200 OK X-Hello: World Content-Length: 12 EOF ).split("\n").join("\r\n") + "\r\n\r\n" assert_equal true, @server.early_hints assert_equal expected_data, data end def test_early_hints_is_off_by_default @server.app = proc { |env| assert_nil env['rack.early_hints'] [200, { "X-Hello" => "World" }, ["Hello world!"]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "HEAD / HTTP/1.0\r\n\r\n" data = sock.read expected_data = (< 'text', 'Location' => 'foo.html'}, ['302 found']] } @server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re} @server.app = proc { |e| raise "don't leak me bro" } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.0\r\n\r\n" data = sock.read assert_match(/HTTP\/1.0 302 Found/, data) end def test_leh_gets_env_as_well @events = Puma::Events.strings re = lambda { |err,env| env['REQUEST_PATH'] || raise("where is env?") [302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']] } @server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re} @server.app = proc { |e| raise "don't leak me bro" } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.0\r\n\r\n" data = sock.read assert_match(/HTTP\/1.0 302 Found/, data) end def test_custom_http_codes_10 @server.app = proc { |env| [449, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.0\r\n\r\n" data = sock.read assert_equal "HTTP/1.0 449 CUSTOM\r\nContent-Length: 0\r\n\r\n", data end def test_custom_http_codes_11 @server.app = proc { |env| [449, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n" data = sock.read assert_equal "HTTP/1.1 449 CUSTOM\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data end def test_HEAD_returns_content_headers @server.app = proc { |env| [200, {"Content-Type" => "application/pdf", "Content-Length" => "4242"}, []] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "HEAD / HTTP/1.0\r\n\r\n" data = sock.read assert_equal "HTTP/1.0 200 OK\r\nContent-Type: application/pdf\r\nContent-Length: 4242\r\n\r\n", data end def test_status_hook_fires_when_server_changes_states states = [] @events.register(:state) { |s| states << s } @server.app = proc { |env| [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "HEAD / HTTP/1.0\r\n\r\n" sock.read assert_equal [:booting, :running], states @server.stop(true) assert_equal [:booting, :running, :stop, :done], states end def test_timeout_in_data_phase @server.first_data_timeout = 2 @server.add_tcp_listener @host, @port @server.run client = TCPSocket.new @host, @server.connected_port client << "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\n" data = client.gets assert_equal "HTTP/1.1 408 Request Timeout\r\n", data end def test_http_11_keep_alive_with_body @server.app = proc { |env| [200, {"Content-Type" => "plain/text"}, ["hello\n"]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n" h = header(sock) body = sock.gets assert_equal ["HTTP/1.1 200 OK", "Content-Type: plain/text", "Content-Length: 6"], h assert_equal "hello\n", body sock.close end def test_http_11_close_with_body @server.app = proc { |env| [200, {"Content-Type" => "plain/text"}, ["hello"]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n" data = sock.read assert_equal "HTTP/1.1 200 OK\r\nContent-Type: plain/text\r\nConnection: close\r\nContent-Length: 5\r\n\r\nhello", data end def test_http_11_keep_alive_without_body @server.app = proc { |env| [204, {}, []] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n" h = header(sock) sock.close assert_equal ["HTTP/1.1 204 No Content"], h end def test_http_11_close_without_body @server.app = proc { |env| [204, {}, []] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n" h = header(sock) sock.close assert_equal ["HTTP/1.1 204 No Content", "Connection: close"], h end def test_http_10_keep_alive_with_body @server.app = proc { |env| [200, {"Content-Type" => "plain/text"}, ["hello\n"]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n" h = header(sock) body = sock.gets assert_equal ["HTTP/1.0 200 OK", "Content-Type: plain/text", "Connection: Keep-Alive", "Content-Length: 6"], h assert_equal "hello\n", body sock.close end def test_http_10_close_with_body @server.app = proc { |env| [200, {"Content-Type" => "plain/text"}, ["hello"]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.0\r\nConnection: close\r\n\r\n" data = sock.read assert_equal "HTTP/1.0 200 OK\r\nContent-Type: plain/text\r\nContent-Length: 5\r\n\r\nhello", data end def test_http_10_partial_hijack_with_content_length body_parts = ['abc', 'de'] @server.app = proc do |env| hijack_lambda = proc do | io | io.write(body_parts[0]) io.write(body_parts[1]) io.close end [200, {"Content-Length" => "5", 'rack.hijack' => hijack_lambda}, nil] end @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.0\r\nConnection: close\r\n\r\n" data = sock.read assert_equal "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nabcde", data end def test_http_10_keep_alive_without_body @server.app = proc { |env| [204, {}, []] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n" h = header(sock) assert_equal ["HTTP/1.0 204 No Content", "Connection: Keep-Alive"], h sock.close end def test_http_10_close_without_body @server.app = proc { |env| [204, {}, []] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.0\r\nConnection: close\r\n\r\n" data = sock.read assert_equal "HTTP/1.0 204 No Content\r\n\r\n", data end def test_Expect_100 @server.app = proc { |env| [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\nExpect: 100-continue\r\n\r\n" data = sock.read assert_equal "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data end def test_chunked_request body = nil @server.app = proc { |env| body = env['rack.input'].read [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n" data = sock.read assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data assert_equal "hello", body end def test_chunked_request_pause_before_value body = nil @server.app = proc { |env| body = env['rack.input'].read [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\n" sleep 1 sock << "h\r\n4\r\nello\r\n0\r\n" data = sock.read assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data assert_equal "hello", body end def test_chunked_request_pause_between_chunks body = nil @server.app = proc { |env| body = env['rack.input'].read [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n" sleep 1 sock << "4\r\nello\r\n0\r\n" data = sock.read assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data assert_equal "hello", body end def test_chunked_request_pause_mid_count body = nil @server.app = proc { |env| body = env['rack.input'].read [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r" sleep 1 sock << "\nh\r\n4\r\nello\r\n0\r\n" data = sock.read assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data assert_equal "hello", body end def test_chunked_request_pause_before_count_newline body = nil @server.app = proc { |env| body = env['rack.input'].read [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1" sleep 1 sock << "\r\nh\r\n4\r\nello\r\n0\r\n" data = sock.read assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data assert_equal "hello", body end def test_chunked_request_pause_mid_value body = nil @server.app = proc { |env| body = env['rack.input'].read [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\ne" sleep 1 sock << "llo\r\n0\r\n" data = sock.read assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data assert_equal "hello", body end def test_chunked_request_header_case body = nil @server.app = proc { |env| body = env['rack.input'].read [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: Chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n" data = sock.read assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data assert_equal "hello", body end def test_chunked_keep_alive body = nil @server.app = proc { |env| body = env['rack.input'].read [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n" h = header(sock) assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h assert_equal "hello", body sock.close end def test_chunked_keep_alive_two_back_to_back body = nil @server.app = proc { |env| body = env['rack.input'].read [200, {}, [""]] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n" h = header(sock) assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h assert_equal "hello", body sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ngood\r\n3\r\nbye\r\n0\r\n\r\n" sleep 0.1 h = header(sock) assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], h assert_equal "goodbye", body sock.close end def test_empty_header_values @server.app = proc { |env| [200, {"X-Empty-Header" => ""}, []] } @server.add_tcp_listener @host, @port @server.run sock = TCPSocket.new @host, @server.connected_port sock << "HEAD / HTTP/1.0\r\n\r\n" data = sock.read assert_equal "HTTP/1.0 200 OK\r\nX-Empty-Header: \r\n\r\n", data end # Rack may pass a newline in a header expecting us to split it. def test_newline_splits server_run app: ->(_) { [200, {'X-header' => "first line\nsecond line"}, ["Hello"]] } data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" assert_match "X-header: first line\r\nX-header: second line\r\n", data end def test_newline_splits_in_early_hint server_run early_hints: true, app: ->(env) do env['rack.early_hints'].call({'X-header' => "first line\nsecond line"}) [200, {}, ["Hello world!"]] end data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" assert_match "X-header: first line\r\nX-header: second line\r\n", data end # To comply with the Rack spec, we have to split header field values # containing newlines into multiple headers. def assert_does_not_allow_http_injection(app, opts = {}) server_run(early_hints: opts[:early_hints], app: app) data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n" refute_match(/[\r\n]Cookie: hack[\r\n]/, data) end # HTTP Injection Tests # # Puma should prevent injection of CR and LF characters into headers, either as # CRLF or CR or LF, because browsers may interpret it at as a line end and # allow untrusted input in the header to split the header or start the # response body. While it's not documented anywhere and they shouldn't be doing # it, Chrome and curl recognize a lone CR as a line end. According to RFC, # clients SHOULD interpret LF as a line end for robustness, and CRLF is the # specced line end. # # There are three different tests because there are three ways to set header # content in Puma. Regular (rack env), early hints, and a special case for # overriding content-length. {"cr" => "\r", "lf" => "\n", "crlf" => "\r\n"}.each do |suffix, line_ending| # The cr-only case for the following test was CVE-2020-5247 define_method("test_prevent_response_splitting_headers_#{suffix}") do app = ->(_) { [200, {'X-header' => "untrusted input#{line_ending}Cookie: hack"}, ["Hello"]] } assert_does_not_allow_http_injection(app) end define_method("test_prevent_response_splitting_headers_early_hint_#{suffix}") do app = ->(env) do env['rack.early_hints'].call("X-header" => "untrusted input#{line_ending}Cookie: hack") [200, {}, ["Hello"]] end assert_does_not_allow_http_injection(app, early_hints: true) end define_method("test_prevent_content_length_injection_#{suffix}") do app = ->(_) { [200, {'content-length' => "untrusted input#{line_ending}Cookie: hack"}, ["Hello"]] } assert_does_not_allow_http_injection(app) end end end puma-3.12.4/test/test_puma_server_ssl.rb000066400000000000000000000156321362626474300203430ustar00rootroot00000000000000require_relative "helper" require "puma/minissl" require "puma/puma_http11" #——————————————————————————————————————————————————————————————————————————————— # NOTE: ALL TESTS BYPASSED IF DISABLE_SSL IS TRUE #——————————————————————————————————————————————————————————————————————————————— class SSLEventsHelper < ::Puma::Events attr_accessor :addr, :cert, :error def ssl_error(server, peeraddr, peercert, error) self.addr = peeraddr self.cert = peercert self.error = error end end DISABLE_SSL = begin Puma::Server.class Puma::MiniSSL.check puts "", RUBY_DESCRIPTION puts "Puma::MiniSSL OPENSSL_LIBRARY_VERSION: #{Puma::MiniSSL::OPENSSL_LIBRARY_VERSION}", " OPENSSL_VERSION: #{Puma::MiniSSL::OPENSSL_VERSION}", "" rescue true else false end class TestPumaServerSSL < Minitest::Test def setup return if DISABLE_SSL port = UniquePort.call host = "127.0.0.1" app = lambda { |env| [200, {}, [env['rack.url_scheme']]] } ctx = Puma::MiniSSL::Context.new if Puma.jruby? ctx.keystore = File.expand_path "../../examples/puma/keystore.jks", __FILE__ ctx.keystore_pass = 'blahblah' else ctx.key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__ ctx.cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__ end ctx.verify_mode = Puma::MiniSSL::VERIFY_NONE @events = SSLEventsHelper.new STDOUT, STDERR @server = Puma::Server.new app, @events @ssl_listener = @server.add_ssl_listener host, port, ctx @server.run @http = Net::HTTP.new host, port @http.use_ssl = true @http.verify_mode = OpenSSL::SSL::VERIFY_NONE end def teardown return if DISABLE_SSL @http.finish if @http.started? @server.stop(true) end def test_url_scheme_for_https body = nil @http.start do req = Net::HTTP::Get.new "/", {} @http.request(req) do |rep| body = rep.body end end assert_equal "https", body end def test_very_large_return giant = "x" * 2056610 @server.app = proc do [200, {}, [giant]] end body = nil @http.start do req = Net::HTTP::Get.new "/" @http.request(req) do |rep| body = rep.body end end assert_equal giant.bytesize, body.bytesize end def test_form_submit body = nil @http.start do req = Net::HTTP::Post.new '/' req.set_form_data('a' => '1', 'b' => '2') @http.request(req) do |rep| body = rep.body end end assert_equal "https", body end def test_ssl_v3_rejection @http.ssl_version= :SSLv3 assert_raises(OpenSSL::SSL::SSLError) do @http.start do Net::HTTP::Get.new '/' end end unless Puma.jruby? assert_match(/wrong version number|no protocols available/, @events.error.message) if @events.error end end end unless DISABLE_SSL # client-side TLS authentication tests class TestPumaServerSSLClient < Minitest::Test def assert_ssl_client_error_match(error, subject=nil, &blk) port = 3212 host = "127.0.0.1" app = lambda { |env| [200, {}, [env['rack.url_scheme']]] } ctx = Puma::MiniSSL::Context.new if Puma.jruby? ctx.keystore = File.expand_path "../../examples/puma/client-certs/keystore.jks", __FILE__ ctx.keystore_pass = 'blahblah' else ctx.key = File.expand_path "../../examples/puma/client-certs/server.key", __FILE__ ctx.cert = File.expand_path "../../examples/puma/client-certs/server.crt", __FILE__ ctx.ca = File.expand_path "../../examples/puma/client-certs/ca.crt", __FILE__ end ctx.verify_mode = Puma::MiniSSL::VERIFY_PEER | Puma::MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT events = SSLEventsHelper.new STDOUT, STDERR server = Puma::Server.new app, events ssl_listener = server.add_ssl_listener host, port, ctx server.run http = Net::HTTP.new host, port http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE yield http client_error = false begin http.start do req = Net::HTTP::Get.new "/", {} http.request(req) end rescue OpenSSL::SSL::SSLError, EOFError client_error = true end sleep 0.1 assert_equal !!error, client_error # The JRuby MiniSSL implementation lacks error capturing currently, so we can't inspect the # messages here unless Puma.jruby? assert_match error, events.error.message if error assert_equal host, events.addr if error assert_equal subject, events.cert.subject.to_s if subject end ensure server.stop(true) end def test_verify_fail_if_no_client_cert return if DISABLE_SSL assert_ssl_client_error_match 'peer did not return a certificate' do |http| # nothing end end def test_verify_fail_if_client_unknown_ca return if DISABLE_SSL assert_ssl_client_error_match('self signed certificate in certificate chain', '/DC=net/DC=puma/CN=ca-unknown') do |http| key = File.expand_path "../../examples/puma/client-certs/client_unknown.key", __FILE__ crt = File.expand_path "../../examples/puma/client-certs/client_unknown.crt", __FILE__ http.key = OpenSSL::PKey::RSA.new File.read(key) http.cert = OpenSSL::X509::Certificate.new File.read(crt) http.ca_file = File.expand_path "../../examples/puma/client-certs/unknown_ca.crt", __FILE__ end end def test_verify_fail_if_client_expired_cert return if DISABLE_SSL assert_ssl_client_error_match('certificate has expired', '/DC=net/DC=puma/CN=client-expired') do |http| key = File.expand_path "../../examples/puma/client-certs/client_expired.key", __FILE__ crt = File.expand_path "../../examples/puma/client-certs/client_expired.crt", __FILE__ http.key = OpenSSL::PKey::RSA.new File.read(key) http.cert = OpenSSL::X509::Certificate.new File.read(crt) http.ca_file = File.expand_path "../../examples/puma/client-certs/ca.crt", __FILE__ end end def test_verify_client_cert return if DISABLE_SSL assert_ssl_client_error_match(nil) do |http| key = File.expand_path "../../examples/puma/client-certs/client.key", __FILE__ crt = File.expand_path "../../examples/puma/client-certs/client.crt", __FILE__ http.key = OpenSSL::PKey::RSA.new File.read(key) http.cert = OpenSSL::X509::Certificate.new File.read(crt) http.ca_file = File.expand_path "../../examples/puma/client-certs/ca.crt", __FILE__ http.verify_mode = OpenSSL::SSL::VERIFY_PEER end end end unless DISABLE_SSL puma-3.12.4/test/test_pumactl.rb000066400000000000000000000025551362626474300165770ustar00rootroot00000000000000require_relative "helper" require 'puma/control_cli' class TestPumaControlCli < Minitest::Test def setup # use a pipe to get info across thread boundary @wait, @ready = IO.pipe end def wait_booted line = @wait.gets until line =~ /Listening on/ end def teardown @wait.close @ready.close end def find_open_port server = TCPServer.new("127.0.0.1", 0) server.addr[1] ensure server.close end def test_config_file control_cli = Puma::ControlCLI.new ["--config-file", "test/config/state_file_testing_config.rb", "halt"] assert_equal "t3-pid", control_cli.instance_variable_get("@pidfile") end def test_control_url host = "127.0.0.1" port = find_open_port url = "tcp://#{host}:#{port}/" opts = [ "--control-url", url, "--control-token", "ctrl", "--config-file", "test/config/app.rb", ] control_cli = Puma::ControlCLI.new (opts + ["start"]), @ready, @ready t = Thread.new do Thread.current.abort_on_exception = true control_cli.run end wait_booted s = TCPSocket.new host, 9292 s << "GET / HTTP/1.0\r\n\r\n" body = s.read assert_match "200 OK", body assert_match "embedded app", body shutdown_cmd = Puma::ControlCLI.new(opts + ["halt"]) shutdown_cmd.run # TODO: assert something about the stop command t.join end end puma-3.12.4/test/test_rack_handler.rb000066400000000000000000000126441362626474300175470ustar00rootroot00000000000000require_relative "helper" require "rack/handler/puma" class TestHandlerGetStrSym < Minitest::Test def test_handler handler = Rack::Handler.get(:puma) assert_equal Rack::Handler::Puma, handler handler = Rack::Handler.get('Puma') assert_equal Rack::Handler::Puma, handler end end class TestPathHandler < Minitest::Test def app Proc.new {|env| @input = env; [200, {}, ["hello world"]]} end def setup @input = nil end def in_handler(app, options = {}) options[:Port] ||= 0 options[:Silent] = true @launcher = nil thread = Thread.new do Rack::Handler::Puma.run(app, options) do |s, p| @launcher = s end end thread.abort_on_exception = true # Wait for launcher to boot Timeout.timeout(10) do until @launcher sleep 1 end end sleep 1 yield @launcher ensure @launcher.stop if @launcher thread.join if thread end def test_handler_boots host = windows? ? "127.0.1.1" : "0.0.0.0" opts = { Host: host } in_handler(app, opts) do |launcher| hit(["http://#{host}:#{ launcher.connected_port }/test"]) assert_equal("/test", @input["PATH_INFO"]) end end end class TestUserSuppliedOptionsPortIsSet < Minitest::Test def setup @options = {} @options[:user_supplied_options] = [:Port] end def test_port_wins_over_config user_port = 5001 file_port = 6001 Dir.mktmpdir do |d| Dir.chdir(d) do FileUtils.mkdir("config") File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" } @options[:Port] = user_port conf = Rack::Handler::Puma.config(->{}, @options) conf.load assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds] end end end end class TestUserSuppliedOptionsHostIsSet < Minitest::Test def setup @options = {} @options[:user_supplied_options] = [:Host] end def test_host_uses_supplied_port_default user_port = rand(1000..9999) user_host = "123.456.789" @options[:Host] = user_host @options[:Port] = user_port conf = Rack::Handler::Puma.config(->{}, @options) conf.load assert_equal ["tcp://#{user_host}:#{user_port}"], conf.options[:binds] end end class TestUserSuppliedOptionsIsEmpty < Minitest::Test def setup @options = {} @options[:user_supplied_options] = [] end def test_config_file_wins_over_port user_port = 5001 file_port = 6001 Dir.mktmpdir do |d| Dir.chdir(d) do FileUtils.mkdir("config") File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" } @options[:Port] = user_port conf = Rack::Handler::Puma.config(->{}, @options) conf.load assert_equal ["tcp://0.0.0.0:#{file_port}"], conf.options[:binds] end end end def test_default_host_when_using_config_file user_port = 5001 file_port = 6001 Dir.mktmpdir do |d| Dir.chdir(d) do FileUtils.mkdir("config") File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" } @options[:Host] = "localhost" @options[:Port] = user_port conf = Rack::Handler::Puma.config(->{}, @options) conf.load assert_equal ["tcp://localhost:#{file_port}"], conf.options[:binds] end end end def test_default_host_when_using_config_file_with_explicit_host user_port = 5001 file_port = 6001 Dir.mktmpdir do |d| Dir.chdir(d) do FileUtils.mkdir("config") File.open("config/puma.rb", "w") { |f| f << "port #{file_port}, '1.2.3.4'" } @options[:Host] = "localhost" @options[:Port] = user_port conf = Rack::Handler::Puma.config(->{}, @options) conf.load assert_equal ["tcp://1.2.3.4:#{file_port}"], conf.options[:binds] end end end end class TestUserSuppliedOptionsIsNotPresent < Minitest::Test def setup @options = {} end def test_default_port_when_no_config_file conf = Rack::Handler::Puma.config(->{}, @options) conf.load assert_equal ["tcp://0.0.0.0:9292"], conf.options[:binds] end def test_config_wins_over_default file_port = 6001 Dir.mktmpdir do |d| Dir.chdir(d) do FileUtils.mkdir("config") File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" } conf = Rack::Handler::Puma.config(->{}, @options) conf.load assert_equal ["tcp://0.0.0.0:#{file_port}"], conf.options[:binds] end end end def test_user_port_wins_over_default_when_user_supplied_is_blank user_port = 5001 @options[:user_supplied_options] = [] @options[:Port] = user_port conf = Rack::Handler::Puma.config(->{}, @options) conf.load assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds] end def test_user_port_wins_over_default user_port = 5001 @options[:Port] = user_port conf = Rack::Handler::Puma.config(->{}, @options) conf.load assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds] end def test_user_port_wins_over_config user_port = 5001 file_port = 6001 Dir.mktmpdir do |d| Dir.chdir(d) do FileUtils.mkdir("config") File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" } @options[:Port] = user_port conf = Rack::Handler::Puma.config(->{}, @options) conf.load assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds] end end end end puma-3.12.4/test/test_rack_server.rb000066400000000000000000000047361362626474300174430ustar00rootroot00000000000000require_relative "helper" require "rack" class TestRackServer < Minitest::Test class ErrorChecker def initialize(app) @app = app @exception = nil @env = nil end attr_reader :exception, :env def call(env) begin @env = env return @app.call(env) rescue Exception => e @exception = e [ 500, { "X-Exception" => e.message, "X-Exception-Class" => e.class.to_s }, ["Error detected"] ] end end end class ServerLint < Rack::Lint def call(env) assert("No env given") { env } check_env env @app.call(env) end end def setup @valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n" @simple = lambda { |env| [200, { "X-Header" => "Works" }, ["Hello"]] } @server = Puma::Server.new @simple @server.add_tcp_listener "127.0.0.1", 0 @stopped = false end def stop @server.stop(true) @stopped = true end def teardown @server.stop(true) unless @stopped end def test_lint @checker = ErrorChecker.new ServerLint.new(@simple) @server.app = @checker @server.run hit(["http://127.0.0.1:#{ @server.connected_port }/test"]) stop if exc = @checker.exception raise exc end end def test_large_post_body @checker = ErrorChecker.new ServerLint.new(@simple) @server.app = @checker @server.run big = "x" * (1024 * 16) Net::HTTP.post_form URI.parse("http://127.0.0.1:#{ @server.connected_port }/test"), { "big" => big } stop if exc = @checker.exception raise exc end end def test_path_info input = nil @server.app = lambda { |env| input = env; @simple.call(env) } @server.run hit(["http://127.0.0.1:#{ @server.connected_port }/test/a/b/c"]) stop assert_equal "/test/a/b/c", input['PATH_INFO'] end def test_after_reply closed = false @server.app = lambda do |env| env['rack.after_reply'] << lambda { closed = true } @simple.call(env) end @server.run hit(["http://127.0.0.1:#{ @server.connected_port }/test"]) stop assert_equal true, closed end def test_common_logger log = StringIO.new logger = Rack::CommonLogger.new(@simple, log) @server.app = logger @server.run hit(["http://127.0.0.1:#{ @server.connected_port }/test"]) stop assert_match %r!GET /test HTTP/1\.1!, log.string end end puma-3.12.4/test/test_tcp_logger.rb000066400000000000000000000016371362626474300172570ustar00rootroot00000000000000require_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 puma-3.12.4/test/test_tcp_rack.rb000066400000000000000000000011251362626474300167100ustar00rootroot00000000000000require_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 puma-3.12.4/test/test_thread_pool.rb000066400000000000000000000072551362626474300174340ustar00rootroot00000000000000require_relative "helper" require "puma/thread_pool" class TestThreadPool < Minitest::Test def teardown @pool.shutdown if @pool end def new_pool(min, max, &block) block = proc { } unless block @pool = Puma::ThreadPool.new(min, max, &block) end def pause sleep 0.2 end def test_append_spawns saw = [] thread_name = nil pool = new_pool(0, 1) do |work| saw << work thread_name = Thread.current.name if Thread.current.respond_to?(:name) end pool << 1 pause assert_equal [1], saw assert_equal 1, pool.spawned # Thread name is new in Ruby 2.3 assert_equal('puma 001', thread_name) if Thread.current.respond_to?(:name) end def test_converts_pool_sizes pool = new_pool('0', '1') assert_equal 0, pool.spawned pool << 1 assert_equal 1, pool.spawned end def test_append_queues_on_max finish = false pool = new_pool(0, 1) { Thread.pass until finish } pool << 1 pool << 2 pool << 3 pause assert_equal 2, pool.backlog finish = true end def test_trim pool = new_pool(0, 1) pool << 1 pause assert_equal 1, pool.spawned pool.trim pause assert_equal 0, pool.spawned end def test_trim_leaves_min finish = false pool = new_pool(1, 2) { Thread.pass until finish } pool << 1 pool << 2 finish = true pause assert_equal 2, pool.spawned pool.trim pause assert_equal 1, pool.spawned pool.trim pause assert_equal 1, pool.spawned end def test_force_trim_doesnt_overtrim finish = false pool = new_pool(1, 2) { Thread.pass until finish } pool << 1 pool << 2 assert_equal 2, pool.spawned pool.trim true pool.trim true finish = true pause assert_equal 1, pool.spawned end def test_trim_is_ignored_if_no_waiting_threads finish = false pool = new_pool(1, 2) { Thread.pass until finish } pool << 1 pool << 2 assert_equal 2, pool.spawned pool.trim pool.trim assert_equal 0, pool.trim_requested finish = true pause end def test_autotrim finish = false pool = new_pool(1, 2) { Thread.pass until finish } pool << 1 pool << 2 assert_equal 2, pool.spawned finish = true pause assert_equal 2, pool.spawned pool.auto_trim! 1 sleep 1 pause assert_equal 1, pool.spawned end def test_cleanliness values = [] n = 100 mutex = Mutex.new finished = false pool = new_pool(1,1) { mutex.synchronize { values.push Thread.current[:foo] } Thread.current[:foo] = :hai Thread.pass until finished } pool.clean_thread_locals = true n.times { pool << 1 } finished = true pause assert_equal n, values.length assert_equal [], values.compact end def test_reap_only_dead_threads pool = new_pool(2,2) { Thread.current.kill } assert_equal 2, pool.spawned pool << 1 pause assert_equal 2, pool.spawned pool.reap assert_equal 1, pool.spawned pool << 2 pause assert_equal 1, pool.spawned pool.reap assert_equal 0, pool.spawned end def test_auto_reap_dead_threads pool = new_pool(2,2) { Thread.current.kill } assert_equal 2, pool.spawned pool << 1 pool << 2 pause assert_equal 2, pool.spawned pool.auto_reap! 1 sleep 1 pause assert_equal 0, pool.spawned end def test_force_shutdown_immediately pool = new_pool(1, 1) { begin sleep 1 rescue Puma::ThreadPool::ForceShutdown end } pool << 1 sleep 0.1 pool.shutdown(0) assert_equal 0, pool.spawned end end puma-3.12.4/test/test_unix_socket.rb000066400000000000000000000013351362626474300174600ustar00rootroot00000000000000require_relative "helper" class TestPumaUnixSocket < Minitest::Test App = lambda { |env| [200, {}, ["Works"]] } Path = "test/puma.sock" def setup # UNIX sockets are not recommended on JRuby or Windows skip_on :jruby, :windows, suffix: " - UNIX sockets are not recommended" @server = Puma::Server.new App @server.add_unix_listener Path @server.run end def teardown @server.stop(true) if @server File.unlink Path if File.exist? Path end def test_server sock = UNIXSocket.new Path sock << "GET / HTTP/1.0\r\nHost: blah.com\r\n\r\n" expected = "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nWorks" assert_equal expected, sock.read(expected.size) sock.close end end puma-3.12.4/test/test_web_server.rb000066400000000000000000000040671362626474300172750ustar00rootroot00000000000000# Copyright (c) 2011 Evan Phoenix # Copyright (c) 2005 Zed A. Shaw require_relative "helper" require "puma/server" class TestHandler attr_reader :ran_test def call(env) @ran_test = true [200, {"Content-Type" => "text/plain"}, ["hello!"]] end end class WebServerTest < Minitest::Test def setup @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n" @tester = TestHandler.new @server = Puma::Server.new @tester, Puma::Events.strings @server.add_tcp_listener "127.0.0.1", 0 @server.run end def teardown @server.stop(true) end def test_simple_server hit(["http://127.0.0.1:#{ @server.connected_port }/test"]) assert @tester.ran_test, "Handler didn't really run" end def do_test(string, chunk, close_after=nil, shutdown_delay=0) # Do not use instance variables here, because it needs to be thread safe socket = TCPSocket.new("127.0.0.1", @server.connected_port); request = StringIO.new(string) chunks_out = 0 while data = request.read(chunk) chunks_out += socket.write(data) socket.flush sleep 0.2 if close_after and chunks_out > close_after socket.close sleep 1 end end sleep(shutdown_delay) socket.write(" ") # Some platforms only raise the exception on attempted write socket.flush end def test_trickle_attack do_test(@valid_request, 3) end def test_close_client assert_raises IOError do do_test(@valid_request, 10, 20) end end def test_bad_client do_test("GET /test HTTP/BAD", 3) end def test_header_is_too_long long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n" assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do do_test(long, long.length/2, 10) end end def test_file_streamed_request body = "a" * (Puma::Const::MAX_BODY * 2) long = "GET /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body do_test(long, (Puma::Const::CHUNK_SIZE * 2) - 400) end end puma-3.12.4/tools/000077500000000000000000000000001362626474300137205ustar00rootroot00000000000000puma-3.12.4/tools/jungle/000077500000000000000000000000001362626474300152045ustar00rootroot00000000000000puma-3.12.4/tools/jungle/README.md000066400000000000000000000010531362626474300164620ustar00rootroot00000000000000# 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 puma-3.12.4/tools/jungle/init.d/000077500000000000000000000000001362626474300163715ustar00rootroot00000000000000puma-3.12.4/tools/jungle/init.d/README.md000066400000000000000000000041271362626474300176540ustar00rootroot00000000000000# 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 ``` puma-3.12.4/tools/jungle/init.d/puma000077500000000000000000000237751362626474300172770ustar00rootroot00000000000000#! /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 # # 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 isn't running, run it, otherwise restart it. if ps -p $PID > /dev/null; then do_start_one_do $1 else do_restart_one $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 "---> Puma $1 isn't running." else log_daemon_msg "---> About to kill PID `cat $PIDFILE`" if [ "$USE_LOCAL_BUNDLE" -eq 1 ]; then cd $1 && bundle exec pumactl --state $STATEFILE stop else pumactl --state $STATEFILE stop fi # Many daemons don't delete their pidfiles when they exit. rm -f $PIDFILE $STATEFILE 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, specifiy 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 : puma-3.12.4/tools/jungle/init.d/run-puma000077500000000000000000000011521362626474300200620ustar00rootroot00000000000000#!/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 puma-3.12.4/tools/jungle/rc.d/000077500000000000000000000000001362626474300160325ustar00rootroot00000000000000puma-3.12.4/tools/jungle/rc.d/README.md000066400000000000000000000032541362626474300173150ustar00rootroot00000000000000# Puma as a service using rc.d Manage multilpe Puma servers as services on one box using FreeBSD's rc.d service. ## Dependencies * `jq` - a command-line json parser is needed to parse the json in the config file ## Installation # Copy the puma script to the rc.d directory (make sure everyone has read/execute perms) sudo cp puma /usr/local/etc/rc.d/ # Create an empty configuration file sudo touch /usr/local/etc/puma.conf # Enable the puma service sudo echo 'puma_enable="YES"' >> /etc/rc.conf ## Managing the jungle Puma apps are referenced in /usr/local/etc/puma.conf by default. Start the jungle running: `service puma start` This script will run at boot time. You can also stop the jungle (stops ALL puma instances) by running: `service puma stop` To restart the jungle: `service puma restart` ## Conventions * The script expects: * a config file to exist under `config/puma.rb` in your app. E.g.: `/home/apps/my-app/config/puma.rb`. You can always change those defaults by editing the scripts. ## Here's what a minimal app's config file should have ``` { "servers" : [ { "dir": "/path/to/rails/project", "user": "deploy-user", "ruby_version": "ruby.version", "ruby_env": "rbenv" } ] } ``` ## Before starting... You need to customise `puma.conf` to: * Set the right user your app should be running on unless you want root to execute it! * Set the directory of the app * Set the ruby version to execute * Set the ruby environment (currently set to rbenv, since that is the only ruby environment currently supported) * Add additional server instances following the scheme in the example ## Notes: Only rbenv is currently supported. puma-3.12.4/tools/jungle/rc.d/puma000077500000000000000000000030571362626474300167270ustar00rootroot00000000000000#!/bin/sh # # PROVIDE: puma . /etc/rc.subr name="puma" start_cmd="puma_start" stop_cmd="puma_stop" restart_cmd="puma_restart" rcvar=puma_enable required_files=/usr/local/etc/puma.conf puma_start() { server_count=$(/usr/local/bin/jq ".servers[] .ruby_env" /usr/local/etc/puma.conf | wc -l) i=0 while [ "$i" -lt "$server_count" ]; do rb_env=$(/usr/local/bin/jq -r ".servers[$i].ruby_env" /usr/local/etc/puma.conf) dir=$(/usr/local/bin/jq -r ".servers[$i].dir" /usr/local/etc/puma.conf) user=$(/usr/local/bin/jq -r ".servers[$i].user" /usr/local/etc/puma.conf) rb_ver=$(/usr/local/bin/jq -r ".servers[$i].ruby_version" /usr/local/etc/puma.conf) case $rb_env in "rbenv") su - $user -c "cd $dir && rbenv shell $rb_ver && bundle exec puma -C $dir/config/puma.rb -d" ;; *) ;; esac i=$(( i + 1 )) done } puma_stop() { pkill ruby } puma_restart() { server_count=$(/usr/local/bin/jq ".servers[] .ruby_env" /usr/local/etc/puma.conf | wc -l) i=0 while [ "$i" -lt "$server_count" ]; do rb_env=$(/usr/local/bin/jq -r ".servers[$i].ruby_env" /usr/local/etc/puma.conf) dir=$(/usr/local/bin/jq -r ".servers[$i].dir" /usr/local/etc/puma.conf) user=$(/usr/local/bin/jq -r ".servers[$i].user" /usr/local/etc/puma.conf) rb_ver=$(/usr/local/bin/jq -r ".servers[$i].ruby_version" /usr/local/etc/puma.conf) case $rb_env in "rbenv") su - $user -c "cd $dir && pkill ruby && rbenv shell $ruby_version && bundle exec puma -C $dir/config/puma.rb -d" ;; *) ;; esac i=$(( i + 1 )) done } load_rc_config $name run_rc_command "$1" puma-3.12.4/tools/jungle/rc.d/puma.conf000066400000000000000000000002261362626474300176430ustar00rootroot00000000000000{ "servers" : [ { "dir": "/path/to/rails/project", "user": "deploy-user", "ruby_version": "ruby.version", "ruby_env": "rbenv" } ] } puma-3.12.4/tools/jungle/upstart/000077500000000000000000000000001362626474300167065ustar00rootroot00000000000000puma-3.12.4/tools/jungle/upstart/README.md000066400000000000000000000035611362626474300201720ustar00rootroot00000000000000# Puma as a service using Upstart Manage multiple Puma servers as services on the same box using Ubuntu upstart. ## Installation # Copy the scripts to services directory sudo cp puma.conf puma-manager.conf /etc/init # Create an empty configuration file sudo touch /etc/puma.conf ## Managing the jungle Puma apps are referenced in /etc/puma.conf by default. Add each app's path as a new line, e.g.: ``` /home/apps/my-cool-ruby-app /home/apps/another-app/current ``` Start the jungle running: `sudo start puma-manager` This script will run at boot time. Start a single puma like this: `sudo start puma app=/path/to/app` ## Logs Everything is logged by upstart, defaulting to `/var/log/upstart`. Each puma instance is named after its directory, so for an app called `/home/apps/my-app` the log file would be `/var/log/upstart/puma-_home_apps_my-app.log`. ## Conventions * The script expects: * a config file to exist under `config/puma.rb` in your app. E.g.: `/home/apps/my-app/config/puma.rb`. * a temporary folder to put the PID, socket and state files to exist called `tmp/puma`. E.g.: `/home/apps/my-app/tmp/puma`. Puma will take care of the files for you. You can always change those defaults by editing the scripts. ## 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 ``` ## Before starting... You need to customise `puma.conf` to: * Set the right user your app should be running on unless you want root to execute it! * Look for `setuid apps` and `setgid apps`, uncomment those lines and replace `apps` to whatever your deployment user is. * Replace `apps` on the paths (or set the right paths to your user's home) everywhere else. * Uncomment the source lines for `rbenv` or `rvm` support unless you use a system wide installation of Ruby. puma-3.12.4/tools/jungle/upstart/puma-manager.conf000066400000000000000000000015621362626474300221330ustar00rootroot00000000000000# /etc/init/puma-manager.conf - manage a set of Pumas # This example config should work with Ubuntu 12.04+. It # allows you to manage multiple Puma instances with # Upstart, Ubuntu's native service management tool. # # See puma.conf for how to manage a single Puma instance. # # Use "stop puma-manager" to stop all Puma instances. # Use "start puma-manager" to start all instances. # Use "restart puma-manager" to restart all instances. # Crazy, right? # description "Manages the set of puma processes" # This starts upon bootup and stops on shutdown start on runlevel [2345] stop on runlevel [06] # Set this to the number of Puma processes you want # to run on this machine env PUMA_CONF="/etc/puma.conf" pre-start script for i in `cat $PUMA_CONF`; do app=`echo $i | cut -d , -f 1` logger -t "puma-manager" "Starting $app" start puma app=$app done end script puma-3.12.4/tools/jungle/upstart/puma.conf000066400000000000000000000040341362626474300205200ustar00rootroot00000000000000# /etc/init/puma.conf - Puma config # This example config should work with Ubuntu 12.04+. It # allows you to manage multiple Puma instances with # Upstart, Ubuntu's native service management tool. # # See puma-manager.conf for how to manage all Puma instances at once. # # Save this config as /etc/init/puma.conf then manage puma with: # sudo start puma app=PATH_TO_APP # sudo stop puma app=PATH_TO_APP # sudo status puma app=PATH_TO_APP # # or use the service command: # sudo service puma {start,stop,restart,status} # description "Puma Background Worker" # no "start on", we don't want to automatically start stop on (stopping puma-manager or runlevel [06]) # change apps to match your deployment user if you want to use this as a less privileged user (recommended!) setuid apps setgid apps respawn respawn limit 3 30 instance ${app} script # this script runs in /bin/sh by default # respawn as bash so we can source in rbenv/rvm # quoted heredoc to tell /bin/sh not to interpret # variables # source ENV variables manually as Upstart doesn't, eg: #. /etc/environment exec /bin/bash <<'EOT' # set HOME to the setuid user's home, there doesn't seem to be a better, portable way export HOME="$(eval echo ~$(id -un))" if [ -d "/usr/local/rbenv/bin" ]; then export PATH="/usr/local/rbenv/bin:/usr/local/rbenv/shims:$PATH" elif [ -d "$HOME/.rbenv/bin" ]; then export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH" 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" 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 cd $app logger -t puma "Starting server: $app" exec bundle exec puma -C config/puma.rb EOT end script puma-3.12.4/tools/trickletest.rb000066400000000000000000000016251362626474300166060ustar00rootroot00000000000000require 'socket' require 'stringio' def do_test(st, chunk) s = TCPSocket.new('127.0.0.1',ARGV[0].to_i); req = StringIO.new(st) nout = 0 randstop = rand(st.length / 10) STDERR.puts "stopping after: #{randstop}" begin while data = req.read(chunk) nout += s.write(data) s.flush sleep 0.1 if nout > randstop STDERR.puts "BANG! after #{nout} bytes." break end end rescue Object => e STDERR.puts "ERROR: #{e}" ensure s.close end end content = "-" * (1024 * 240) st = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\nContent-Length: #{content.length}\r\n\r\n#{content}" puts "length: #{content.length}" threads = [] ARGV[1].to_i.times do t = Thread.new do size = 100 puts ">>>> #{size} sized chunks" do_test(st, size) end t.abort_on_exception = true threads << t end threads.each {|t| t.join} puma-3.12.4/win_gem_test/000077500000000000000000000000001362626474300152445ustar00rootroot00000000000000puma-3.12.4/win_gem_test/Rakefile_wintest000066400000000000000000000003151362626474300204650ustar00rootroot00000000000000# rake -f Rakefile_wintest -N -R norakelib require "rake/testtask" Rake::TestTask.new(:win_test) do |t| t.libs << "test" t.warning = false t.options = '--verbose' end task :default => [:win_test] puma-3.12.4/win_gem_test/package_gem.rb000066400000000000000000000011241362626474300200120ustar00rootroot00000000000000# frozen_string_literal: true require 'rubygems' require 'rubygems/package' spec = Gem::Specification.load("./puma.gemspec") spec.files.concat ['Rakefile_wintest', 'lib/puma/puma_http11.rb'] spec.files.concat Dir['lib/**/*.so'] spec.test_files = Dir['{examples,test}/**/*.*'] # below lines are required and not gem specific spec.platform = ARGV[0] spec.required_ruby_version = [">= #{ARGV[1]}", "< #{ARGV[2]}"] spec.extensions = [] if spec.respond_to?(:metadata=) spec.metadata.delete("msys2_mingw_dependencies") spec.metadata['commit'] = ENV['commit_info'] end Gem::Package.build(spec) puma-3.12.4/win_gem_test/puma.ps1000066400000000000000000000055711362626474300166430ustar00rootroot00000000000000# PowerShell script for building & testing SQLite3-Ruby fat binary gem # Code by MSP-Greg, see https://github.com/MSP-Greg/av-gem-build-test # load utility functions, pass 64 or 32 . $PSScriptRoot\shared\appveyor_setup.ps1 $args[0] if ($LastExitCode) { exit } # above is required code #———————————————————————————————————————————————————————————————— above for all repos Make-Const gem_name 'puma' Make-Const repo_name 'puma' Make-Const url_repo 'https://github.com/puma/puma.git' #———————————————————————————————————————————————————————————————— lowest ruby version Make-Const ruby_vers_low 22 # null = don't compile; false = compile, ignore test (allow failure); # true = compile & test Make-Const trunk $false ; Make-Const trunk_x64 $false Make-Const trunk_JIT $null ; Make-Const trunk_x64_JIT $null #———————————————————————————————————————————————————————————————— make info Make-Const dest_so 'lib\puma' Make-Const exts @( @{ 'conf' = 'ext/puma_http11/extconf.rb' ; 'so' = 'puma_http11' } ) Make-Const write_so_require $true #———————————————————————————————————————————————————————————————— Pre-Compile # runs before compiling starts on every ruby version function Pre-Compile { # load the correct OpenSSL version in the build system Check-OpenSSL Write-Host Compiling With $env:SSL_VERS } #———————————————————————————————————————————————————————————————— Run-Tests function Run-Tests { # call with comma separated list of gems to install or update Update-Gems minitest, minitest-retry, rack, rake $env:CI = 1 rake -f Rakefile_wintest -N -R norakelib | Set-Content -Path $log_name -PassThru -Encoding UTF8 # add info after test results $(ruby -ropenssl -e "STDOUT.puts $/ + OpenSSL::OPENSSL_LIBRARY_VERSION") | Add-Content -Path $log_name -PassThru -Encoding UTF8 minitest # collects test results } #———————————————————————————————————————————————————————————————— below for all repos # below is required code Make-Const dir_gem $(Convert-Path $PSScriptRoot\..) Make-Const dir_ps $PSScriptRoot Push-Location $PSScriptRoot .\shared\make.ps1 .\shared\test.ps1 Pop-Location exit $ttl_errors_fails + $exit_code