puma 6.0.0 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +184 -6
  3. data/LICENSE +0 -0
  4. data/README.md +58 -13
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +0 -0
  7. data/docs/compile_options.md +0 -0
  8. data/docs/deployment.md +0 -0
  9. data/docs/fork_worker.md +0 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +12 -0
  17. data/docs/nginx.md +1 -1
  18. data/docs/plugins.md +0 -0
  19. data/docs/rails_dev_mode.md +0 -0
  20. data/docs/restart.md +0 -0
  21. data/docs/signals.md +0 -0
  22. data/docs/stats.md +0 -0
  23. data/docs/systemd.md +1 -2
  24. data/docs/testing_benchmarks_local_files.md +0 -0
  25. data/docs/testing_test_rackup_ci_files.md +0 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +0 -0
  29. data/ext/puma_http11/http11_parser.c +0 -0
  30. data/ext/puma_http11/http11_parser.h +0 -0
  31. data/ext/puma_http11/http11_parser.java.rl +0 -0
  32. data/ext/puma_http11/http11_parser.rl +0 -0
  33. data/ext/puma_http11/http11_parser_common.rl +0 -0
  34. data/ext/puma_http11/mini_ssl.c +91 -8
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +0 -0
  39. data/ext/puma_http11/puma_http11.c +0 -0
  40. data/lib/puma/app/status.rb +1 -1
  41. data/lib/puma/binder.rb +14 -11
  42. data/lib/puma/cli.rb +5 -1
  43. data/lib/puma/client.rb +53 -16
  44. data/lib/puma/cluster/worker.rb +5 -0
  45. data/lib/puma/cluster/worker_handle.rb +0 -0
  46. data/lib/puma/cluster.rb +5 -5
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +5 -1
  49. data/lib/puma/const.rb +129 -88
  50. data/lib/puma/control_cli.rb +12 -5
  51. data/lib/puma/detect.rb +2 -0
  52. data/lib/puma/dsl.rb +147 -7
  53. data/lib/puma/error_logger.rb +2 -1
  54. data/lib/puma/events.rb +0 -0
  55. data/lib/puma/io_buffer.rb +10 -0
  56. data/lib/puma/jruby_restart.rb +0 -0
  57. data/lib/puma/json_serialization.rb +0 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +0 -0
  59. data/lib/puma/launcher.rb +9 -22
  60. data/lib/puma/log_writer.rb +14 -4
  61. data/lib/puma/minissl/context_builder.rb +1 -0
  62. data/lib/puma/minissl.rb +17 -0
  63. data/lib/puma/plugin/systemd.rb +90 -0
  64. data/lib/puma/plugin/tmp_restart.rb +0 -0
  65. data/lib/puma/plugin.rb +0 -0
  66. data/lib/puma/rack/builder.rb +2 -2
  67. data/lib/puma/rack/urlmap.rb +1 -1
  68. data/lib/puma/rack_default.rb +18 -3
  69. data/lib/puma/reactor.rb +17 -8
  70. data/lib/puma/request.rb +189 -125
  71. data/lib/puma/runner.rb +16 -1
  72. data/lib/puma/sd_notify.rb +149 -0
  73. data/lib/puma/server.rb +74 -34
  74. data/lib/puma/single.rb +3 -1
  75. data/lib/puma/state_file.rb +0 -0
  76. data/lib/puma/thread_pool.rb +42 -7
  77. data/lib/puma/util.rb +0 -0
  78. data/lib/puma.rb +1 -3
  79. data/lib/rack/handler/puma.rb +113 -86
  80. data/tools/Dockerfile +0 -0
  81. data/tools/trickletest.rb +0 -0
  82. metadata +4 -3
  83. data/lib/puma/systemd.rb +0 -47
data/lib/puma/const.rb CHANGED
@@ -5,7 +5,6 @@ module Puma
5
5
  class UnsupportedOption < RuntimeError
6
6
  end
7
7
 
8
-
9
8
  # Every standard HTTP code mapped to the appropriate message. These are
10
9
  # used so frequently that they are placed directly in Puma for easy
