diff --git a/Gemfile b/Gemfile index 44f6e48b80..24fbed33cd 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,7 @@ gem "nio4r", "~> 2.0" gem "rack", "< 3.0" gem "minitest", "~> 5.11" gem "minitest-retry" +gem "minitest-proveit" gem "jruby-openssl", :platform => "jruby" diff --git a/History.md b/History.md index 2a9416fc57..f2da4d47ee 100644 --- a/History.md +++ b/History.md @@ -1,12 +1,16 @@ ## Master -x features +* Features + * Add log_formatter configuration #1816 + +* Bugfixes + * Your bugfix goes here (#Github Number) + +## 4.0.1 / 2019-07-11 * 2 bugfixes - * Socket removed after reload (#1829) - * Add extconf tests for DTLS_method & TLS_server_method, use in minissl.rb. (#1832) -* 1 feature - * Add log_formatter configuration + * Fix socket removed after reload - should fix problems with systemd socket activation. (#1829) + * Add extconf tests for DTLS_method & TLS_server_method, use in minissl.rb. Should fix "undefined symbol: DTLS_method" when compiling against old OpenSSL versions. (#1832) ## 4.0.0 / 2019-06-25 @@ -1439,3 +1443,12 @@ be added back in a future date when a java Puma::MiniSSL is added. ## 1.0.0 / 2012-03-29 * Released! + +## Ignore - this is for maintainers to copy-paste during release +## Master + +* Features + * Your feature goes here (#Github Number) + +* Bugfixes + * Your bugfix goes here (#Github Number) diff --git a/README.md b/README.md index 3bd0c5e6b8..ac75ef1143 100644 --- a/README.md +++ b/README.md @@ -10,36 +10,38 @@ [![Code Climate](https://codeclimate.com/github/puma/puma.svg)](https://codeclimate.com/github/puma/puma) [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=puma&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=puma&package-manager=bundler&version-scheme=semver) -Puma is a **simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications** in development and production. +Puma is a **simple, fast, multi-threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications**. ## 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 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 using a thread pool. Each request is served in a separate thread, so truly concurrent Ruby implementations (JRuby, Rubinius) will use all available CPU cores. Puma was designed to be the go-to server for [Rubinius](https://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. +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 IO waiting to be done in parallel. ## Quick Start ``` $ gem install puma -$ puma +$ puma ``` +Without arguments, puma will look for a rackup (.ru) file in the current working directory called `config.ru`. + ## Frameworks ### Rails -Puma is the default server for Rails, and should already be included in your Gemfile. +Puma is the default server for Rails, included in the generated Gemfile. -Then start your server with the `rails` command: +Start your server with the `rails` command: ``` -$ rails s +$ rails server ``` -Many configuration options are not available when using `rails s`. It is recommended that you use Puma's executable instead: +Many configuration options and Puma features are not available when using `rails server`. It is recommended that you use Puma's executable instead: ``` $ bundle exec puma @@ -53,7 +55,7 @@ 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: +Or you can configure your Sinatra application to always use Puma: ```ruby require 'sinatra' @@ -72,9 +74,9 @@ Puma uses a thread pool. You can set the minimum and maximum number of threads t $ 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). +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 cause contention for the Global VM Lock, when using MRI). -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. +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 @@ -84,9 +86,9 @@ Puma also offers "clustered mode". Clustered mode `fork`s workers from a master $ 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. +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, with 16 in each worker process. -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: +In clustered mode, Puma can "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 @@ -111,8 +113,7 @@ 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. +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: @@ -125,7 +126,7 @@ on_worker_boot do end ``` -On top of that, you can specify a block in your configuration file that will be run before workers are forked: +`before_fork` specifies a block to be run before workers are forked: ```ruby # config/puma.rb @@ -138,13 +139,13 @@ Preloading can’t be used with phased restart, since phased restart kills and r ### 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: +Bind Puma to a socket 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)? +To use a UNIX Socket instead of TCP: ``` $ puma -b unix:///var/run/puma.sock @@ -157,13 +158,14 @@ $ 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. +To use or avoid specific SSL cipher suites, use `ssl_cipher_filter` or `ssl_cipher_list` options. ##### Ruby: @@ -179,7 +181,7 @@ $ puma -b 'ssl://127.0.0.1:9292?keystore=path_to_keystore&keystore-pass=keystore See https://www.openssl.org/docs/man1.0.2/apps/ciphers.html for cipher filter format and full list of cipher suites. -Don't want to use insecure TLSv1.0 ? +Disable TLS v1 with the `no_tlsv1` option: ``` $ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&no_tlsv1=true' @@ -187,13 +189,13 @@ $ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&no_tlsv1=true' ### Control/Status Server -Puma has a built-in status/control app that can be used to query and control Puma itself. +Puma has a built-in status and control app that can be used to query and control Puma. ``` $ 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. +Puma will start the control server on localhost port 9293. All requests to the control server will need to include control token (in this case, `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 status app has available. You can also interact with the control server via `pumactl`. This command will restart Puma: @@ -205,13 +207,13 @@ 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: +You can also provide a configuration file 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 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, Puma looks for configuration at `config/puma/.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: @@ -236,7 +238,7 @@ Puma responds to several signals. A detailed guide to using UNIX signals with Pu 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). + * **Windows**: Cluster mode is not supported due to a lack of fork(2). ## Known Bugs @@ -278,6 +280,24 @@ $ bundle install $ bundle exec rake ``` +To run a single test file, use the `TEST` environment variable: + +```bash +$ TEST=test/test_binder.rb bundle exec rake test +``` + +Or use [`m`](https://github.com/qrush/m): + +``` +$ bundle exec m test/test_binder.rb +``` + +Which can also be used to run a single test case: + +``` +$ bundle exec m test/test_binder.rb:37 +``` + ## License Puma is copyright Evan Phoenix and contributors, licensed under the BSD 3-Clause license. See the included LICENSE file for details. diff --git a/Rakefile b/Rakefile index f56dce4a4f..231be50b12 100644 --- a/Rakefile +++ b/Rakefile @@ -7,14 +7,12 @@ require_relative 'lib/puma/detect' require 'rubygems/package_task' require 'bundler/gem_tasks' -gemspec = Gem::Specification.load(Dir['*.gemspec'].first) +gemspec = Gem::Specification.load("puma.gemspec") 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 @@ -40,16 +38,16 @@ 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| + Rake::ExtensionTask.new("puma_http11", gemspec) 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 + 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| + Rake::JavaExtensionTask.new("puma_http11", gemspec) do |ext| ext.lib_dir = "lib/puma" end end @@ -75,6 +73,7 @@ end namespace :test do desc "Run the integration tests" + task :integration do sh "ruby test/shell/run.rb" end diff --git a/lib/puma.rb b/lib/puma.rb index 7a65d1256f..c48e352fdf 100644 --- a/lib/puma.rb +++ b/lib/puma.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Standard libraries require 'socket' require 'tempfile' diff --git a/lib/puma/accept_nonblock.rb b/lib/puma/accept_nonblock.rb index a8d67673f2..e697032a2e 100644 --- a/lib/puma/accept_nonblock.rb +++ b/lib/puma/accept_nonblock.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'openssl' module OpenSSL diff --git a/lib/puma/app/status.rb b/lib/puma/app/status.rb index 58615d7db1..84b3965415 100644 --- a/lib/puma/app/status.rb +++ b/lib/puma/app/status.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'json' module Puma diff --git a/lib/puma/configuration.rb b/lib/puma/configuration.rb index 900a0105ee..6034520a1f 100644 --- a/lib/puma/configuration.rb +++ b/lib/puma/configuration.rb @@ -20,7 +20,7 @@ module ConfigDefault # 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 input is preferred over "defaults": # user_options = { foo: "bar" } # default_options = { foo: "zoo" } # options = UserFileDefaultOptions.new(user_options, default_options) @@ -32,7 +32,7 @@ module ConfigDefault # puts options.all_of(:foo) # # => ["bar", "zoo"] # - # A "file" option can be set. This config will be prefered over "default" options + # A "file" option can be set. This config will be preferred over "default" options # but will defer to any available "user" specified options. # # user_options = { foo: "bar" } diff --git a/lib/puma/const.rb b/lib/puma/const.rb index 15b9b7e169..77e86d44c1 100644 --- a/lib/puma/const.rb +++ b/lib/puma/const.rb @@ -100,7 +100,7 @@ class UnsupportedOption < RuntimeError # too taxing on performance. module Const - PUMA_VERSION = VERSION = "4.0.0".freeze + PUMA_VERSION = VERSION = "4.0.1".freeze CODE_NAME = "4 Fast 4 Furious".freeze PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze @@ -160,6 +160,9 @@ module Const LINE_END = "\r\n".freeze REMOTE_ADDR = "REMOTE_ADDR".freeze HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze + HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze + HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze + HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze SERVER_NAME = "SERVER_NAME".freeze SERVER_PORT = "SERVER_PORT".freeze diff --git a/lib/puma/dsl.rb b/lib/puma/dsl.rb index f5ab4ae519..a6ac191a5e 100644 --- a/lib/puma/dsl.rb +++ b/lib/puma/dsl.rb @@ -480,7 +480,7 @@ def worker_timeout(timeout) raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})" end - @options[:worker_timeout] = Integer(timeout) + @options[:worker_timeout] = timeout end # *Cluster mode only* Set the timeout for workers to boot diff --git a/lib/puma/events.rb b/lib/puma/events.rb index 04308e2fa2..148926cb95 100644 --- a/lib/puma/events.rb +++ b/lib/puma/events.rb @@ -39,8 +39,8 @@ def call(str) # def initialize(stdout, stderr) @formatter = DefaultFormatter.new - @stdout = stdout - @stderr = stderr + @stdout = stdout.dup + @stderr = stderr.dup @stdout.sync = true @stderr.sync = true @@ -103,7 +103,10 @@ def format(str) # 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" + @stderr.puts "#{Time.now}: HTTP parse error, malformed request " \ + "(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \ + "#{error.inspect}" \ + "\n---\n" end # An SSL error has occurred. diff --git a/lib/puma/plugin/tmp_restart.rb b/lib/puma/plugin/tmp_restart.rb index 7c2c402abd..5e326bf3a2 100644 --- a/lib/puma/plugin/tmp_restart.rb +++ b/lib/puma/plugin/tmp_restart.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'puma/plugin' Puma::Plugin.create do diff --git a/lib/puma/rack/builder.rb b/lib/puma/rack/builder.rb index c9eb29d2db..74c8aa40c2 100644 --- a/lib/puma/rack/builder.rb +++ b/lib/puma/rack/builder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Puma end diff --git a/lib/puma/rack/urlmap.rb b/lib/puma/rack/urlmap.rb index adaf74a1e3..0d0a5144f1 100644 --- a/lib/puma/rack/urlmap.rb +++ b/lib/puma/rack/urlmap.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module 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 diff --git a/lib/puma/rack_default.rb b/lib/puma/rack_default.rb index f95fd97289..016f54d424 100644 --- a/lib/puma/rack_default.rb +++ b/lib/puma/rack_default.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/handler/puma' module Rack::Handler diff --git a/lib/puma/reactor.rb b/lib/puma/reactor.rb index 0d419cfd0c..5bf10f2c19 100644 --- a/lib/puma/reactor.rb +++ b/lib/puma/reactor.rb @@ -23,7 +23,7 @@ module Puma # A connection comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance, # which stores it in an array and waits for any of the connections to be ready for reading. # - # The waiting/wake up is performed with nio4r, which will use the apropriate backend (libev, Java NIO or + # The waiting/wake up is performed with nio4r, which will use the appropriate backend (libev, Java NIO or # just plain IO#select). The call to `NIO::Selector#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 diff --git a/lib/puma/server.rb b/lib/puma/server.rb index ac1e28674a..d4435f264e 100644 --- a/lib/puma/server.rb +++ b/lib/puma/server.rb @@ -588,8 +588,11 @@ def normalize_env(env, client) 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 + if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on" + PORT_443 + else + PORT_80 + end end # Takes the request +req+, invokes the Rack application to construct @@ -627,7 +630,7 @@ def handle_request(req, lines) head = env[REQUEST_METHOD] == HEAD env[RACK_INPUT] = body - env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP + env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP if @early_hints env[EARLY_HINTS] = lambda { |headers| diff --git a/lib/rack/handler/puma.rb b/lib/rack/handler/puma.rb index 4607b7c103..a8f03bc794 100644 --- a/lib/rack/handler/puma.rb +++ b/lib/rack/handler/puma.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/handler' module Rack diff --git a/test/helper.rb b/test/helper.rb index 85b255d596..8ada8f5a39 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Copyright (c) 2011 Evan Phoenix # Copyright (c) 2005 Zed A. Shaw @@ -5,21 +6,16 @@ begin require 'stopgap_13632' rescue LoadError + puts "For test stability, you must install the stopgap_13632 gem." + exit(1) 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" +require "minitest/proveit" $LOAD_PATH << File.expand_path("../../lib", __FILE__) Thread.abort_on_exception = true @@ -126,3 +122,10 @@ def next_port(incr = 1) end Minitest::Test.include TestSkips + +class Minitest::Test + def self.run(reporter, options = {}) # :nodoc: + prove_it! + super + end +end diff --git a/test/test_app_status.rb b/test/test_app_status.rb index 7697975633..2dd1519d86 100644 --- a/test/test_app_status.rb +++ b/test/test_app_status.rb @@ -1,9 +1,13 @@ +# frozen_string_literal: true + require_relative "helper" require "puma/app/status" require "rack" class TestAppStatus < Minitest::Test + parallelize_me! + class FakeServer def initialize @status = :running diff --git a/test/test_binder.rb b/test/test_binder.rb index db9f659c2b..3146031e03 100644 --- a/test/test_binder.rb +++ b/test/test_binder.rb @@ -1,96 +1,85 @@ +# frozen_string_literal: true + require_relative "helper" require "puma/binder" require "puma/puma_http11" -class TestBinder < Minitest::Test +class TestBinderBase < Minitest::Test def setup @events = Puma::Events.null @binder = Puma::Binder.new(@events) + @key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__ + @cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__ end - def test_localhost_addresses_dont_alter_listeners_for_tcp_addresses - skip_on :jruby + private + + def ssl_context_for_binder(binder) + binder.instance_variable_get(:@ios)[0].instance_variable_get(:@ctx) + end +end +class TestBinder < TestBinderBase + def test_localhost_addresses_dont_alter_listeners_for_tcp_addresses @binder.parse(["tcp://localhost:10001"], @events) assert_equal [], @binder.listeners end +end - def test_localhost_addresses_dont_alter_listeners_for_ssl_addresses - skip_on :jruby +class TestBinderJRuby < TestBinderBase + def setup + super + skip_unless :jruby + end - key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__ - cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__ + def test_binder_parses_jruby_ssl_options + keystore = File.expand_path "../../examples/puma/keystore.jks", __FILE__ + ssl_cipher_list = "TLS_DHE_RSA_WITH_DES_CBC_SHA,TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA" - @binder.parse(["ssl://localhost:10002?key=#{key}&cert=#{cert}"], @events) + @binder.parse(["ssl://0.0.0.0:8080?keystore=#{keystore}&keystore-pass=&ssl_cipher_list=#{ssl_cipher_list}"], @events) - assert_equal [], @binder.listeners + assert_equal keystore, ssl_context_for_binder(@binder).keystore + assert_equal ssl_cipher_list, ssl_context_for_binder(@binder).ssl_cipher_list end +end - def test_binder_parses_ssl_cipher_filter +class TestBinderMRI < TestBinderBase + def setup + super skip_on :jruby + end - 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) + def test_localhost_addresses_dont_alter_listeners_for_ssl_addresses + @binder.parse(["ssl://localhost:10002?key=#{@key}&cert=#{@cert}"], @events) - ssl = @binder.instance_variable_get(:@ios)[0] - ctx = ssl.instance_variable_get(:@ctx) - assert_equal(ssl_cipher_filter, ctx.ssl_cipher_filter) + assert_equal [], @binder.listeners end - def test_binder_parses_jruby_ssl_options - skip_unless :jruby + def test_binder_parses_ssl_cipher_filter + ssl_cipher_filter = "AES@STRENGTH" - 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) + @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(keystore, ctx.keystore) - assert_equal(ssl_cipher_list, ctx.ssl_cipher_list) + assert_equal ssl_cipher_filter, ssl_context_for_binder(@binder).ssl_cipher_filter end def test_binder_parses_tlsv1_disabled - skip_on :jruby - - key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__ - cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__ + @binder.parse(["ssl://0.0.0.0?key=#{@key}&cert=#{@cert}&no_tlsv1=true"], @events) - @binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}&no_tlsv1=true"], @events) - - ssl = @binder.instance_variable_get(:@ios).first - ctx = ssl.instance_variable_get(:@ctx) - assert_equal(true, ctx.no_tlsv1) + assert ssl_context_for_binder(@binder).no_tlsv1 end def test_binder_parses_tlsv1_enabled - skip_on :jruby + @binder.parse(["ssl://0.0.0.0?key=#{@key}&cert=#{@cert}&no_tlsv1=false"], @events) - key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__ - cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__ - - @binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}&no_tlsv1=false"], @events) - - ssl = @binder.instance_variable_get(:@ios).first - ctx = ssl.instance_variable_get(:@ctx) - refute(ctx.no_tlsv1) + refute ssl_context_for_binder(@binder).no_tlsv1 end def test_binder_parses_tlsv1_unspecified_defaults_to_enabled - skip_on :jruby - - key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__ - cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__ - - @binder.parse(["ssl://0.0.0.0?key=#{key}&cert=#{cert}"], @events) + @binder.parse(["ssl://0.0.0.0?key=#{@key}&cert=#{@cert}"], @events) - ssl = @binder.instance_variable_get(:@ios).first - ctx = ssl.instance_variable_get(:@ctx) - refute(ctx.no_tlsv1) + refute ssl_context_for_binder(@binder).no_tlsv1 end end diff --git a/test/test_cli.rb b/test/test_cli.rb index cadefd097a..5140138665 100644 --- a/test/test_cli.rb +++ b/test/test_cli.rb @@ -87,8 +87,6 @@ def test_control_clustered wait_booted - sleep 2 - s = UNIXSocket.new @tmp_path s << "GET /stats HTTP/1.0\r\n\r\n" body = s.read diff --git a/test/test_config.rb b/test/test_config.rb index ae88bcda86..2fbef5ea07 100644 --- a/test/test_config.rb +++ b/test/test_config.rb @@ -1,12 +1,28 @@ +# frozen_string_literal: true + require_relative "helper" require "puma/configuration" -class TestConfigFile < Minitest::Test - def setup - FileUtils.mkpath("config/puma") - File.write("config/puma/fake-env.rb", "") +class TestConfigFileBase < Minitest::Test + 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 + +class TestConfigFile < TestConfigFileBase + parallelize_me! def test_app_from_rackup conf = Puma::Configuration.new do |c| @@ -46,19 +62,6 @@ def test_ssl_configuration_from_DSL 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_ssl_bind skip_on :jruby @@ -168,24 +171,6 @@ def test_config_files_with_non_existing_path 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 @@ -211,23 +196,49 @@ def test_config_raise_exception_on_sigterm conf.options[:raise_exception_on_sigterm] = true assert_equal conf.options[:raise_exception_on_sigterm], true end +end - def teardown - FileUtils.rm_r("config/puma") +# Thread unsafe modification of ENV +class TestEnvModifificationConfig < TestConfigFileBase + 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 +end - private +class TestConfigFileWithFakeEnv < TestConfigFileBase + def setup + FileUtils.mkpath("config/puma") + File.write("config/puma/fake-env.rb", "") + end - 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 + 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 teardown + FileUtils.rm_r("config/puma") + end end diff --git a/test/test_events.rb b/test/test_events.rb index 8b91a69f84..98f3446d33 100644 --- a/test/test_events.rb +++ b/test/test_events.rb @@ -6,7 +6,6 @@ def test_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 @@ -19,8 +18,17 @@ def test_strings def test_stdio events = Puma::Events.stdio - assert_equal STDOUT, events.stdout - assert_equal STDERR, events.stderr + # events.stdout is a dup, so same file handle, different ruby object, but inspect should show the same file handle + assert_equal STDOUT.inspect, events.stdout.inspect + assert_equal STDERR.inspect, events.stderr.inspect + end + + def test_stdio_respects_sync + STDOUT.sync = false + events = Puma::Events.stdio + + assert !STDOUT.sync + assert events.stdout.sync end def test_register_callback_with_block @@ -156,4 +164,25 @@ def test_custom_log_formatter assert_equal "-> ready", out end + + def test_parse_error + port = 0 + host = "127.0.0.1" + app = proc { |env| [200, {"Content-Type" => "plain/text"}, ["hello\n"]] } + events = Puma::Events.strings + server = Puma::Server.new app, events + + server.add_tcp_listener host, port + server.run + + sock = TCPSocket.new host, server.connected_port + path = "/" + params = "a"*1024*10 + + sock << "GET #{path}?a=#{params} HTTP/1.1\r\nConnection: close\r\n\r\n" + sock.read + sleep 0.1 # important so that the previous data is sent as a packet + assert_match %r!HTTP parse error, malformed request \(#{path}\)!, events.stderr.string + server.stop(true) + end end diff --git a/test/test_integration.rb b/test/test_integration.rb index 78f4f01d8f..c07c308f42 100644 --- a/test/test_integration.rb +++ b/test/test_integration.rb @@ -6,6 +6,9 @@ # These don't run on travis because they're too fragile +# TODO: Remove over-utilization of @instance variables +# TODO: remove stdout logging, get everything out of my rainbow dots + class TestIntegration < Minitest::Test def setup @@ -41,22 +44,25 @@ def teardown end end - def server(argv) + def server_cmd(argv) @tcp_port = next_port 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") + base = "bundle exec #{base}" if defined?(Bundler) + "#{base} -b tcp://127.0.0.1:#{@tcp_port} #{argv}" + end - wait_for_server_to_boot + def server(argv) + @server = IO.popen(server_cmd(argv), "r") + + wait_for_server_to_boot(@server) @server end def start_forked_server(argv) - @tcp_port = next_port + servercmd = server_cmd(argv) pid = fork do - exec "#{Gem.ruby} -I lib/ bin/puma -b tcp://127.0.0.1:#{@tcp_port} #{argv}" + exec servercmd end sleep 5 @@ -71,9 +77,9 @@ def stop_forked_server(pid) def restart_server_and_listen(argv) server(argv) - s = connect - initial_reply = read_body(s) - restart_server(s) + connection = connect + initial_reply = read_body(connection) + restart_server(@server, connection) [initial_reply, read_body(connect)] end @@ -86,12 +92,12 @@ def wait_booted end # reuses an existing connection to make sure that works - def restart_server(connection) + def restart_server(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 + wait_for_server_to_boot(server) end def connect @@ -101,8 +107,8 @@ def connect s end - def wait_for_server_to_boot - true while @server.gets !~ /Ctrl-C/ # wait for server to say it booted + def wait_for_server_to_boot(server) + true while server.gets !~ /Ctrl-C/ # wait for server to say it booted end def read_body(connection) @@ -204,7 +210,7 @@ def test_kill_unknown_via_pumactl skip_on :jruby # 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 + # do not want to accidentally kill a valid other process io = IO.popen(windows? ? "dir" : "ls") safe_pid = io.pid Process.wait safe_pid @@ -270,7 +276,7 @@ def test_sigterm_closes_listeners_on_forked_servers rescue Errno::ECONNREFUSED # connection was was never accepted # it can therefore be re-tried before the - # client receives an empty reponse + # client receives an empty response next_replies << :connection_refused end end diff --git a/test/test_null_io.rb b/test/test_null_io.rb index 5af9d6e0b1..c620cd0090 100644 --- a/test/test_null_io.rb +++ b/test/test_null_io.rb @@ -1,8 +1,12 @@ +# frozen_string_literal: true + require_relative "helper" require "puma/null_io" class TestNullIO < Minitest::Test + parallelize_me! + attr_accessor :nio def setup @@ -18,7 +22,9 @@ def test_gets_returns_nil end def test_each_never_yields - nio.each { raise "never yield" } + nio.instance_variable_set(:@foo, :baz) + nio.each { @foo = :bar } + assert_equal :baz, nio.instance_variable_get(:@foo) end def test_read_with_no_arguments diff --git a/test/test_persistent.rb b/test/test_persistent.rb index 06ec90aa71..7144f72fc8 100644 --- a/test/test_persistent.rb +++ b/test/test_persistent.rb @@ -152,14 +152,14 @@ def test_one_with_keep_alive_header end def test_persistent_timeout - @server.persistent_timeout = 2 + @server.persistent_timeout = 1 @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 + sleep 2 assert_raises EOFError do @client.read_nonblock(1) diff --git a/test/test_puma_server.rb b/test/test_puma_server.rb index 73a7190e40..7902850911 100644 --- a/test/test_puma_server.rb +++ b/test/test_puma_server.rb @@ -95,6 +95,30 @@ def test_very_large_return end def test_respect_x_forwarded_proto + env = {} + env['HOST'] = "example.com" + env['HTTP_X_FORWARDED_PROTO'] = "https,http" + + assert_equal "443", @server.default_server_port(env) + end + + def test_respect_x_forwarded_ssl_on + env = {} + env['HOST'] = "example.com" + env['HTTP_X_FORWARDED_SSL'] = "on" + + assert_equal "443", @server.default_server_port(env) + end + + def test_respect_x_forwarded_scheme + env = {} + env['HOST'] = "example.com" + env['HTTP_X_FORWARDED_SCHEME'] = "https" + + assert_equal "443", @server.default_server_port(env) + end + + def test_default_server_port @server.app = proc do |env| [200, {}, [env['SERVER_PORT']]] end @@ -104,16 +128,15 @@ def test_respect_x_forwarded_proto 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 + assert_equal "80", res.body end - def test_default_server_port + def test_default_server_port_respects_x_forwarded_proto @server.app = proc do |env| [200, {}, [env['SERVER_PORT']]] end @@ -123,12 +146,13 @@ def test_default_server_port req = Net::HTTP::Get.new("/") req['HOST'] = "example.com" + req['X_FORWARDED_PROTO'] = "https,http" res = Net::HTTP.start @host, @server.connected_port do |http| http.request(req) end - assert_equal "80", res.body + assert_equal "443", res.body end def test_HEAD_has_no_body diff --git a/test/test_rack_server.rb b/test/test_rack_server.rb index 063d2f6253..d0fcac7db0 100644 --- a/test/test_rack_server.rb +++ b/test/test_rack_server.rb @@ -1,37 +1,31 @@ +# frozen_string_literal: true require_relative "helper" require "rack" class TestRackServer < Minitest::Test + parallelize_me! 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) + @app.call(env) rescue Exception => e @exception = e - - [ - 500, - { "X-Exception" => e.message, "X-Exception-Class" => e.class.to_s }, - ["Error detected"] - ] + [ 500, {}, ["Error detected"] ] end end end class ServerLint < Rack::Lint def call(env) - assert("No env given") { env } check_env env @app.call(env) @@ -39,8 +33,6 @@ def call(env) 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 @@ -67,9 +59,7 @@ def test_lint stop - if exc = @checker.exception - raise exc - end + refute @checker.exception, "Checker raised exception" end def test_large_post_body @@ -85,9 +75,7 @@ def test_large_post_body stop - if exc = @checker.exception - raise exc - end + refute @checker.exception, "Checker raised exception" end def test_path_info @@ -134,5 +122,4 @@ def test_common_logger assert_match %r!GET /test HTTP/1\.1!, log.string end - end diff --git a/test/test_tcp_logger.rb b/test/test_tcp_logger.rb index e6b31f7a13..ff9bf06994 100644 --- a/test/test_tcp_logger.rb +++ b/test/test_tcp_logger.rb @@ -18,6 +18,7 @@ def test_events # in lib/puma/launcher.rb:85 # Puma::Events is default tcp_logger for cluster mode logger = Puma::Events.new(STDOUT, STDERR) + logger.instance_variable_set(:@stdout, $stdout) # ensure capture_process_io has access to the loggers output out, err = capture_subprocess_io do Puma::TCPLogger.new(logger, @server.app).call({}, @socket) end diff --git a/test/test_thread_pool.rb b/test/test_thread_pool.rb index f57dd2c8ad..7f2e65738b 100644 --- a/test/test_thread_pool.rb +++ b/test/test_thread_pool.rb @@ -5,11 +5,13 @@ class TestThreadPool < Minitest::Test def teardown - @pool.shutdown if @pool + @pool.shutdown(1) if @pool end def new_pool(min, max, &block) block = proc { } unless block + @work_mutex = Mutex.new + @work_done = ConditionVariable.new @pool = Puma::ThreadPool.new(min, max, &block) end @@ -22,18 +24,21 @@ def test_append_spawns thread_name = nil pool = new_pool(0, 1) do |work| - saw << work - thread_name = Thread.current.name if Thread.current.respond_to?(:name) + @work_mutex.synchronize do + saw << work + thread_name = Thread.current.name if Thread.current.respond_to?(:name) + @work_done.signal + end 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) + @work_mutex.synchronize do + @work_done.wait(@work_mutex, 5) + assert_equal 1, pool.spawned + assert_equal [1], saw + assert_equal('puma 001', thread_name) if Thread.current.respond_to?(:name) + end end def test_converts_pool_sizes @@ -47,55 +52,60 @@ def test_converts_pool_sizes end def test_append_queues_on_max - finish = false - pool = new_pool(0, 1) { Thread.pass until finish } + pool = new_pool(0, 0) do + "Hello World!" + end pool << 1 pool << 2 pool << 3 - pause - - assert_equal 2, pool.backlog - - finish = true + assert_equal 3, pool.backlog end def test_trim - pool = new_pool(0, 1) + pool = new_pool(0, 1) do |work| + @work_mutex.synchronize do + @work_done.signal + end + end pool << 1 - pause + @work_mutex.synchronize do + @work_done.wait(@work_mutex, 5) + assert_equal 1, pool.spawned + end - assert_equal 1, pool.spawned pool.trim + pool.instance_variable_get(:@workers).first.join - pause assert_equal 0, pool.spawned end def test_trim_leaves_min - finish = false - pool = new_pool(1, 2) { Thread.pass until finish } + pool = new_pool(1, 2) do |work| + @work_mutex.synchronize do + @work_done.signal + end + end pool << 1 pool << 2 - finish = true - - pause + @work_mutex.synchronize do + @work_done.wait(@work_mutex, 5) + assert_equal 2, pool.spawned + end - 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 @@ -215,16 +225,11 @@ def test_auto_reap_dead_threads assert_equal 2, pool.spawned + # TODO: is there a point to these two lines? pool << 1 pool << 2 - pause - - assert_equal 2, pool.spawned - - pool.auto_reap! 1 - - sleep 1 + pool.auto_reap! 0.1 pause @@ -232,19 +237,22 @@ def test_auto_reap_dead_threads end def test_force_shutdown_immediately - pool = new_pool(1, 1) { + pool = new_pool(0, 1) do |work| begin - sleep 1 + @work_mutex.synchronize do + @work_done.signal + end + sleep 10 # TODO: do something here other than sleep rescue Puma::ThreadPool::ForceShutdown end - } + end pool << 1 - sleep 0.1 - - pool.shutdown(0) - - assert_equal 0, pool.spawned + @work_mutex.synchronize do + @work_done.wait(@work_mutex, 5) + pool.shutdown(0) + assert_equal 0, pool.spawned + end end end diff --git a/test/test_unix_socket.rb b/test/test_unix_socket.rb index 712d6cdb4b..870a942356 100644 --- a/test/test_unix_socket.rb +++ b/test/test_unix_socket.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "helper" class TestPumaUnixSocket < Minitest::Test @@ -14,8 +16,7 @@ def setup end def teardown - @server.stop(true) if @server - File.unlink Path if File.exist? Path + @server.stop(true) end def test_server @@ -26,7 +27,5 @@ def test_server 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 diff --git a/test/test_web_server.rb b/test/test_web_server.rb index cc691b5bd3..4fd4b34803 100644 --- a/test/test_web_server.rb +++ b/test/test_web_server.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Copyright (c) 2011 Evan Phoenix # Copyright (c) 2005 Zed A. Shaw @@ -16,12 +17,10 @@ def call(env) end class WebServerTest < Minitest::Test + VALID_REQUEST = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n" 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 @@ -33,43 +32,24 @@ def teardown end def test_simple_server - hit(["http://127.0.0.1:#{ @server.connected_port }/test"]) + 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) + socket = do_test(VALID_REQUEST, 3) + assert_match "hello", socket.read end def test_close_client assert_raises IOError do - do_test(@valid_request, 10, 20) + do_test(VALID_REQUEST, 10, 20) end end def test_bad_client - do_test("GET /test HTTP/BAD", 3) + socket = do_test("GET /test HTTP/BAD", 3) + assert_match "Bad Request", socket.read end def test_header_is_too_long @@ -79,10 +59,30 @@ def test_header_is_too_long end end + # TODO: Why does this test take exactly 20 seconds? 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) + socket = do_test(long, (Puma::Const::CHUNK_SIZE * 2) - 400) + assert_match "hello", socket.read end + private + + def do_test(string, chunk, close_after=nil) + # 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 + socket.close if close_after && chunks_out > close_after + end + + socket.write(" ") # Some platforms only raise the exception on attempted write + socket.flush + socket + end end diff --git a/tools/jungle/init.d/puma b/tools/jungle/init.d/puma index 5f9e036f9a..32fe85d683 100755 --- a/tools/jungle/init.d/puma +++ b/tools/jungle/init.d/puma @@ -398,7 +398,7 @@ case "$1" in ;; remove) if [ "$#" -lt 2 ]; then - echo "Please, specifiy the app's directory to remove." + echo "Please, specify the app's directory to remove." exit 1 else do_remove $2 diff --git a/win_gem_test/puma.ps1 b/win_gem_test/puma.ps1 index 60a6275353..89fbfce457 100644 --- a/win_gem_test/puma.ps1 +++ b/win_gem_test/puma.ps1 @@ -37,7 +37,7 @@ function Pre-Compile { #———————————————————————————————————————————————————————————————— Run-Tests function Run-Tests { # call with comma separated list of gems to install or update - Update-Gems minitest, minitest-retry, rack, rake + Update-Gems minitest, minitest-retry, minitest-proveit, 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