puma 5.6.4 → 6.1.1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +199 -3
  3. data/README.md +22 -17
  4. data/bin/puma-wild +1 -1
  5. data/docs/compile_options.md +34 -0
  6. data/docs/fork_worker.md +1 -3
  7. data/docs/nginx.md +1 -1
  8. data/docs/systemd.md +1 -2
  9. data/docs/testing_benchmarks_local_files.md +150 -0
  10. data/docs/testing_test_rackup_ci_files.md +36 -0
  11. data/ext/puma_http11/extconf.rb +18 -10
  12. data/ext/puma_http11/http11_parser.c +1 -1
  13. data/ext/puma_http11/http11_parser.h +1 -1
  14. data/ext/puma_http11/http11_parser.java.rl +2 -2
  15. data/ext/puma_http11/http11_parser.rl +2 -2
  16. data/ext/puma_http11/http11_parser_common.rl +2 -2
  17. data/ext/puma_http11/mini_ssl.c +63 -24
  18. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  19. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  20. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +166 -65
  21. data/ext/puma_http11/puma_http11.c +17 -9
  22. data/lib/puma/app/status.rb +6 -3
  23. data/lib/puma/binder.rb +41 -46
  24. data/lib/puma/cli.rb +11 -17
  25. data/lib/puma/client.rb +54 -16
  26. data/lib/puma/cluster/worker.rb +18 -11
  27. data/lib/puma/cluster/worker_handle.rb +4 -1
  28. data/lib/puma/cluster.rb +33 -30
  29. data/lib/puma/configuration.rb +75 -58
  30. data/lib/puma/const.rb +76 -88
  31. data/lib/puma/control_cli.rb +21 -18
  32. data/lib/puma/detect.rb +4 -0
  33. data/lib/puma/dsl.rb +111 -49
  34. data/lib/puma/error_logger.rb +17 -9
  35. data/lib/puma/events.rb +6 -126
  36. data/lib/puma/io_buffer.rb +39 -4
  37. data/lib/puma/jruby_restart.rb +2 -1
  38. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  39. data/lib/puma/launcher.rb +111 -175
  40. data/lib/puma/log_writer.rb +141 -0
  41. data/lib/puma/minissl/context_builder.rb +23 -12
  42. data/lib/puma/minissl.rb +91 -15
  43. data/lib/puma/null_io.rb +5 -0
  44. data/lib/puma/plugin/systemd.rb +90 -0
  45. data/lib/puma/plugin/tmp_restart.rb +1 -1
  46. data/lib/puma/rack/builder.rb +4 -4
  47. data/lib/puma/rack_default.rb +19 -4
  48. data/lib/puma/reactor.rb +4 -4
  49. data/lib/puma/request.rb +344 -161
  50. data/lib/puma/runner.rb +52 -20
  51. data/lib/puma/sd_notify.rb +149 -0
  52. data/lib/puma/server.rb +57 -69
  53. data/lib/puma/single.rb +13 -11
  54. data/lib/puma/state_file.rb +2 -4
  55. data/lib/puma/thread_pool.rb +16 -16
  56. data/lib/puma/util.rb +12 -14
  57. data/lib/puma.rb +12 -11
  58. data/lib/rack/handler/puma.rb +115 -94
  59. metadata +10 -5
  60. data/lib/puma/queue_close.rb +0 -26
  61. data/lib/puma/systemd.rb +0 -46
data/lib/puma/request.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Puma
4
+ #———————————————————————— DO NOT USE — this class is for internal use only ———
5
+
4
6
 
5
7
  # The methods here are included in Server, but are separated into this file.
6
8
  # All the methods here pertain to passing the request to the app, then
@@ -10,7 +12,24 @@ module Puma
10
12
  # #handle_request, which is called in Server#process_client.
11
13
  # @version 5.0.3
12
14
  #
13
- module Request
15
+ module Request # :nodoc:
16
+
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
21
+
22
+ # File body: smaller bodies are combined with io_buffer, then written to
23
+ # socket. Larger bodies are written separately using `copy_stream`
24
+ IO_BODY_MAX = 1_024 * 64
25
+
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
29
+
30
+ SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
31
+
32
+ CUSTOM_STAT = 'CUSTOM'
14
33
 
15
34
  include Puma::Const
16
35
 
@@ -25,40 +44,42 @@ module Puma
25
44
  #
26
45
  # Finally, it'll return +true+ on keep-alive connections.