11
10
  # access rather than Puma::Const itself.
@@ -19,6 +18,7 @@ module Puma
19
18
  100 => 'Continue',
20
19
  101 => 'Switching Protocols',
21
20
  102 => 'Processing',
21
+ 103 => 'Early Hints',
22
22
  200 => 'OK',
23
23
  201 => 'Created',
24
24
  202 => 'Accepted',
@@ -50,16 +50,16 @@ module Puma
50
50
  410 => 'Gone',
51
51
  411 => 'Length Required',
52
52
  412 => 'Precondition Failed',
53
- 413 => 'Payload Too Large',
53
+ 413 => 'Content Too Large',
54
54
  414 => 'URI Too Long',
55
55
  415 => 'Unsupported Media Type',
56
56
  416 => 'Range Not Satisfiable',
57
57
  417 => 'Expectation Failed',
58
- 418 => 'I\'m A Teapot',
59
58
  421 => 'Misdirected Request',
60
- 422 => 'Unprocessable Entity',
59
+ 422 => 'Unprocessable Content',
61
60
  423 => 'Locked',
62
61
  424 => 'Failed Dependency',
62
+ 425 => 'Too Early',
63
63
  426 => 'Upgrade Required',
64
64
  428 => 'Precondition Required',
65
65
  429 => 'Too Many Requests',
@@ -74,7 +74,7 @@ module Puma
74
74
  506 => 'Variant Also Negotiates',
75
75
  507 => 'Insufficient Storage',
76
76
  508 => 'Loop Detected',
77
- 510 => 'Not Extended',
77
+ 510 => 'Not Extended (OBSOLETED)',
78
78
  511 => 'Network Authentication Required'
79
79
  }.freeze
80
80
 
@@ -100,10 +100,10 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "6.0.0".freeze
104
- CODE_NAME = "Sunflower".freeze
103
+ PUMA_VERSION = VERSION = "6.4.0"
104
+ CODE_NAME = "The Eagle of Durango"
105
105
 
106
- PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
+ PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
107
107
 
108
108
  FAST_TRACK_KA_TIMEOUT = 0.2
109
109
 
@@ -112,28 +112,28 @@ module Puma
112
112
  WRITE_TIMEOUT = 10
113
113
 
114
114
  # The original URI requested by the client.
115
- REQUEST_URI= 'REQUEST_URI'.freeze
116
- REQUEST_PATH = 'REQUEST_PATH'.freeze
117
- QUERY_STRING = 'QUERY_STRING'.freeze
118
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
115
+ REQUEST_URI= "REQUEST_URI"
116
+ REQUEST_PATH = "REQUEST_PATH"
117
+ QUERY_STRING = "QUERY_STRING"
118
+ CONTENT_LENGTH = "CONTENT_LENGTH"
119
119
 
120
- PATH_INFO = 'PATH_INFO'.freeze
120
+ PATH_INFO = "PATH_INFO"
121
121
 
122
- PUMA_TMP_BASE = "puma".freeze
122
+ PUMA_TMP_BASE = "puma"
123
123
 
124
124
  ERROR_RESPONSE = {
125
125
  # Indicate that we couldn't parse the request
126
- 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
126
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
127
127
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
128
- 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
128
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n",
129
129
  # The standard empty 408 response for requests that timed out.
130
- 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
130
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n",
131
131
  # Indicate that there was an internal error, obviously.
132
- 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
132
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n",
133
133
  # Incorrect or invalid header value
134
- 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
134
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n",
135
135
  # A common header for indicating the server is too busy. Not used yet.
136
- 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
136
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\n"
137
137
  }.freeze
138
138
 
139
139
  # The basic max request size we'll try to read.
@@ -146,95 +146,136 @@ module Puma
146
146
  # Maximum request body size before it is moved out of memory and into a tempfile for reading.
147
147
  MAX_BODY = MAX_HEADER
148
148
 
