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