27
46
  # @param client [Puma::Client]
28
- # @param lines [Puma::IOBuffer]
29
47
  # @param requests [Integer]
30
48
  # @return [Boolean,:async]
31
49
  #
32
- def handle_request(client, lines, requests)
50
+ def handle_request(client, requests)
33
51
  env = client.env
34
- io = client.io # io may be a MiniSSL::Socket
52
+ io_buffer = client.io_buffer
53
+ socket = client.io # io may be a MiniSSL::Socket
54
+ app_body = nil
55
+
56
+
57
+ return false if closed_socket?(socket)
35
58
 
36
- return false if closed_socket?(io)
59
+ if client.http_content_length_limit_exceeded
60
+ return prepare_response(413, {}, ["Payload Too Large"], requests, client)
61
+ end
37
62
 
38
63
  normalize_env env, client
39
64
 
40
- env[PUMA_SOCKET] = io
65
+ env[PUMA_SOCKET] = socket
41
66
 
42
- if env[HTTPS_KEY] && io.peercert
43
- env[PUMA_PEERCERT] = io.peercert
67
+ if env[HTTPS_KEY] && socket.peercert
68
+ env[PUMA_PEERCERT] = socket.peercert
44
69
  end
45
70
 
46
71
  env[HIJACK_P] = true
47
72
  env[HIJACK] = client
48
73
 
49
- body = client.body
50
-
51
- head = env[REQUEST_METHOD] == HEAD
52
-
53
- env[RACK_INPUT] = body
74
+ env[RACK_INPUT] = client.body
54
75
  env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
55
76
 
56
77
  if @early_hints
57
78
  env[EARLY_HINTS] = lambda { |headers|
58
79
  begin
59
- fast_write io, str_early_hints(headers)
80
+ fast_write_str socket, str_early_hints(headers)
60
81
  rescue ConnectionError => e
61
- @events.debug_error e
82
+ @log_writer.debug_error e
62
83
  # noop, if we lost the socket we just won't send the early hints
63
84
  end
64
85
  }
@@ -69,119 +90,170 @@ module Puma
69
90
  # A rack extension. If the app writes #call'ables to this
70
91
  # array, we will invoke them when the request is done.
71
92
  #
72
- after_reply = env[RACK_AFTER_REPLY] = []
93
+ env[RACK_AFTER_REPLY] ||= []
73
94
 
74
95
  begin
75
- begin
76
- status, headers, res_body = @thread_pool.with_force_shutdown do
96
+ if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
97
+ status, headers, app_body = @thread_pool.with_force_shutdown do
77
98
  @app.call(env)
78
99
  end
100
+ else
101
+ @log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
102
+ status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
103
+ end
79
104
 
80
- return :async if client.hijacked
105
+ # app_body needs to always be closed, hold value in case lowlevel_error
106
+ # is called
107
+ res_body = app_body
81
108
 
82
- status = status.to_i
109
+ return :async if client.hijacked
83
110
 
84
- if status == -1
85
- unless headers.empty? and res_body == []
86
- raise "async response must have empty headers and body"
87
- end
111
+ status = status.to_i
88
112
 
89
- return :async
113
+ if status == -1
114
+ unless headers.empty? and res_body == []
115
+ raise "async response must have empty headers and body"
90
116
  end
91
- rescue ThreadPool::ForceShutdown => e
92
- @events.unknown_error e, client, "Rack app"
93
- @events.log "Detected force shutdown of a thread"
94
-
95
- status, headers, res_body = lowlevel_error(e, env, 503)
96
- rescue Exception => e
97
- @events.unknown_error e, client, "Rack app"
98
117
 
99
- status, headers, res_body = lowlevel_error(e, env, 500)
118
+ return :async
100
119
  end
120
+ rescue ThreadPool::ForceShutdown => e
121
+ @log_writer.unknown_error e, client, "Rack app"
122
+ @log_writer.log "Detected force shutdown of a thread"
123
+
124
+ status, headers, res_body = lowlevel_error(e, env, 503)
125
+ rescue Exception => e
126
+ @log_writer.unknown_error e, client, "Rack app"
101
127
 