149
- REQUEST_METHOD = "REQUEST_METHOD".freeze
150
- HEAD = "HEAD".freeze
151
- GET = "GET".freeze
152
- POST = "POST".freeze
153
- PUT = "PUT".freeze
154
- DELETE = "DELETE".freeze
155
- OPTIONS = "OPTIONS".freeze
156
- TRACE = "TRACE".freeze
157
- PATCH = "PATCH".freeze
158
- SUPPORTED_HTTP_METHODS = [HEAD, GET, POST, PUT, DELETE, OPTIONS, TRACE, PATCH].freeze
149
+ REQUEST_METHOD = "REQUEST_METHOD"
150
+ HEAD = "HEAD"
151
+
152
+ # based on https://www.rfc-editor.org/rfc/rfc9110.html#name-overview,
153
+ # with CONNECT removed, and PATCH added
154
+ SUPPORTED_HTTP_METHODS = %w[HEAD GET POST PUT DELETE OPTIONS TRACE PATCH].freeze
155
+
156
+ # list from https://www.iana.org/assignments/http-methods/http-methods.xhtml
157
+ # as of 04-May-23
158
+ IANA_HTTP_METHODS = %w[
159
+ ACL
160
+ BASELINE-CONTROL
161
+ BIND
162
+ CHECKIN
163
+ CHECKOUT
164
+ CONNECT
165
+ COPY
166
+ DELETE
167
+ GET
168
+ HEAD
169
+ LABEL
170
+ LINK
171
+ LOCK
172
+ MERGE
173
+ MKACTIVITY
174
+ MKCALENDAR
175
+ MKCOL
176
+ MKREDIRECTREF
177
+ MKWORKSPACE
178
+ MOVE
179
+ OPTIONS
180
+ ORDERPATCH
181
+ PATCH
182
+ POST
183
+ PRI
184
+ PROPFIND
185
+ PROPPATCH
186
+ PUT
187
+ REBIND
188
+ REPORT
189
+ SEARCH
190
+ TRACE
191
+ UNBIND
192
+ UNCHECKOUT
193
+ UNLINK
194
+ UNLOCK
195
+ UPDATE
196
+ UPDATEREDIRECTREF
197
+ VERSION-CONTROL
198
+ ].freeze
199
+
159
200
  # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
160
- LINE_END = "\r\n".freeze
161
- REMOTE_ADDR = "REMOTE_ADDR".freeze
162
- HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
163
- HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
164
- HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
165
- HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
201
+ LINE_END = "\r\n"
202
+ REMOTE_ADDR = "REMOTE_ADDR"
203
+ HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
204
+ HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL"
205
+ HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME"
206
+ HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO"
166
207
 
167
- SERVER_NAME = "SERVER_NAME".freeze
168
- SERVER_PORT = "SERVER_PORT".freeze
169
- HTTP_HOST = "HTTP_HOST".freeze
170
- PORT_80 = "80".freeze
171
- PORT_443 = "443".freeze
172
- LOCALHOST = "localhost".freeze
173
- LOCALHOST_IPV4 = "127.0.0.1".freeze
174
- LOCALHOST_IPV6 = "::1".freeze
175
- UNSPECIFIED_IPV4 = "0.0.0.0".freeze
176
- UNSPECIFIED_IPV6 = "::".freeze
208
+ SERVER_NAME = "SERVER_NAME"
209
+ SERVER_PORT = "SERVER_PORT"
210
+ HTTP_HOST = "HTTP_HOST"
211
+ PORT_80 = "80"
212
+ PORT_443 = "443"
213
+ LOCALHOST = "localhost"
214
+ LOCALHOST_IPV4 = "127.0.0.1"
215
+ LOCALHOST_IPV6 = "::1"
216
+ UNSPECIFIED_IPV4 = "0.0.0.0"
217
+ UNSPECIFIED_IPV6 = "::"
177
218
 
178
- SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
179
- HTTP_11 = "HTTP/1.1".freeze
219
+ SERVER_PROTOCOL = "SERVER_PROTOCOL"
220
+ HTTP_11 = "HTTP/1.1"
180
221
 
181
- SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
182
- GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
183
- CGI_VER = "CGI/1.2".freeze
222
+ SERVER_SOFTWARE = "SERVER_SOFTWARE"
223
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE"
224
+ CGI_VER = "CGI/1.2"
184
225
 
