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/request.rb CHANGED
@@ -14,15 +14,18 @@ module Puma
14
14
  #
15
15
  module Request # :nodoc:
16
16
 
17
- # determines whether to write body to io_buffer first, or straight to socket
18
- # also fixes max size of chunked body read when bosy is an IO.
19
- BODY_LEN_MAX = 1_024 * 256
17
+ # Single element array body: smaller bodies are written to io_buffer first,
18
+ # then a single write from io_buffer. Larger sizes are written separately.
19
+ # Also fixes max size of chunked file body read.
20
+ BODY_LEN_MAX = 1_024 * 256
20
21
 
21
- # size divide for using copy_stream on body
22
+ # File body: smaller bodies are combined with io_buffer, then written to
23
+ # socket. Larger bodies are written separately using `copy_stream`
22
24
  IO_BODY_MAX = 1_024 * 64
23
25
 
24
- # max size for io_buffer, force write when exceeded
25
- IO_BUFFER_LEN_MAX = 1_024 * 1_024 * 4
26
+ # Array body: elements are collected in io_buffer. When io_buffer's size
27
+ # exceeds value, they are written to the socket.
28
+ IO_BUFFER_LEN_MAX = 1_024 * 512
26
29
 
27
30
  SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
28
31
 
@@ -41,16 +44,22 @@ module Puma
41
44
  #
42
45
  # Finally, it'll return +true+ on keep-alive connections.
43
46
  # @param client [Puma::Client]
44
- # @param io_buffer [Puma::IOBuffer]
45
47
  # @param requests [Integer]
46
48
  # @return [Boolean,:async]
47
49
  #
48
- def handle_request(client, io_buffer, requests)
50
+ def handle_request(client, requests)
49
51
  env = client.env
52
+ io_buffer = client.io_buffer
50
53
  socket = client.io # io may be a MiniSSL::Socket
54
+ app_body = nil
55
+
51
56
 
52
57
  return false if closed_socket?(socket)
53
58
 
59
+ if client.http_content_length_limit_exceeded
60
+ return prepare_response(413, {}, ["Payload Too Large"], requests, client)
61
+ end
62
+
54
63
  normalize_env env, client
55
64
 
56
65
  env[PUMA_SOCKET] = socket
@@ -68,7 +77,9 @@ module Puma
68
77
  if @early_hints