102
- res_info = {}
103
- res_info[:content_length] = nil
104
- res_info[:no_body] = head
128
+ status, headers, res_body = lowlevel_error(e, env, 500)
129
+ end
130
+ prepare_response(status, headers, res_body, requests, client)
131
+ ensure
132
+ io_buffer.reset
133
+ uncork_socket client.io
134
+ app_body.close if app_body.respond_to? :close
135
+ client.tempfile&.unlink
136
+ after_reply = env[RACK_AFTER_REPLY] || []
137
+ begin
138
+ after_reply.each { |o| o.call }
139
+ rescue StandardError => e
140
+ @log_writer.debug_error e
141
+ end unless after_reply.empty?
142
+ end
105
143
 
106
- res_info[:content_length] = if res_body.kind_of? Array and res_body.size == 1
107
- res_body[0].bytesize
144
+ # Assembles the headers and prepares the body for actually sending the
145
+ # response via `#fast_write_response`.
146
+ #
147
+ # @param status [Integer] the status returned by the Rack application
148
+ # @param headers [Hash] the headers returned by the Rack application
149
+ # @param res_body [Array] the body returned by the Rack application or
150
+ # a call to `Server#lowlevel_error`
151
+ # @param requests [Integer] number of inline requests handled
152
+ # @param client [Puma::Client]
153
+ # @return [Boolean,:async] keep-alive status or `:async`
154
+ def prepare_response(status, headers, res_body, requests, client)
155
+ env = client.env
156
+ socket = client.io
157
+ io_buffer = client.io_buffer
158
+
159
+ return false if closed_socket?(socket)
160
+
161
+ # Close the connection after a reasonable number of inline requests
162
+ # if the server is at capacity and the listener has a new connection ready.
163
+ # This allows Puma to service connections fairly when the number
164
+ # of concurrent connections exceeds the size of the threadpool.
165
+ force_keep_alive = requests < @max_fast_inline ||
166
+ @thread_pool.busy_threads < @max_threads ||
167
+ !client.listener.to_io.wait_readable(0)
168
+
169
+ resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
170
+
171
+ close_body = false
172
+
173
+ # below converts app_body into body, dependent on app_body's characteristics, and
174
+ # resp_info[:content_length] will be set if it can be determined
175
+ if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
176
+ if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) && array_body.is_a?(Array)
177
+ body = array_body
178
+ resp_info[:content_length] = body.sum(&:bytesize)
179
+ elsif res_body.is_a?(File) && res_body.respond_to?(:size)
180
+ body = res_body
181
+ resp_info[:content_length] = body.size
182
+ elsif res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
183
+ File.readable?(fn = res_body.to_path)
184
+ body = File.open fn, 'rb'
185
+ resp_info[:content_length] = body.size
186
+ close_body = true
108
187
  else
109
- nil
188
+ body = res_body
189
+ end
190
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
191
+ File.readable?(fn = res_body.to_path)
192
+ body = File.open fn, 'rb'
193
+ resp_info[:content_length] = body.size
194
+ close_body = true
195
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) && res_body.respond_to?(:each) &&
196
+ res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
197
+ # Sprockets::Asset
198
+ resp_info[:content_length] = res_body.bytesize unless resp_info[:content_length]
199
+ if res_body.to_hash[:source] # use each to return @source
200
+ body = res_body
201
+ else # avoid each and use a File object
202
+ body = File.open fn, 'rb'
203
+ close_body = true
110
204
  end
205
+ else
206
+ body = res_body
207
+ end
111
208
 
112
- cork_socket io
209
+ line_ending = LINE_END
113
210
 
114
- str_headers(env, status, headers, res_info, lines, requests, client)
211
+ content_length = resp_info[:content_length]
212
+ keep_alive = resp_info[:keep_alive]
115
213
 
116
- line_ending = LINE_END
214
+ if res_body && !res_body.respond_to?(:each)
215
+ response_hijack = res_body
216
+ else
217
+ response_hijack = resp_info[:response_hijack]
218
+ end
117
219
 
118
- content_length = res_info[:content_length]
119
- response_hijack = res_info[:response_hijack]
220
+ cork_socket socket
120
221
 
121
- if res_info[:no_body]
122
- if content_length and status != 204
123
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
222
+ if resp_info[:no_body]
223
+ # 101 (Switching Protocols) doesn't return here or have content_length,
224
+ # it should be using `response_hijack`
225
+ unless status == 101
226
+ if content_length && status != 204
227
+ io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
124
228
  end
125
229
 
126
- lines << LINE_END
127
- fast_write io, lines.to_s
128
- return res_info[:keep_alive]
230
+ io_buffer << LINE_END
231
+ fast_write_str socket, io_buffer.read_and_reset
232
+ socket.flush
233
+ return keep_alive
129
234
  end