185
- STOP_COMMAND = "?".freeze
186
- HALT_COMMAND = "!".freeze
187
- RESTART_COMMAND = "R".freeze
226
+ STOP_COMMAND = "?"
227
+ HALT_COMMAND = "!"
228
+ RESTART_COMMAND = "R"
188
229
 
189
- RACK_INPUT = "rack.input".freeze
190
- RACK_URL_SCHEME = "rack.url_scheme".freeze
191
- RACK_AFTER_REPLY = "rack.after_reply".freeze
192
- PUMA_SOCKET = "puma.socket".freeze
193
- PUMA_CONFIG = "puma.config".freeze
194
- PUMA_PEERCERT = "puma.peercert".freeze
230
+ RACK_INPUT = "rack.input"
231
+ RACK_URL_SCHEME = "rack.url_scheme"
232
+ RACK_AFTER_REPLY = "rack.after_reply"
233
+ PUMA_SOCKET = "puma.socket"
234
+ PUMA_CONFIG = "puma.config"
235
+ PUMA_PEERCERT = "puma.peercert"
195
236
 
196
- HTTP = "http".freeze
197
- HTTPS = "https".freeze
237
+ HTTP = "http"
238
+ HTTPS = "https"
198
239
 
199
- HTTPS_KEY = "HTTPS".freeze
240
+ HTTPS_KEY = "HTTPS"
200
241
 
201
- HTTP_VERSION = "HTTP_VERSION".freeze
202
- HTTP_CONNECTION = "HTTP_CONNECTION".freeze
203
- HTTP_EXPECT = "HTTP_EXPECT".freeze
204
- CONTINUE = "100-continue".freeze
242
+ HTTP_VERSION = "HTTP_VERSION"
243
+ HTTP_CONNECTION = "HTTP_CONNECTION"
244
+ HTTP_EXPECT = "HTTP_EXPECT"
245
+ CONTINUE = "100-continue"
205
246
 
206
- HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze
207
- HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
208
- HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
247
+ HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n"
248
+ HTTP_11_200 = "HTTP/1.1 200 OK\r\n"
249
+ HTTP_10_200 = "HTTP/1.0 200 OK\r\n"
209
250
 
210
- CLOSE = "close".freeze
211
- KEEP_ALIVE = "keep-alive".freeze
251
+ CLOSE = "close"
252
+ KEEP_ALIVE = "keep-alive"
212
253
 
213
- CONTENT_LENGTH2 = "content-length".freeze
214
- CONTENT_LENGTH_S = "Content-Length: ".freeze
215
- TRANSFER_ENCODING = "transfer-encoding".freeze
216
- TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze
254
+ CONTENT_LENGTH2 = "content-length"
255
+ CONTENT_LENGTH_S = "Content-Length: "
256
+ TRANSFER_ENCODING = "transfer-encoding"
257
+ TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
217
258
 
218
- CONNECTION_CLOSE = "Connection: close\r\n".freeze
219
- CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
259
+ CONNECTION_CLOSE = "Connection: close\r\n"
260
+ CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n"
220
261
 
221
- TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
222
- CLOSE_CHUNKED = "0\r\n\r\n".freeze
262
+ TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n"
263
+ CLOSE_CHUNKED = "0\r\n\r\n"
223
264
 
224
- CHUNKED = "chunked".freeze
265
+ CHUNKED = "chunked"
225
266
 
226
- COLON = ": ".freeze
267
+ COLON = ": "
227
268
 
228
- NEWLINE = "\n".freeze
269
+ NEWLINE = "\n"
229
270
 
230
- HIJACK_P = "rack.hijack?".freeze
231
- HIJACK = "rack.hijack".freeze
232
- HIJACK_IO = "rack.hijack_io".freeze
271
+ HIJACK_P = "rack.hijack?"
272
+ HIJACK = "rack.hijack"
273
+ HIJACK_IO = "rack.hijack_io"
233
274
 
234
- EARLY_HINTS = "rack.early_hints".freeze
275
+ EARLY_HINTS = "rack.early_hints"
235
276
 
