puma 2.1.1 → 7.2.1

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