69
78
  env[EARLY_HINTS] = lambda { |headers|
70
79
  begin
71
- fast_write_str socket, str_early_hints(headers)
80
+ unless (str = str_early_hints headers).empty?
81
+ fast_write_str socket, "HTTP/1.1 103 Early Hints\r\n#{str}\r\n"
82
+ end
72
83
  rescue ConnectionError => e
73
84
  @log_writer.debug_error e
74
85
  # noop, if we lost the socket we just won't send the early hints
@@ -84,15 +95,20 @@ module Puma
84
95
  env[RACK_AFTER_REPLY] ||= []
85
96
 
86
97
  begin
87
- if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
88
- status, headers, res_body = @thread_pool.with_force_shutdown do
98
+ if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
99
+ status, headers, app_body = @thread_pool.with_force_shutdown do
89
100
  @app.call(env)
90
101
  end
91
102
  else
92
103
  @log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
93
- status, headers, res_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
104
+ status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
94
105
  end
95
106
 
107
+ # app_body needs to always be closed, hold value in case lowlevel_error
108
+ # is called
109
+ res_body = app_body
110
+
111
+ # full hijack, app called env['rack.hijack']
96
112
  return :async if client.hijacked
97
113
 
98
114
  status = status.to_i
@@ -114,114 +130,136 @@ module Puma
114
130
 
115
131
  status, headers, res_body = lowlevel_error(e, env, 500)
116
132
  end
117
- prepare_response(status, headers, res_body, io_buffer, requests, client)
133
+ prepare_response(status, headers, res_body, requests, client)
134
+ ensure
135
+ io_buffer.reset
136
+ uncork_socket client.io
137
+ app_body.close if app_body.respond_to? :close
138
+ client.tempfile&.unlink
139
+ after_reply = env[RACK_AFTER_REPLY] || []
140
+ begin
141
+ after_reply.each { |o| o.call }
142
+ rescue StandardError => e
143
+ @log_writer.debug_error e
144
+ end unless after_reply.empty?
118
145
  end
119
146
 
120
147
  # Assembles the headers and prepares the body for actually sending the
121
- # response via #fast_write_response.
148
+ # response via `#fast_write_response`.
122
149
  #
123
150
  # @param status [Integer] the status returned by the Rack application
124
151
  # @param headers [Hash] the headers returned by the Rack application
125
- # @param app_body [Array] the body returned by the Rack application or
126
- # a call to `lowlevel_error`
127
- # @param io_buffer [Puma::IOBuffer] modified in place
152
+ # @param res_body [Array] the body returned by the Rack application or
153
+ # a call to `Server#lowlevel_error`
128
154
  # @param requests [Integer] number of inline requests handled
129
155
  # @param client [Puma::Client]
130
- # @return [Boolean,:async]
131
- def prepare_response(status, headers, app_body, io_buffer, requests, client)
156
+ # @return [Boolean,:async] keep-alive status or `:async`
157
+ def prepare_response(status, headers, res_body, requests, client)
132
158
  env = client.env
133
- socket = client.io
134
-
135
- after_reply = env[RACK_AFTER_REPLY] || []
159
+ socket = client.io
160
+ io_buffer = client.io_buffer
136
161
 
137
162
  return false if closed_socket?(socket)
138
163
 
139
- resp_info = str_headers(env, status, headers, app_body, io_buffer, requests, client)
140
-
141
- # below converts app_body into body, dependent on app_body's characteristics, and
142
- # resp_info[:content_length] will be set if it can be determined
143
- if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
144
- if app_body.respond_to?(:to_ary)
145
- length = 0
146
- if array_body = app_body.to_ary
147
- body = array_body.map { |part| length += part.bytesize; part }
148
- elsif app_body.is_a?(::File) && app_body.respond_to?(:size)
149
- length = app_body.size
150
- elsif app_body.respond_to?(:each)
151
- body = []
152
- app_body.each { |part| length += part.bytesize; body << part }
164
+ # Close the connection after a reasonable number of inline requests
165
+ # if the server is at capacity and the listener has a new connection ready.
166
+ # This allows Puma to service connections fairly when the number
167
+ # of concurrent connections exceeds the size of the threadpool.
168
+ force_keep_alive = requests < @max_fast_inline ||
169
+ @thread_pool.busy_threads < @max_threads ||
170
+ !client.listener.to_io.wait_readable(0)
171
+
172
+ resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
173
+
174
+ close_body = false
175
+ response_hijack = nil
176
+ content_length = resp_info[:content_length]
177
+ keep_alive = resp_info[:keep_alive]
178
+
179
+ if res_body.respond_to?(:each) && !resp_info[:response_hijack]
180
+ # below converts app_body into body, dependent on app_body's characteristics, and
181
+ # content_length will be set if it can be determined
182
+ if !content_length && !resp_info[:transfer_encoding] && status != 204
183
+ if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) &&
184
+ array_body.is_a?(Array)
185
+ body = array_body.compact
186
+ content_length = body.sum(&:bytesize)
187
+ elsif res_body.is_a?(File) && res_body.respond_to?(:size)
188
+ body = res_body
189
+ content_length = body.size
190
+ elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path)
191
+ body = File.open fn, 'rb'
192
+ content_length = body.size
193
+ close_body = true
194
+ else
195
+ body = res_body
153
196
  end
154
- resp_info[:content_length] = length
155
- elsif app_body.is_a?(File) && app_body.respond_to?(:size)
156
- resp_info[:content_length] = app_body.size
157
- body = app_body
158
- elsif app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
159
- File.readable?(fn = app_body.to_path)
197
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
198
+ File.readable?(fn = res_body.to_path)
160
199
  body = File.open fn, 'rb'
161
- resp_info[:content_length] = body.size
200
+ content_length = body.size
201
+ close_body = true
202
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) &&
203
+ res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
204
+ # Sprockets::Asset
205
+ content_length = res_body.bytesize unless content_length
206
+ if (body_str = res_body.to_hash[:source])
207
+ body = [body_str]
208
+ else # avoid each and use a File object
209
+ body = File.open fn, 'rb'
210
+ close_body = true
211
+ end
162
212
  else
163
- body = app_body
213
+ body = res_body
164
214
  end
165
- elsif !app_body.is_a?(::File) && app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
166
- File.readable?(fn = app_body.to_path)
167
- body = File.open fn, 'rb'
168
- resp_info[:content_length] = body.size
169
215
  else
170
- body = app_body
216
+ # partial hijack, from Rack spec:
217
+ # Servers must ignore the body part of the response tuple when the
218
+ # rack.hijack response header is present.
219
+ response_hijack = resp_info[:response_hijack] || res_body
171
220
  end
172
221
 