236
277
  # Illegal character in the key or value of response header
237
- DQUOTE = "\"".freeze
278
+ DQUOTE = "\""
238
279
  HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
239
280
  ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
240
281
  # header values can contain HTAB?
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'optparse'
4
- require_relative 'state_file'
5
4
  require_relative 'const'
6
5
  require_relative 'detect'
7
- require_relative 'configuration'
8
6
  require 'uri'
9
7
  require 'socket'
10
8
 
@@ -126,6 +124,9 @@ module Puma
126
124
  end
127
125
 
128
126
  if @config_file
127
+ require_relative 'configuration'
128
+ require_relative 'log_writer'
129
+
129
130
  config = Puma::Configuration.new({ config_files: [@config_file] }, {})
130
131
  config.load
131
132
  @state ||= config.options[:state]
@@ -149,6 +150,8 @@ module Puma
149
150
  raise "State file not found: #{@state}"
150
151
  end
151
152
 
153
+ require_relative 'state_file'
154
+
152
155
  sf = Puma::StateFile.new
153
156
  sf.load @state
154
157
 
@@ -164,22 +167,26 @@ module Puma
164
167
  def send_request
165
168
  uri = URI.parse @control_url
166
169
 
170
+ host = uri.host
171
+
167
172
  # create server object by scheme
168
173
  server =
169
174
  case uri.scheme
170
175
  when 'ssl'
171
176
  require 'openssl'
177
+ host = host[1..-2] if host&.start_with? '['
172
178
  OpenSSL::SSL::SSLSocket.new(
173
- TCPSocket.new(uri.host, uri.port),
179
+ TCPSocket.new(host, uri.port),
174
180
  OpenSSL::SSL::SSLContext.new)
175
181
  .tap { |ssl| ssl.sync_close = true } # default is false
176
182
  .tap(&:connect)
177
183
  when 'tcp'
178
- TCPSocket.new uri.host, uri.port
184
+ host = host[1..-2] if host&.start_with? '['
185
+ TCPSocket.new host, uri.port
179
186
  when 'unix'
180
187
  # check for abstract UNIXSocket
181
188
  UNIXSocket.new(@control_url.start_with?('unix://@') ?
182
- "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
189
+ "\0#{host}#{uri.path}" : "#{host}#{uri.path}")
183
190
  else
184
191
  raise "Invalid scheme: #{uri.scheme}"
185
192
  end
data/lib/puma/detect.rb CHANGED
@@ -17,6 +17,8 @@ module Puma
17
17
  IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
18
18
  IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
19
19
 
20
+ IS_LINUX = !(IS_OSX || IS_WINDOWS)
21
+
20
22
  # @version 5.2.0
21
23
  IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
22
24
 
data/lib/puma/dsl.rb CHANGED
@@ -65,6 +65,7 @@ module Puma
65
65
 
66
66
  ca_additions = "&ca=#{Puma::Util.escape(opts[:ca])}" if ['peer', 'force_peer'].include?(verify)
67
67
 
68
+ low_latency_str = opts.key?(:low_latency) ? "&low_latency=#{opts[:low_latency]}" : ''
68
69
  backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
69
70
 
70
71
  if defined?(JRUBY_VERSION)
@@ -88,6 +89,7 @@ module Puma
88
89
 
89
90
  cert_flags = (cert = opts[:cert]) ? "cert=#{Puma::Util.escape(cert)}" : nil
90
91
  key_flags = (key = opts[:key]) ? "&key=#{Puma::Util.escape(key)}" : nil
92
+ password_flags = (password_command = opts[:key_password_command]) ? "&key_password_command=#{Puma::Util.escape(password_command)}" : nil
91
93
 
92
94
  reuse_flag =
93
95
  if (reuse = opts[:reuse])
@@ -113,8 +115,8 @@ module Puma
113
115
  nil
114
116
  end
115
117
 
116
- "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{ssl_cipher_filter}" \
117
- "#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
118
+ "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{password_flags}#{ssl_cipher_filter}" \
119
+ "#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}#{low_latency_str}"
118
120
  end
