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