puma 3.12.6 → 6.2.2

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