119
121
  end
120
122
 
@@ -250,6 +252,7 @@ module Puma
250
252
  #
251
253
  # * Set the socket backlog depth with +backlog+, default is 1024.
252
254
  # * Set up an SSL certificate with +key+ & +cert+.
255
+ # * Set up an SSL certificate for mTLS with +key+, +cert+, +ca+ and +verify_mode+.
253
256
  # * Set whether to optimize for low latency instead of throughput with
254
257
  # +low_latency+, default is to not optimize for low latency. This is done
255
258
  # via +Socket::TCP_NODELAY+.
@@ -259,6 +262,8 @@ module Puma
259
262
  # bind 'unix:///var/run/puma.sock?backlog=512'
260
263
  # @example SSL cert
261
264
  # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem'
265
+ # @example SSL cert for mutual TLS (mTLS)
266
+ # bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
262
267
  # @example Disable optimization for low latency
263
268
  # bind 'tcp://0.0.0.0:9292?low_latency=false'
264
269
  # @example Socket permissions
@@ -310,16 +315,22 @@ module Puma
310
315
  bind URI::Generic.build(scheme: 'tcp', host: host, port: Integer(port)).to_s
311
316
  end
312
317
 
318
+ # Define how long the tcp socket stays open, if no data has been received.
319
+ # @see Puma::Server.new
320
+ def first_data_timeout(seconds)
321
+ @options[:first_data_timeout] = Integer(seconds)
322
+ end
323
+
313
324
  # Define how long persistent connections can be idle before Puma closes them.
314
325
  # @see Puma::Server.new
315
326
  def persistent_timeout(seconds)
316
327
  @options[:persistent_timeout] = Integer(seconds)
317
328
  end
318
329
 
319
- # Define how long the tcp socket stays open, if no data has been received.
330
+ # If a new request is not received within this number of seconds, begin shutting down.
320
331
  # @see Puma::Server.new
321
- def first_data_timeout(seconds)
322
- @options[:first_data_timeout] = Integer(seconds)
332
+ def idle_timeout(seconds)
333
+ @options[:idle_timeout] = Integer(seconds)
323
334
  end
324
335
 
325
336
  # Work around leaky apps that leave garbage in Thread locals
@@ -415,6 +426,11 @@ module Puma
415
426
  @options[:log_requests] = which
416
427
  end
417
428
 
429
+ # Pass in a custom logging class instance
430
+ def custom_logger(custom_logger)
431
+ @options[:custom_logger] = custom_logger
432
+ end
433
+
418
434
  # Show debugging info
419
435
  #
420
436
  def debug
@@ -500,6 +516,12 @@ module Puma
500
516
  # `true`, which sets reuse 'on' with default values, or a hash, with `:size`
501
517
  # and/or `:timeout` keys, each with integer values.
502
518
  #
519
+ # The `cert:` options hash parameter can be the path to a certificate
520
+ # file including all intermediate certificates in PEM format.
521
+ #
522
+ # The `cert_pem:` options hash parameter can be String containing the
523
+ # cerificate and all intermediate certificates in PEM format.
524
+ #
503
525
  # @example
504
526
  # ssl_bind '127.0.0.1', '9292', {
505
527
  # cert: path_to_cert,
@@ -581,6 +603,11 @@ module Puma
581
603
  @options[:silence_single_worker_warning] = true
582
604
  end
583
605
 
606
+ # Disable warning message when running single mode with callback hook defined.
607
+ def silence_fork_callback_warning
608
+ @options[:silence_fork_callback_warning] = true
609
+ end
610
+
584
611
  # Code to run immediately before master process
585
612
  # forks workers (once on boot). These hooks can block if necessary
586
613
  # to wait for background operations unknown to Puma to finish before
@@ -596,6 +623,8 @@ module Puma
596
623
  # puts "Starting workers..."
597
624
  # end
598
625
  def before_fork(&block)
626
+ warn_if_in_single_mode('before_fork')
627
+
599
628
  @options[:before_fork] ||= []
600
629
  @options[:before_fork] << block