130
-
235
+ else
131
236
  if content_length
132
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
237
+ io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
133
238
  chunked = false
134
- elsif !response_hijack and res_info[:allow_chunked]
135
- lines << TRANSFER_ENCODING_CHUNKED
239
+ elsif !response_hijack && resp_info[:allow_chunked]
240
+ io_buffer << TRANSFER_ENCODING_CHUNKED
136
241
  chunked = true
137
242
  end
243
+ end
138
244
 
139
- lines << line_ending
140
-
141
- fast_write io, lines.to_s
142
-
143
- if response_hijack
144
- response_hijack.call io
145
- return :async
146
- end
147
-
148
- begin
149
- res_body.each do |part|
150
- next if part.bytesize.zero?
151
- if chunked
152
- fast_write io, (part.bytesize.to_s(16) << line_ending)
153
- fast_write io, part # part may have different encoding
154
- fast_write io, line_ending
155
- else
156
- fast_write io, part
157
- end
158
- io.flush
159
- end
160
-
161
- if chunked
162
- fast_write io, CLOSE_CHUNKED
163
- io.flush
164
- end
165
- rescue SystemCallError, IOError
166
- raise ConnectionError, "Connection error detected during write"
167
- end
168
-
169
- ensure
170
- begin
171
- uncork_socket io
172
-
173
- body.close
174
- client.tempfile.unlink if client.tempfile
175
- ensure
176
- # Whatever happens, we MUST call `close` on the response body.
177
- # Otherwise Rack::BodyProxy callbacks may not fire and lead to various state leaks
178
- res_body.close if res_body.respond_to? :close
179
- end
245
+ io_buffer << line_ending
180
246
 
181
- after_reply.each { |o| o.call }
247
+ if response_hijack
248
+ fast_write_str socket, io_buffer.read_and_reset
249
+ uncork_socket socket
250
+ response_hijack.call socket
251
+ return :async
182
252
  end
183
253
 
184
- res_info[:keep_alive]
254
+ fast_write_response socket, body, io_buffer, chunked, content_length.to_i
255
+ body.close if close_body
256
+ keep_alive
185
257
  end
186
258
 
187
259
  # @param env [Hash] see Puma::Client#env, from request
@@ -195,45 +267,126 @@ module Puma
195
267
  end
196
268
  end
197
269
 
198
- # Writes to an io (normally Client#io) using #syswrite
199
- # @param io [#syswrite] the io to write to
270
+ # Used to write 'early hints', 'no body' responses, 'hijacked' responses,
271
+ # and body segments (called by `fast_write_response`).
272
+ # Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
273
+ # Large strings may not be written in one pass, especially if `io` is a
274
+ # `MiniSSL::Socket`.
275
+ # @param socket [#write_nonblock] the request/response socket
200
276
  # @param str [String] the string written to the io
201
277
  # @raise [ConnectionError]
202
278
  #
203
- def fast_write(io, str)
279
+ def fast_write_str(socket, str)
204
280
  n = 0
205
- while true
281
+ byte_size = str.bytesize
282
+ while n < byte_size
206
283
  begin
207
- n = io.syswrite str
284
+ n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
208
285
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
209
- unless io.wait_writable WRITE_TIMEOUT
210
- raise ConnectionError, "Socket timeout writing data"
286
+ unless socket.wait_writable WRITE_TIMEOUT
287
+ raise ConnectionError, SOCKET_WRITE_ERR_MSG
211
288
  end
212
-
213
289
  retry
214
290
  rescue Errno::EPIPE, SystemCallError, IOError
215
- raise ConnectionError, "Socket timeout writing data"
291
+ raise ConnectionError, SOCKET_WRITE_ERR_MSG
216
292
  end
217
-
218
- return if n == str.bytesize
219
- str = str.byteslice(n..-1)
220
293
  end
221
294
  end
222
- private :fast_write
223
295
 
224
- # @param status [Integer] status from the app
225
- # @return [String] the text description from Puma::HTTP_STATUS_CODES
296
+ # Used to write headers and body.
297
+ # Writes to a socket (normally `Client#io`) using `#fast_write_str`.
298
+ # Accumulates `body` items into `io_buffer`, then writes to socket.
299
+ # @param socket [#write] the response socket
300
+ # @param body [Enumerable, File] the body object
301
+ # @param io_buffer [Puma::IOBuffer] contains headers
302
+ # @param chunked [Boolean]
303
+ # @paramn content_length [Integer
304
+ # @raise [ConnectionError]
226
305
  #