173
222
  line_ending = LINE_END
174
223
 
175
- content_length = resp_info[:content_length]
176
- keep_alive = resp_info[:keep_alive]
177
-
178
- if app_body && !app_body.respond_to?(:each)
179
- response_hijack = app_body
180
- else
181
- response_hijack = resp_info[:response_hijack]
182
- end
183
-
184
224
  cork_socket socket
185
225
 
186
226
  if resp_info[:no_body]
187
- if content_length and status != 204
227
+ # 101 (Switching Protocols) doesn't return here or have content_length,
228
+ # it should be using `response_hijack`
229
+ unless status == 101
230
+ if content_length && status != 204
231
+ io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
232
+ end
233
+
234
+ io_buffer << LINE_END
235
+ fast_write_str socket, io_buffer.read_and_reset
236
+ socket.flush
237
+ return keep_alive
238
+ end
239
+ else
240
+ if content_length
188
241
  io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
242
+ chunked = false
243
+ elsif !response_hijack && resp_info[:allow_chunked]
244
+ io_buffer << TRANSFER_ENCODING_CHUNKED
245
+ chunked = true
189
246
  end
190
-
191
- io_buffer << LINE_END
192
- fast_write_str socket, io_buffer.to_s
193
- return keep_alive
194
- end
195
- if content_length
196
- io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
197
- chunked = false
198
- elsif !response_hijack and resp_info[:allow_chunked]
199
- io_buffer << TRANSFER_ENCODING_CHUNKED
200
- chunked = true
201
247
  end
202
248
 
203
249
  io_buffer << line_ending
204
250
 
251
+ # partial hijack, we write headers, then hand the socket to the app via
252
+ # response_hijack.call
205
253
  if response_hijack
206
- fast_write_str socket, io_buffer.to_s
254
+ fast_write_str socket, io_buffer.read_and_reset
255
+ uncork_socket socket
207
256
  response_hijack.call socket
208
257
  return :async
209
258
  end
210
259
 
211
260
  fast_write_response socket, body, io_buffer, chunked, content_length.to_i
261
+ body.close if close_body
212
262
  keep_alive
213
- ensure
214
- io_buffer.reset
215
- resp_info = nil
216
- uncork_socket socket
217
- app_body.close if app_body.respond_to? :close
218
- client.tempfile&.unlink
219
-
220
- begin
221
- after_reply.each { |o| o.call }
222
- rescue StandardError => e
223
- @log_writer.debug_error e
224
- end unless after_reply.empty?
225
263
  end
226
264
 
227
265
  # @param env [Hash] see Puma::Client#env, from request
@@ -237,10 +275,10 @@ module Puma
237
275
 
238
276
  # Used to write 'early hints', 'no body' responses, 'hijacked' responses,
239
277
  # and body segments (called by `fast_write_response`).
240
- # Writes a string to an io (normally `Client#io`) using `write_nonblock`.
278
+ # Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
241
279
  # Large strings may not be written in one pass, especially if `io` is a
242
280
  # `MiniSSL::Socket`.
243
- # @param io [#write_nonblock] the io to write to
281
+ # @param socket [#write_nonblock] the request/response socket
244
282
  # @param str [String] the string written to the io
245
283
  # @raise [ConnectionError]
246
284
  #
@@ -249,7 +287,7 @@ module Puma
249
287
  byte_size = str.bytesize
250
288
  while n < byte_size
251
289
  begin
252
- n += socket.syswrite(n.zero? ? str : str.byteslice(n..-1))
290
+ n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
253
291
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
254
292
  unless socket.wait_writable WRITE_TIMEOUT
255
293
  raise ConnectionError, SOCKET_WRITE_ERR_MSG
@@ -267,39 +305,41 @@ module Puma
267
305
  # @param socket [#write] the response socket
268
306
  # @param body [Enumerable, File] the body object
269
307
  # @param io_buffer [Puma::IOBuffer] contains headers
270
- # @param chunk [Boolean]
308
+ # @param chunked [Boolean]
309
+ # @paramn content_length [Integer
271
310
  # @raise [ConnectionError]
272
311
  #
273
312
  def fast_write_response(socket, body, io_buffer, chunked, content_length)
274
- if body.is_a?(::File) || body.respond_to?(:read) || body.respond_to?(:readpartial)
313
+ if body.is_a?(::File) && body.respond_to?(:read)
275
314
  if chunked # would this ever happen?
276
- while part = body.read(BODY_LEN_MAX)
277
- io_buffer.append part.bytesize.to_s(16), LINE_END, part, LINE_END
315
+ while chunk = body.read(BODY_LEN_MAX)
316
+ io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
278
317
  end