601
630
  end
@@ -611,6 +640,8 @@ module Puma
611
640
  # puts 'Before worker boot...'
612
641
  # end
613
642
  def on_worker_boot(key = nil, &block)
643
+ warn_if_in_single_mode('on_worker_boot')
644
+
614
645
  process_hook :before_worker_boot, key, block, 'on_worker_boot'
615
646
  end
616
647
 
@@ -627,6 +658,8 @@ module Puma
627
658
  # puts 'On worker shutdown...'
628
659
  # end
629
660
  def on_worker_shutdown(key = nil, &block)
661
+ warn_if_in_single_mode('on_worker_shutdown')
662
+
630
663
  process_hook :before_worker_shutdown, key, block, 'on_worker_shutdown'
631
664
  end
632
665
 
@@ -641,6 +674,8 @@ module Puma
641
674
  # puts 'Before worker fork...'
642
675
  # end
643
676
  def on_worker_fork(&block)
677
+ warn_if_in_single_mode('on_worker_fork')
678
+
644
679
  process_hook :before_worker_fork, nil, block, 'on_worker_fork'
645
680
  end
646
681
 
@@ -655,11 +690,23 @@ module Puma
655
690
  # puts 'After worker fork...'
656
691
  # end
657
692
  def after_worker_fork(&block)
693
+ warn_if_in_single_mode('after_worker_fork')
694
+
658
695
  process_hook :after_worker_fork, nil, block, 'after_worker_fork'
659
696
  end
660
697
 
661
698
  alias_method :after_worker_boot, :after_worker_fork
662
699
 
700
+ # Code to run after puma is booted (works for both: single and clustered)
701
+ #
702
+ # @example
703
+ # on_booted do
704
+ # puts 'After booting...'
705
+ # end
706
+ def on_booted(&block)
707
+ @config.options[:events].on_booted(&block)
708
+ end
709
+
663
710
  # When `fork_worker` is enabled, code to run in Worker 0
664
711
  # before all other workers are re-forked from this process,
665
712
  # after the server has temporarily stopped serving requests
@@ -682,6 +729,40 @@ module Puma
682
729
  process_hook :before_refork, key, block, 'on_refork'
683
730
  end
684
731
 
732
+ # Code to run immediately before a thread starts. The worker does not
733
+ # start new threads until this code finishes.
734
+ #
735
+ # This hook is useful for doing something when a thread
736
+ # starts.
737
+ #
738
+ # This can be called multiple times to add several hooks.
739
+ #
740
+ # @example
741
+ # on_thread_start do
742
+ # puts 'On thread start...'
743
+ # end
744
+ def on_thread_start(&block)
745
+ @options[:before_thread_start] ||= []
746
+ @options[:before_thread_start] << block
747
+ end
748
+
749
+ # Code to run immediately before a thread exits. The worker does not
750
+ # accept new requests until this code finishes.
751
+ #
752
+ # This hook is useful for cleaning up thread local resources when a thread
753
+ # is trimmed.
754
+ #
755
+ # This can be called multiple times to add several hooks.
756
+ #
757
+ # @example
758
+ # on_thread_exit do
759
+ # puts 'On thread exit...'
760
+ # end
761
+ def on_thread_exit(&block)
762
+ @options[:before_thread_exit] ||= []
763
+ @options[:before_thread_exit] << block
764
+ end
765
+
685
766
  # Code to run out-of-band when the worker is idle.
686
767
  # These hooks run immediately after a request has finished
687
768
  # processing and there are no busy threads on the worker.
@@ -813,7 +894,8 @@ module Puma
813
894
  # not a request timeout, it is to protect against a hung or dead process.
814
895
  # Setting this value will not protect against slow requests.
815
896
  #
816
- # The minimum value is 6 seconds, the default value is 60 seconds.
897
+ # This value must be greater than worker_check_interval.
898
+ # The default value is 60 seconds.
817
899
  #
818
900
  # @note Cluster mode only.
819
901
  # @example
@@ -1019,6 +1101,51 @@ module Puma
1019
1101
  @options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
1020
1102
  end