227
- def fetch_status_code(status)
228
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
306
+ def fast_write_response(socket, body, io_buffer, chunked, content_length)
307
+ if body.is_a?(::File) && body.respond_to?(:read)
308
+ if chunked # would this ever happen?
309
+ while chunk = body.read(BODY_LEN_MAX)
310
+ io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
311
+ end
312
+ fast_write_str socket, CLOSE_CHUNKED
313
+ else
314
+ if content_length <= IO_BODY_MAX
315
+ io_buffer.write body.read(content_length)
316
+ fast_write_str socket, io_buffer.read_and_reset
317
+ else
318
+ fast_write_str socket, io_buffer.read_and_reset
319
+ IO.copy_stream body, socket
320
+ end
321
+ end
322
+ elsif body.is_a?(::Array) && body.length == 1
323
+ body_first = nil
324
+ # using body_first = body.first causes issues?
325
+ body.each { |str| body_first ||= str }
326
+
327
+ if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
328
+ # smaller body, write to io_buffer first
329
+ io_buffer.write body_first
330
+ fast_write_str socket, io_buffer.read_and_reset
331
+ else
332
+ # large body, write both header & body to socket
333
+ fast_write_str socket, io_buffer.read_and_reset
334
+ fast_write_str socket, body_first
335
+ end
336
+ elsif body.is_a?(::Array)
337
+ # for array bodies, flush io_buffer to socket when size is greater than
338
+ # IO_BUFFER_LEN_MAX
339
+ if chunked
340
+ body.each do |part|
341
+ next if (byte_size = part.bytesize).zero?
342
+ io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
343
+ if io_buffer.length > IO_BUFFER_LEN_MAX
344
+ fast_write_str socket, io_buffer.read_and_reset
345
+ end
346
+ end
347
+ io_buffer.write CLOSE_CHUNKED
348
+ else
349
+ body.each do |part|
350
+ next if part.bytesize.zero?
351
+ io_buffer.write part
352
+ if io_buffer.length > IO_BUFFER_LEN_MAX
353
+ fast_write_str socket, io_buffer.read_and_reset
354
+ end
355
+ end
356
+ end
357
+ # may write last body part for non-chunked, also headers if array is empty
358
+ fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
359
+ else
360
+ # for enum bodies
361
+ fast_write_str socket, io_buffer.read_and_reset
362
+ if chunked
363
+ body.each do |part|
364
+ next if (byte_size = part.bytesize).zero?
365
+ fast_write_str socket, (byte_size.to_s(16) << LINE_END)
366
+ fast_write_str socket, part
367
+ fast_write_str socket, LINE_END
368
+ end
369
+ fast_write_str socket, CLOSE_CHUNKED
370
+ else
371
+ body.each do |part|
372
+ next if part.bytesize.zero?
373
+ fast_write_str socket, part
374
+ end
375
+ end
376
+ end
377
+ socket.flush
378
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
379
+ raise ConnectionError, SOCKET_WRITE_ERR_MSG
380
+ rescue Errno::EPIPE, SystemCallError, IOError
381
+ raise ConnectionError, SOCKET_WRITE_ERR_MSG
229
382
  end
230
- private :fetch_status_code
383
+
384
+ private :fast_write_str, :fast_write_response
231
385
 
232
386
  # Given a Hash +env+ for the request read from +client+, add
233
387
  # and fixup keys to comply with Rack's env guidelines.
234
388
  # @param env [Hash] see Puma::Client#env, from request
235
389
  # @param client [Puma::Client] only needed for Client#peerip
236
- # @todo make private in 6.0.0
237
390
  #
238
391
  def normalize_env(env, client)
239
392
  if host = env[HTTP_HOST]
@@ -258,14 +411,12 @@ module Puma
258
411
  uri = URI.parse(env[REQUEST_URI])
259
412
  env[REQUEST_PATH] = uri.path
260
413
 
261
- raise "No REQUEST PATH" unless env[REQUEST_PATH]
262
-
263
414
  # A nil env value will cause a LintError (and fatal errors elsewhere),
264
415
  # so only set the env value if there actually is a value.
265
416
  env[QUERY_STRING] = uri.query if uri.query
266
417
  end
267
418
 