279
- io_buffer << CLOSE_CHUNKED
280
- fast_write_str socket, io_buffer.to_s
318
+ fast_write_str socket, CLOSE_CHUNKED
281
319
  else
282
320
  if content_length <= IO_BODY_MAX
283
- io_buffer.write body.sysread(content_length)
284
- fast_write_str socket, io_buffer.to_s
321
+ io_buffer.write body.read(content_length)
322
+ fast_write_str socket, io_buffer.read_and_reset
285
323
  else
286
- fast_write_str socket, io_buffer.to_s
324
+ fast_write_str socket, io_buffer.read_and_reset
287
325
  IO.copy_stream body, socket
288
326
  end
289
327
  end
290
- body.close
291
328
  elsif body.is_a?(::Array) && body.length == 1
292
- body_first = body.first
293
- if body_first.is_a?(::String) && body_first.bytesize >= BODY_LEN_MAX
329
+ body_first = nil
330
+ # using body_first = body.first causes issues?
331
+ body.each { |str| body_first ||= str }
332
+
333
+ if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
334
+ # smaller body, write to io_buffer first
335
+ io_buffer.write body_first
336
+ fast_write_str socket, io_buffer.read_and_reset
337
+ else
294
338
  # large body, write both header & body to socket
295
- fast_write_str socket, io_buffer.to_s
339
+ fast_write_str socket, io_buffer.read_and_reset
296
340
  fast_write_str socket, body_first
297
- else
298
- # smaller body, write to stream first
299
- io_buffer.write body_first
300
- fast_write_str socket, io_buffer.to_s
301
341
  end
302
- else
342
+ elsif body.is_a?(::Array)
303
343
  # for array bodies, flush io_buffer to socket when size is greater than
304
344
  # IO_BUFFER_LEN_MAX
305
345
  if chunked
@@ -307,8 +347,7 @@ module Puma
307
347
  next if (byte_size = part.bytesize).zero?
308
348
  io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
309
349
  if io_buffer.length > IO_BUFFER_LEN_MAX
310
- fast_write_str socket, io_buffer.to_s
311
- io_buffer.reset
350
+ fast_write_str socket, io_buffer.read_and_reset
312
351
  end
313
352
  end
314
353
  io_buffer.write CLOSE_CHUNKED
@@ -317,13 +356,37 @@ module Puma
317
356
  next if part.bytesize.zero?
318
357
  io_buffer.write part
319
358
  if io_buffer.length > IO_BUFFER_LEN_MAX
320
- fast_write_str socket, io_buffer.to_s
321
- io_buffer.reset
359
+ fast_write_str socket, io_buffer.read_and_reset
322
360
  end
323
361
  end
324
362
  end
325
- fast_write_str(socket, io_buffer.to_s) unless io_buffer.length.zero?
363
+ # may write last body part for non-chunked, also headers if array is empty
364
+ fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
365
+ else
366
+ # for enum bodies
367
+ if chunked
368
+ empty_body = true
369
+ body.each do |part|
370
+ next if part.nil? || (byte_size = part.bytesize).zero?
371
+ empty_body = false
372
+ io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
373
+ fast_write_str socket, io_buffer.read_and_reset
374
+ end
375
+ if empty_body
376
+ io_buffer << CLOSE_CHUNKED
377
+ fast_write_str socket, io_buffer.read_and_reset
378
+ else
379
+ fast_write_str socket, CLOSE_CHUNKED
380
+ end
381
+ else
382
+ fast_write_str socket, io_buffer.read_and_reset
383
+ body.each do |part|
384
+ next if part.bytesize.zero?
385
+ fast_write_str socket, part
386
+ end
387
+ end
326
388
  end
389
+ socket.flush
327
390
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
328
391
  raise ConnectionError, SOCKET_WRITE_ERR_MSG
329
392
  rescue Errno::EPIPE, SystemCallError, IOError
@@ -357,7 +420,11 @@ module Puma
357
420
 
358
421
  unless env[REQUEST_PATH]
359
422
  # it might be a dumbass full host request header
360
- uri = URI.parse(env[REQUEST_URI])
423
+ uri = begin
424
+ URI.parse(env[REQUEST_URI])
425
+ rescue URI::InvalidURIError
426
+ raise Puma::HttpParserError
427
+ end
361
428
  env[REQUEST_PATH] = uri.path
362
429
 
363
430
  # A nil env value will cause a LintError (and fatal errors elsewhere),