1021
1103
 
1104
+ # Specify how big the request payload should be, in bytes.
1105
+ # This limit is compared against Content-Length HTTP header.
1106
+ # If the payload size (CONTENT_LENGTH) is larger than http_content_length_limit,
1107
+ # HTTP 413 status code is returned.
1108
+ #
1109
+ # When no Content-Length http header is present, it is compared against the
1110
+ # size of the body of the request.
1111
+ #
1112
+ # The default value for http_content_length_limit is nil.
1113
+ def http_content_length_limit(limit)
1114
+ @options[:http_content_length_limit] = limit
1115
+ end
1116
+
1117
+ # Supported http methods, which will replace `Puma::Const::SUPPORTED_HTTP_METHODS`.
1118
+ # The value of `:any` will allows all methods, otherwise, the value must be
1119
+ # an array of strings. Note that methods are all uppercase.
1120
+ #
1121
+ # `Puma::Const::SUPPORTED_HTTP_METHODS` is conservative, if you want a
1122
+ # complete set of methods, the methods defined by the
1123
+ # [IANA Method Registry](https://www.iana.org/assignments/http-methods/http-methods.xhtml)
1124
+ # are pre-defined as the constant `Puma::Const::IANA_HTTP_METHODS`.
1125
+ #
1126
+ # @note If the `methods` value is `:any`, no method check with be performed,
1127
+ # similar to Puma v5 and earlier.
1128
+ #
1129
+ # @example Adds 'PROPFIND' to existing supported methods
1130
+ # supported_http_methods(Puma::Const::SUPPORTED_HTTP_METHODS + ['PROPFIND'])
1131
+ # @example Restricts methods to the array elements
1132
+ # supported_http_methods %w[HEAD GET POST PUT DELETE OPTIONS PROPFIND]
1133
+ # @example Restricts methods to the methods in the IANA Registry
1134
+ # supported_http_methods Puma::Const::IANA_HTTP_METHODS
1135
+ # @example Allows any method
1136
+ # supported_http_methods :any
1137
+ #
1138
+ def supported_http_methods(methods)
1139
+ if methods == :any
1140
+ @options[:supported_http_methods] = :any
1141
+ elsif Array === methods && methods == (ary = methods.grep(String).uniq) &&
1142
+ !ary.empty?
1143
+ @options[:supported_http_methods] = ary
1144
+ else
1145
+ raise "supported_http_methods must be ':any' or a unique array of strings"
1146
+ end
1147
+ end
1148
+
1022
1149
  private
1023
1150
 
1024
1151
  # To avoid adding cert_pem and key_pem as URI params, we store them on the
@@ -1046,7 +1173,20 @@ module Puma
1046
1173
  elsif key.nil?
1047
1174
  @options[options_key] << block
1048
1175
  else
1049
- raise "'#{method}' key must be String or Symbol"
1176
+ raise "'#{meth}' key must be String or Symbol"
1177
+ end
1178
+ end
1179
+
1180
+ def warn_if_in_single_mode(hook_name)
1181
+ return if @options[:silence_fork_callback_warning]
1182
+
1183
+ if (@options[:workers] || 0) == 0
1184
+ log_string =
1185
+ "Warning: You specified code to run in a `#{hook_name}` block, " \
1186
+ "but Puma is not configured to run in cluster mode (worker count > 0 ), " \
1187
+ "so your `#{hook_name}` block did not run"
1188
+
1189
+ LogWriter.stdio.log(log_string)
1050
1190
  end
1051
1191
  end
1052
1192
  end
@@ -102,7 +102,8 @@ module Puma
102
102
  @ioerr.is_a?(IO) and @ioerr.wait_writable(1)
103
103
  @ioerr.write "#{w_str}\n"
104
104
  @ioerr.flush unless @ioerr.sync
105
- rescue Errno::EPIPE, Errno::EBADF, IOError
105
+ rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
106
+ # 'Invalid argument' (Errno::EINVAL) may be raised by flush
106
107
  end
107
108
  end
108
109
  rescue ThreadError
data/lib/puma/events.rb CHANGED
File without changes