268
- env[PATH_INFO] = env[REQUEST_PATH]
419
+ env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
269
420
 
270
421
  # From https://www.ietf.org/rfc/rfc3875 :
271
422
  # "Script authors should be aware that the REMOTE_ADDR and
@@ -281,17 +432,31 @@ module Puma
281
432
  addr = client.peerip
282
433
  rescue Errno::ENOTCONN
283
434
  # Client disconnects can result in an inability to get the
284
- # peeraddr from the socket; default to localhost.
285
- addr = LOCALHOST_IP
435
+ # peeraddr from the socket; default to unspec.
436
+ if client.peer_family == Socket::AF_INET6
437
+ addr = UNSPECIFIED_IPV6
438
+ else
439
+ addr = UNSPECIFIED_IPV4
440
+ end
286
441
  end
287
442
 
288
443
  # Set unix socket addrs to localhost
289
- addr = LOCALHOST_IP if addr.empty?
444
+ if addr.empty?
445
+ if client.peer_family == Socket::AF_INET6
446
+ addr = LOCALHOST_IPV6
447
+ else
448
+ addr = LOCALHOST_IPV4
449
+ end
450
+ end
290
451
 
291
452
  env[REMOTE_ADDR] = addr
292
453
  end
454
+
455
+ # The legacy HTTP_VERSION header can be sent as a client header.
456
+ # Rack v4 may remove using HTTP_VERSION. If so, remove this line.
457
+ env[HTTP_VERSION] = env[SERVER_PROTOCOL]
293
458
  end
294
- # private :normalize_env
459
+ private :normalize_env
295
460
 
296
461
  # @param header_key [#to_s]
297
462
  # @return [Boolean]
@@ -322,7 +487,7 @@ module Puma
322
487
  to_add = nil
323
488
 
324
489
  env.each do |k,v|
325
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
490
+ if k.start_with?("HTTP_") && k.include?(",") && k != "HTTP_TRANSFER,ENCODING"
326
491
  if to_delete
327
492
  to_delete << k
328
493
  else
@@ -350,7 +515,7 @@ module Puma
350
515
  # @version 5.0.3
351
516
  #
352
517
  def str_early_hints(headers)
353
- eh_str = "HTTP/1.1 103 Early Hints\r\n".dup
518
+ eh_str = +"HTTP/1.1 103 Early Hints\r\n"
354
519
  headers.each_pair do |k, vs|
355
520
  next if illegal_header_key?(k)
356
521
 
@@ -367,66 +532,74 @@ module Puma
367
532
  end
368
533
  private :str_early_hints
369
534
 
535
+ # @param status [Integer] status from the app
536
+ # @return [String] the text description from Puma::HTTP_STATUS_CODES
537
+ #
538
+ def fetch_status_code(status)
539
+ HTTP_STATUS_CODES.fetch(status) { CUSTOM_STAT }
540
+ end
541
+ private :fetch_status_code
542
+
370
543
  # Processes and write headers to the IOBuffer.
371
544
  # @param env [Hash] see Puma::Client#env, from request
372
545
  # @param status [Integer] the status returned by the Rack application
373
546
  # @param headers [Hash] the headers returned by the Rack application
374
- # @param res_info [Hash] used to pass info between this method and #handle_request
375
- # @param lines [Puma::IOBuffer] modified inn place
376
- # @param requests [Integer] number of inline requests handled
377
- # @param client [Puma::Client]
547
+ # @param content_length [Integer,nil] content length if it can be determined from the
548
+ # response body
549
+ # @param io_buffer [Puma::IOBuffer] modified inn place
550
+ # @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
551
+ # status and `@max_fast_inline`
552
+ # @return [Hash] resp_info
378
553
  # @version 5.0.3
379
554
  #
380
- def str_headers(env, status, headers, res_info, lines, requests, client)
555
+ def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
556
+
381
557
  line_ending = LINE_END
382
558
  colon = COLON
383
559
 
384
- http_11 = env[HTTP_VERSION] == HTTP_11
560
+ resp_info = {}
561
+ resp_info[:no_body] = env[REQUEST_METHOD] == HEAD
562
+
563
+ http_11 = env[SERVER_PROTOCOL] == HTTP_11
385
564
  if http_11
386
- res_info[:allow_chunked] = true
387
- res_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
565
+ resp_info[:allow_chunked] = true
566
+ resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
388
567
 
389
568
  # An optimization. The most common response is 200, so we can
390
569
  # reply with the proper 200 status without having to compute