@@ -436,7 +503,7 @@ module Puma
436
503
  to_add = nil
437
504
 
438
505
  env.each do |k,v|
439
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
506
+ if k.start_with?("HTTP_") && k.include?(",") && k != "HTTP_TRANSFER,ENCODING"
440
507
  if to_delete
441
508
  to_delete << k
442
509
  else
@@ -464,7 +531,7 @@ module Puma
464
531
  # @version 5.0.3
465
532
  #
466
533
  def str_early_hints(headers)
467
- eh_str = +"HTTP/1.1 103 Early Hints\r\n"
534
+ eh_str = +""
468
535
  headers.each_pair do |k, vs|
469
536
  next if illegal_header_key?(k)
470
537
 
@@ -473,11 +540,11 @@ module Puma
473
540
  next if illegal_header_value?(v)
474
541
  eh_str << "#{k}: #{v}\r\n"
475
542
  end
476
- else
543
+ elsif !(vs.to_s.empty? || !illegal_header_value?(vs))
477
544
  eh_str << "#{k}: #{vs}\r\n"
478
545
  end
479
546
  end
480
- "#{eh_str}\r\n".freeze
547
+ eh_str.freeze
481
548
  end
482
549
  private :str_early_hints
483
550
 
@@ -496,12 +563,13 @@ module Puma
496
563
  # @param content_length [Integer,nil] content length if it can be determined from the
497
564
  # response body
498
565
  # @param io_buffer [Puma::IOBuffer] modified inn place
499
- # @param requests [Integer] number of inline requests handled
500
- # @param client [Puma::Client]
566
+ # @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
567
+ # status and `@max_fast_inline`
501
568
  # @return [Hash] resp_info
502
569
  # @version 5.0.3
503
570
  #
504
- def str_headers(env, status, headers, res_body, io_buffer, requests, client)
571
+ def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
572
+
505
573
  line_ending = LINE_END
506
574
  colon = COLON
507
575
 
@@ -544,13 +612,8 @@ module Puma
544
612
  # if running without request queueing
545
613
  resp_info[:keep_alive] &&= @queue_requests
546
614
 
547
- # Close the connection after a reasonable number of inline requests
548
- # if the server is at capacity and the listener has a new connection ready.
549
- # This allows Puma to service connections fairly when the number
550
- # of concurrent connections exceeds the size of the threadpool.
551
- resp_info[:keep_alive] &&= requests < @max_fast_inline ||
552
- @thread_pool.busy_threads < @max_threads ||
553
- !client.listener.to_io.wait_readable(0)
615
+ # see prepare_response
616
+ resp_info[:keep_alive] &&= force_keep_alive
554
617
 
555
618
  resp_info[:response_hijack] = nil
556
619
 
@@ -560,7 +623,8 @@ module Puma
560
623
  case k.downcase
561
624
  when CONTENT_LENGTH2
562
625
  next if illegal_header_value?(vs)
563
- resp_info[:content_length] = vs
626
+ # nil.to_i is 0, nil&.to_i is nil
627
+ resp_info[:content_length] = vs&.to_i
564
628
  next
565
629
  when TRANSFER_ENCODING
566
630
  resp_info[:allow_chunked] = false
data/lib/puma/runner.rb CHANGED
@@ -75,7 +75,11 @@ module Puma
75
75
  control = Puma::Server.new app, nil,
76
76
  { min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
77
77
 
78
- control.binder.parse [str], nil, 'Starting control server'
78
+ begin
79
+ control.binder.parse [str], nil, 'Starting control server'
80
+ rescue Errno::EADDRINUSE, Errno::EACCES => e
81
+ raise e, "Error: Control server address '#{str}' is already in use. Original error: #{e.message}"
82
+ end
79
83
 
80
84
  control.run thread_name: 'ctl'
81
85
  @control = control
@@ -182,6 +186,10 @@ module Puma
182
186
  end
183
187
  end
184
188
 
189
+ def utc_iso8601(val)
190
+ "#{val.utc.strftime '%FT%T'}Z"
191
+ end
192
+
185
193
  def stats
186
194
  {
187
195
  versions: {
@@ -194,5 +202,12 @@ module Puma
194
202
  }
195
203
  }
196
204
  end
205
+
206
+ # this method call should always be guarded by `@log_writer.debug?`
207
+ def debug_loaded_extensions(str)
208
+ @log_writer.debug "────────────────────────────────── #{str}"
209
+ re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
210
+ $LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
211
+ end
197
212
  end
198
213
  end