391
570
  # the response header.
392
571
  #
393
572
  if status == 200
394
- lines << HTTP_11_200
573
+ io_buffer << HTTP_11_200
395
574
  else
396
- lines.append "HTTP/1.1 ", status.to_s, " ",
397
- fetch_status_code(status), line_ending
575
+ io_buffer.append "#{HTTP_11} #{status} ", fetch_status_code(status), line_ending
398
576
 
399
- res_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
577
+ resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
400
578
  end
401
579
  else
402
- res_info[:allow_chunked] = false
403
- res_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
580
+ resp_info[:allow_chunked] = false
581
+ resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
404
582
 
405
583
  # Same optimization as above for HTTP/1.1
406
584
  #
407
585
  if status == 200
408
- lines << HTTP_10_200
586
+ io_buffer << HTTP_10_200
409
587
  else
410
- lines.append "HTTP/1.0 ", status.to_s, " ",
588
+ io_buffer.append "HTTP/1.0 #{status} ",
411
589
  fetch_status_code(status), line_ending
412
590
 
413
- res_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
591
+ resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
414
592
  end
415
593
  end
416
594
 
417
595
  # regardless of what the client wants, we always close the connection
418
596
  # if running without request queueing
419
- res_info[:keep_alive] &&= @queue_requests
597
+ resp_info[:keep_alive] &&= @queue_requests
420
598
 
421
- # Close the connection after a reasonable number of inline requests
422
- # if the server is at capacity and the listener has a new connection ready.
423
- # This allows Puma to service connections fairly when the number
424
- # of concurrent connections exceeds the size of the threadpool.
425
- res_info[:keep_alive] &&= requests < @max_fast_inline ||
426
- @thread_pool.busy_threads < @max_threads ||
427
- !client.listener.to_io.wait_readable(0)
599
+ # see prepare_response
600
+ resp_info[:keep_alive] &&= force_keep_alive
428
601
 
429
- res_info[:response_hijack] = nil
602
+ resp_info[:response_hijack] = nil
430
603
 
431
604
  headers.each do |k, vs|
432
605
  next if illegal_header_key?(k)
@@ -434,25 +607,34 @@ module Puma
434
607
  case k.downcase
435
608
  when CONTENT_LENGTH2
436
609
  next if illegal_header_value?(vs)
437
- res_info[:content_length] = vs
610
+ # nil.to_i is 0, nil&.to_i is nil
611
+ resp_info[:content_length] = vs&.to_i
438
612
  next
439
613
  when TRANSFER_ENCODING
440
- res_info[:allow_chunked] = false
441
- res_info[:content_length] = nil
614
+ resp_info[:allow_chunked] = false
615
+ resp_info[:content_length] = nil
616
+ resp_info[:transfer_encoding] = vs
442
617
  when HIJACK
443
- res_info[:response_hijack] = vs
618
+ resp_info[:response_hijack] = vs
444
619
  next
445
620
  when BANNED_HEADER_KEY
446
621
  next
447
622
  end
448
623
 
449
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
450
- vs.to_s.split(NEWLINE).each do |v|
624
+ ary = if vs.is_a?(::Array) && !vs.empty?
625
+ vs
626
+ elsif vs.respond_to?(:to_s) && !vs.to_s.empty?
627
+ vs.to_s.split NEWLINE
628
+ else
629
+ nil
630
+ end
631
+ if ary
632
+ ary.each do |v|
451
633
  next if illegal_header_value?(v)
452
- lines.append k, colon, v, line_ending
634
+ io_buffer.append k, colon, v, line_ending
453
635
  end
454
636
  else
455
- lines.append k, colon, line_ending
637
+ io_buffer.append k, colon, line_ending
456
638
  end
457
639
  end
458
640
 
@@ -462,10 +644,11 @@ module Puma
462
644
  # Only set the header if we're doing something which is not the default
463
645
  # for this protocol version
464
646
  if http_11
465
- lines << CONNECTION_CLOSE if !res_info[:keep_alive]
647
+ io_buffer << CONNECTION_CLOSE if !resp_info[:keep_alive]
466
648
  else
467
- lines << CONNECTION_KEEP_ALIVE if res_info[:keep_alive]
649
+ io_buffer << CONNECTION_KEEP_ALIVE if resp_info[:keep_alive]
468
650
  end
651
+ resp_info
469
652
  end
470
653
  private :str_headers
471
654
  end