puma 7.2.2 → 8.0.0
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.
- checksums.yaml +4 -4
- data/History.md +39 -22
- data/README.md +1 -2
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- data/docs/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/signals.md +1 -1
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/mini_ssl.c +1 -8
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +168 -104
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +34 -44
- data/ext/puma_http11/puma_http11.c +3 -4
- data/lib/puma/cli.rb +1 -1
- data/lib/puma/client.rb +114 -196
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster.rb +1 -1
- data/lib/puma/configuration.rb +69 -8
- data/lib/puma/const.rb +3 -7
- data/lib/puma/control_cli.rb +4 -15
- data/lib/puma/detect.rb +11 -0
- data/lib/puma/dsl.rb +75 -22
- data/lib/puma/launcher.rb +3 -4
- data/lib/puma/minissl.rb +56 -101
- data/lib/puma/reactor.rb +3 -35
- data/lib/puma/{request.rb → response.rb} +16 -201
- data/lib/puma/server.rb +84 -120
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/thread_pool.rb +129 -23
- data/lib/puma/util.rb +4 -8
- data/lib/rack/handler/puma.rb +1 -1
- metadata +14 -3
data/lib/puma/minissl.rb
CHANGED
|
@@ -25,9 +25,6 @@ module Puma
|
|
|
25
25
|
@engine = engine
|
|
26
26
|
@peercert = nil
|
|
27
27
|
@reuse = nil
|
|
28
|
-
@write_buffer = +''
|
|
29
|
-
@write_owed = 0
|
|
30
|
-
@write_owed_data = nil
|
|
31
28
|
end
|
|
32
29
|
|
|
33
30
|
# @!attribute [r] to_io
|
|
@@ -71,8 +68,9 @@ module Puma
|
|
|
71
68
|
|
|
72
69
|
return output if output
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
while neg_data = @engine.extract
|
|
72
|
+
@socket.write neg_data
|
|
73
|
+
end
|
|
76
74
|
end
|
|
77
75
|
end
|
|
78
76
|
|
|
@@ -87,60 +85,52 @@ module Puma
|
|
|
87
85
|
def read_nonblock(size, *_)
|
|
88
86
|
# *_ is to deal with keyword args that were added
|
|
89
87
|
# at some point (and being used in the wild)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
end
|
|
88
|
+
while true
|
|
89
|
+
output = engine_read_all
|
|
90
|
+
return output if output
|
|
91
|
+
|
|
92
|
+
data = @socket.read_nonblock(size, exception: false)
|
|
93
|
+
if data == :wait_readable || data == :wait_writable
|
|
94
|
+
# It would make more sense to let @socket.read_nonblock raise
|
|
95
|
+
# EAGAIN if necessary but it seems like it'll misbehave on Windows.
|
|
96
|
+
# I don't have a Windows machine to debug this so I can't explain
|
|
97
|
+
# exactly whats happening in that OS. Please let me know if you
|
|
98
|
+
# find out!
|
|
99
|
+
#
|
|
100
|
+
# In the meantime, we can emulate the correct behavior by
|
|
101
|
+
# capturing :wait_readable & :wait_writable and raising EAGAIN
|
|
102
|
+
# ourselves.
|
|
103
|
+
raise IO::EAGAINWaitReadable
|
|
104
|
+
elsif data.nil?
|
|
105
|
+
raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
|
|
106
|
+
return nil
|
|
107
|
+
end
|
|
111
108
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return output if output
|
|
109
|
+
@engine.inject(data)
|
|
110
|
+
output = engine_read_all
|
|
115
111
|
|
|
116
|
-
|
|
117
|
-
flush_write_buffer_nonblock
|
|
118
|
-
raise IO::EAGAINWaitReadable
|
|
119
|
-
end
|
|
112
|
+
return output if output
|
|
120
113
|
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
while neg_data = @engine.extract
|
|
115
|
+
@socket.write neg_data
|
|
116
|
+
end
|
|
117
|
+
end
|
|
123
118
|
end
|
|
124
119
|
|
|
125
120
|
def write(data)
|
|
126
121
|
return 0 if data.empty?
|
|
127
122
|
|
|
128
123
|
data_size = data.bytesize
|
|
129
|
-
|
|
130
|
-
flush_write_buffer
|
|
131
|
-
if @write_owed_data && data.byteslice(0, @write_owed_data.bytesize).b == @write_owed_data
|
|
132
|
-
data = data.byteslice(@write_owed..-1)
|
|
133
|
-
@write_owed = 0
|
|
134
|
-
@write_owed_data = nil
|
|
135
|
-
return data_size if data.empty?
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
need = data.bytesize
|
|
124
|
+
need = data_size
|
|
139
125
|
|
|
140
126
|
while true
|
|
141
127
|
wrote = @engine.write data
|
|
142
|
-
|
|
143
|
-
|
|
128
|
+
|
|
129
|
+
enc_wr = +''
|
|
130
|
+
while (enc = @engine.extract)
|
|
131
|
+
enc_wr << enc
|
|
132
|
+
end
|
|
133
|
+
@socket.write enc_wr unless enc_wr.empty?
|
|
144
134
|
|
|
145
135
|
need -= wrote
|
|
146
136
|
|
|
@@ -153,43 +143,34 @@ module Puma
|
|
|
153
143
|
alias_method :syswrite, :write
|
|
154
144
|
alias_method :<<, :write
|
|
155
145
|
|
|
156
|
-
#
|
|
157
|
-
|
|
158
|
-
return 0 if data.empty?
|
|
159
|
-
|
|
160
|
-
@write_owed = 0 if @write_owed_data && data.byteslice(0, @write_owed_data.bytesize).b != @write_owed_data
|
|
146
|
+
# This is a temporary fix to deal with websockets code using
|
|
147
|
+
# write_nonblock.
|
|
161
148
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
wrote = @write_owed
|
|
175
|
-
@write_owed = 0
|
|
176
|
-
@write_owed_data = nil
|
|
177
|
-
wrote
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def wait_writable(timeout = nil)
|
|
181
|
-
@socket.wait_writable timeout
|
|
149
|
+
# The problem with implementing it properly
|
|
150
|
+
# is that it means we'd have to have the ability to rewind
|
|
151
|
+
# an engine because after we write+extract, the socket
|
|
152
|
+
# write_nonblock call might raise an exception and later
|
|
153
|
+
# code would pass the same data in, but the engine would think
|
|
154
|
+
# it had already written the data in.
|
|
155
|
+
#
|
|
156
|
+
# So for the time being (and since write blocking is quite rare),
|
|
157
|
+
# go ahead and actually block in write_nonblock.
|
|
158
|
+
#
|
|
159
|
+
def write_nonblock(data, *_)
|
|
160
|
+
write data
|
|
182
161
|
end
|
|
183
162
|
|
|
184
163
|
def flush
|
|
185
|
-
flush_write_buffer
|
|
186
164
|
@socket.flush
|
|
187
165
|
end
|
|
188
166
|
|
|
189
167
|
def close
|
|
190
168
|
begin
|
|
191
|
-
|
|
192
|
-
|
|
169
|
+
unless @engine.shutdown
|
|
170
|
+
while alert_data = @engine.extract
|
|
171
|
+
@socket.write alert_data
|
|
172
|
+
end
|
|
173
|
+
end
|
|
193
174
|
rescue IOError, SystemCallError
|
|
194
175
|
# nothing
|
|
195
176
|
ensure
|
|
@@ -197,32 +178,6 @@ module Puma
|
|
|
197
178
|
end
|
|
198
179
|
end
|
|
199
180
|
|
|
200
|
-
def queue_ciphertext
|
|
201
|
-
while (enc = @engine.extract)
|
|
202
|
-
@write_buffer << enc
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
private :queue_ciphertext
|
|
206
|
-
|
|
207
|
-
def flush_write_buffer
|
|
208
|
-
return if @write_buffer.empty?
|
|
209
|
-
|
|
210
|
-
@socket.write @write_buffer
|
|
211
|
-
@write_buffer = +''
|
|
212
|
-
end
|
|
213
|
-
private :flush_write_buffer
|
|
214
|
-
|
|
215
|
-
def flush_write_buffer_nonblock
|
|
216
|
-
until @write_buffer.empty?
|
|
217
|
-
n = @socket.write_nonblock @write_buffer, exception: false
|
|
218
|
-
return false if n == :wait_writable
|
|
219
|
-
|
|
220
|
-
@write_buffer = @write_buffer.byteslice(n..-1)
|
|
221
|
-
end
|
|
222
|
-
true
|
|
223
|
-
end
|
|
224
|
-
private :flush_write_buffer_nonblock
|
|
225
|
-
|
|
226
181
|
# @!attribute [r] peeraddr
|
|
227
182
|
def peeraddr
|
|
228
183
|
@socket.peeraddr
|
data/lib/puma/reactor.rb
CHANGED
|
@@ -34,7 +34,6 @@ module Puma
|
|
|
34
34
|
@selector = ::NIO::Selector.new(NIO::Selector.backends.delete(backend))
|
|
35
35
|
@input = Queue.new
|
|
36
36
|
@timeouts = []
|
|
37
|
-
@timeout_keys = {}.compare_by_identity
|
|
38
37
|
@block = block
|
|
39
38
|
@reactor_size = 0
|
|
40
39
|
@reactor_max = 0
|
|
@@ -78,13 +77,11 @@ module Puma
|
|
|
78
77
|
def select_loop
|
|
79
78
|
begin
|
|
80
79
|
until @input.closed? && @input.empty?
|
|
81
|
-
requeue_moved_timeouts
|
|
82
|
-
|
|
83
80
|
# Wakeup any registered object that receives incoming data.
|
|
84
81
|
# Block until the earliest timeout or Selector#wakeup is called.
|
|
85
82
|
timeout = (earliest = @timeouts.first) && earliest.timeout
|
|
86
83
|
@selector.select(timeout) do |monitor|
|
|
87
|
-
|
|
84
|
+
wakeup!(monitor.value)
|
|
88
85
|
end
|
|
89
86
|
|
|
90
87
|
# Wakeup all objects that timed out.
|
|
@@ -96,7 +93,7 @@ module Puma
|
|
|
96
93
|
client = @input.pop
|
|
97
94
|
register(client) if client.io_ok?
|
|
98
95
|
end
|
|
99
|
-
@timeouts.sort_by!
|
|
96
|
+
@timeouts.sort_by!(&:timeout_at)
|
|
100
97
|
end
|
|
101
98
|
end
|
|
102
99
|
rescue StandardError => e
|
|
@@ -113,19 +110,14 @@ module Puma
|
|
|
113
110
|
|
|
114
111
|
# Start monitoring the object.
|
|
115
112
|
def register(client)
|
|
116
|
-
@selector.register(client.to_io,
|
|
113
|
+
@selector.register(client.to_io, :r).value = client
|
|
117
114
|
@reactor_size += 1
|
|
118
115
|
@reactor_max = @reactor_size if @reactor_max < @reactor_size
|
|
119
116
|
@timeouts << client
|
|
120
|
-
@timeout_keys[client] = client.timeout_at
|
|
121
117
|
rescue ArgumentError
|
|
122
118
|
# unreadable clients raise error when processed by NIO
|
|
123
119
|
end
|
|
124
120
|
|
|
125
|
-
def interests(client)
|
|
126
|
-
client.write_pending? ? :w : :r
|
|
127
|
-
end
|
|
128
|
-
|
|
129
121
|
# 'Wake up' a monitored object by calling the provided block.
|
|
130
122
|
# Stop monitoring the object if the block returns `true`.
|
|
131
123
|
def wakeup!(client)
|
|
@@ -133,31 +125,7 @@ module Puma
|
|
|
133
125
|
@selector.deregister client.to_io
|
|
134
126
|
@reactor_size -= 1
|
|
135
127
|
@timeouts.delete client
|
|
136
|
-
@timeout_keys.delete client
|
|
137
|
-
true
|
|
138
|
-
elsif client.timeout_at < @timeout_keys[client]
|
|
139
|
-
@timeouts.delete client
|
|
140
|
-
requeue client
|
|
141
|
-
false
|
|
142
|
-
else
|
|
143
|
-
false
|
|
144
128
|
end
|
|
145
129
|
end
|
|
146
|
-
|
|
147
|
-
# `@timeouts` is sorted by the timeout each client had when it was queued.
|
|
148
|
-
# Timeouts that moved earlier are requeued in `wakeup!`. Timeouts that moved
|
|
149
|
-
# later are requeued here, once the client reaches the front.
|
|
150
|
-
def requeue_moved_timeouts
|
|
151
|
-
while (client = @timeouts.first) && @timeout_keys[client] < client.timeout_at
|
|
152
|
-
@timeouts.shift
|
|
153
|
-
requeue client
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def requeue(client)
|
|
158
|
-
key = @timeout_keys[client] = client.timeout_at
|
|
159
|
-
index = @timeouts.bsearch_index { |c| @timeout_keys[c] > key } || @timeouts.size
|
|
160
|
-
@timeouts.insert index, client
|
|
161
|
-
end
|
|
162
130
|
end
|
|
163
131
|
end
|
|
@@ -12,7 +12,7 @@ module Puma
|
|
|
12
12
|
# #handle_request, which is called in Server#process_client.
|
|
13
13
|
# @version 5.0.3
|
|
14
14
|
#
|
|
15
|
-
module
|
|
15
|
+
module Response # :nodoc:
|
|
16
16
|
|
|
17
17
|
# Single element array body: smaller bodies are written to io_buffer first,
|
|
18
18
|
# then a single write from io_buffer. Larger sizes are written separately.
|
|
@@ -46,10 +46,11 @@ module Puma
|
|
|
46
46
|
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
|
47
47
|
#
|
|
48
48
|
# Finally, it'll return +true+ on keep-alive connections.
|
|
49
|
+
# @param processor [Puma::ThreadPool::ProcessorThread]
|
|
49
50
|
# @param client [Puma::Client]
|
|
50
51
|
# @param requests [Integer]
|
|
51
52
|
# @return [:close, :keep_alive, :async]
|
|
52
|
-
def handle_request(client, requests)
|
|
53
|
+
def handle_request(processor, client, requests)
|
|
53
54
|
env = client.env
|
|
54
55
|
io_buffer = client.io_buffer
|
|
55
56
|
socket = client.io # io may be a MiniSSL::Socket
|
|
@@ -58,26 +59,6 @@ module Puma
|
|
|
58
59
|
|
|
59
60
|
return :close if closed_socket?(socket)
|
|
60
61
|
|
|
61
|
-
write_pending_interim socket, client
|
|
62
|
-
|
|
63
|
-
if client.http_content_length_limit_exceeded
|
|
64
|
-
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
normalize_env env, client
|
|
68
|
-
|
|
69
|
-
env[PUMA_SOCKET] = socket
|
|
70
|
-
|
|
71
|
-
if env[HTTPS_KEY] && socket.peercert
|
|
72
|
-
env[PUMA_PEERCERT] = socket.peercert
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
env[HIJACK_P] = true
|
|
76
|
-
env[HIJACK] = client.method :full_hijack
|
|
77
|
-
|
|
78
|
-
env[RACK_INPUT] = client.body
|
|
79
|
-
env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
|
80
|
-
|
|
81
62
|
if @early_hints
|
|
82
63
|
env[EARLY_HINTS] = lambda { |headers|
|
|
83
64
|
begin
|
|
@@ -91,22 +72,11 @@ module Puma
|
|
|
91
72
|
}
|
|
92
73
|
end
|
|
93
74
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# A rack extension. If the app writes #call'ables to this
|
|
97
|
-
# array, we will invoke them when the request is done.
|
|
98
|
-
#
|
|
99
|
-
env[RACK_AFTER_REPLY] ||= []
|
|
100
|
-
env[RACK_RESPONSE_FINISHED] ||= []
|
|
75
|
+
env["puma.mark_as_io_bound"] = -> { processor.mark_as_io_thread! }
|
|
101
76
|
|
|
102
77
|
begin
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
@app.call(env)
|
|
106
|
-
end
|
|
107
|
-
else
|
|
108
|
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
|
109
|
-
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
|
78
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
|
79
|
+
@app.call(env)
|
|
110
80
|
end
|
|
111
81
|
|
|
112
82
|
# app_body needs to always be closed, hold value in case lowlevel_error
|
|
@@ -138,7 +108,6 @@ module Puma
|
|
|
138
108
|
prepare_response(status, headers, res_body, requests, client)
|
|
139
109
|
ensure
|
|
140
110
|
io_buffer.reset
|
|
141
|
-
uncork_socket client.io
|
|
142
111
|
app_body.close if app_body.respond_to? :close
|
|
143
112
|
client&.tempfile_close
|
|
144
113
|
if after_reply = env[RACK_AFTER_REPLY]
|
|
@@ -179,8 +148,6 @@ module Puma
|
|
|
179
148
|
|
|
180
149
|
return :close if closed_socket?(socket)
|
|
181
150
|
|
|
182
|
-
write_pending_interim socket, client
|
|
183
|
-
|
|
184
151
|
# Close the connection after a reasonable number of inline requests
|
|
185
152
|
force_keep_alive = @enable_keep_alives && client.requests_served < @max_keep_alive
|
|
186
153
|
|
|
@@ -249,7 +216,8 @@ module Puma
|
|
|
249
216
|
|
|
250
217
|
io_buffer << LINE_END
|
|
251
218
|
fast_write_str socket, io_buffer.read_and_reset
|
|
252
|
-
|
|
219
|
+
|
|
220
|
+
uncork_socket socket.flush
|
|
253
221
|
return keep_alive ? :keep_alive : :close
|
|
254
222
|
end
|
|
255
223
|
else
|
|
@@ -268,40 +236,18 @@ module Puma
|
|
|
268
236
|
# response_hijack.call
|
|
269
237
|
if response_hijack
|
|
270
238
|
fast_write_str socket, io_buffer.read_and_reset
|
|
271
|
-
uncork_socket socket
|
|
239
|
+
uncork_socket socket.flush
|
|
272
240
|
response_hijack.call socket
|
|
273
241
|
return :async
|
|
274
242
|
end
|
|
275
243
|
|
|
276
244
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
|
277
245
|
body.close if close_body
|
|
246
|
+
|
|
278
247
|
# if we're shutting down, close keep_alive connections
|
|
279
248
|
!shutting_down? && keep_alive ? :keep_alive : :close
|
|
280
249
|
end
|
|
281
250
|
|
|
282
|
-
HTTP_ON_VALUES = { "on" => true, HTTPS => true }
|
|
283
|
-
private_constant :HTTP_ON_VALUES
|
|
284
|
-
|
|
285
|
-
# @param env [Hash] see Puma::Client#env, from request
|
|
286
|
-
# @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
|
|
287
|
-
#
|
|
288
|
-
def default_server_port(env)
|
|
289
|
-
if HTTP_ON_VALUES[env[HTTPS_KEY]] ||
|
|
290
|
-
env[HTTP_X_FORWARDED_PROTO]&.start_with?(HTTPS) ||
|
|
291
|
-
env[HTTP_X_FORWARDED_SCHEME] == HTTPS ||
|
|
292
|
-
env[HTTP_X_FORWARDED_SSL] == "on"
|
|
293
|
-
PORT_443
|
|
294
|
-
else
|
|
295
|
-
PORT_80
|
|
296
|
-
end
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
def write_pending_interim(socket, client)
|
|
300
|
-
pending = client.take_pending_write
|
|
301
|
-
fast_write_str socket, pending if pending
|
|
302
|
-
end
|
|
303
|
-
private :write_pending_interim
|
|
304
|
-
|
|
305
251
|
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
|
306
252
|
# and body segments (called by `fast_write_response`).
|
|
307
253
|
# Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
|
|
@@ -351,11 +297,7 @@ module Puma
|
|
|
351
297
|
fast_write_str socket, io_buffer.read_and_reset
|
|
352
298
|
else
|
|
353
299
|
fast_write_str socket, io_buffer.read_and_reset
|
|
354
|
-
|
|
355
|
-
while remaining > 0 && (chunk = body.read([remaining, BODY_LEN_MAX].min))
|
|
356
|
-
fast_write_str socket, chunk
|
|
357
|
-
remaining -= chunk.bytesize
|
|
358
|
-
end
|
|
300
|
+
IO.copy_stream body, socket
|
|
359
301
|
end
|
|
360
302
|
end
|
|
361
303
|
elsif body.is_a?(::Array) && body.length == 1
|
|
@@ -420,6 +362,7 @@ module Puma
|
|
|
420
362
|
end
|
|
421
363
|
end
|
|
422
364
|
socket.flush
|
|
365
|
+
uncork_socket socket
|
|
423
366
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
424
367
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
|
425
368
|
rescue Errno::EPIPE, SystemCallError, IOError
|
|
@@ -428,85 +371,6 @@ module Puma
|
|
|
428
371
|
|
|
429
372
|
private :fast_write_str, :fast_write_response
|
|
430
373
|
|
|
431
|
-
# Given a Hash +env+ for the request read from +client+, add
|
|
432
|
-
# and fixup keys to comply with Rack's env guidelines.
|
|
433
|
-
# @param env [Hash] see Puma::Client#env, from request
|
|
434
|
-
# @param client [Puma::Client] only needed for Client#peerip
|
|
435
|
-
#
|
|
436
|
-
def normalize_env(env, client)
|
|
437
|
-
if host = env[HTTP_HOST]
|
|
438
|
-
# host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
|
|
439
|
-
if colon = host.rindex("]:") # IPV6 with port
|
|
440
|
-
env[SERVER_NAME] = host[0, colon+1]
|
|
441
|
-
env[SERVER_PORT] = host[colon+2, host.bytesize]
|
|
442
|
-
elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
|
|
443
|
-
env[SERVER_NAME] = host[0, colon]
|
|
444
|
-
env[SERVER_PORT] = host[colon+1, host.bytesize]
|
|
445
|
-
else
|
|
446
|
-
env[SERVER_NAME] = host
|
|
447
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
448
|
-
end
|
|
449
|
-
else
|
|
450
|
-
env[SERVER_NAME] = LOCALHOST
|
|
451
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
452
|
-
end
|
|
453
|
-
|
|
454
|
-
unless env[REQUEST_PATH]
|
|
455
|
-
# it might be a dumbass full host request header
|
|
456
|
-
uri = begin
|
|
457
|
-
URI.parse(env[REQUEST_URI])
|
|
458
|
-
rescue URI::InvalidURIError
|
|
459
|
-
raise Puma::HttpParserError
|
|
460
|
-
end
|
|
461
|
-
env[REQUEST_PATH] = uri.path
|
|
462
|
-
|
|
463
|
-
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
|
464
|
-
# so only set the env value if there actually is a value.
|
|
465
|
-
env[QUERY_STRING] = uri.query if uri.query
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
|
|
469
|
-
|
|
470
|
-
# From https://www.ietf.org/rfc/rfc3875 :
|
|
471
|
-
# "Script authors should be aware that the REMOTE_ADDR and
|
|
472
|
-
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
|
473
|
-
# may not identify the ultimate source of the request.
|
|
474
|
-
# They identify the client for the immediate request to the
|
|
475
|
-
# server; that client may be a proxy, gateway, or other
|
|
476
|
-
# intermediary acting on behalf of the actual source client."
|
|
477
|
-
#
|
|
478
|
-
|
|
479
|
-
unless env.key?(REMOTE_ADDR)
|
|
480
|
-
begin
|
|
481
|
-
addr = client.peerip
|
|
482
|
-
rescue Errno::ENOTCONN
|
|
483
|
-
# Client disconnects can result in an inability to get the
|
|
484
|
-
# peeraddr from the socket; default to unspec.
|
|
485
|
-
if client.peer_family == Socket::AF_INET6
|
|
486
|
-
addr = UNSPECIFIED_IPV6
|
|
487
|
-
else
|
|
488
|
-
addr = UNSPECIFIED_IPV4
|
|
489
|
-
end
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
# Set unix socket addrs to localhost
|
|
493
|
-
if addr.empty?
|
|
494
|
-
if client.peer_family == Socket::AF_INET6
|
|
495
|
-
addr = LOCALHOST_IPV6
|
|
496
|
-
else
|
|
497
|
-
addr = LOCALHOST_IPV4
|
|
498
|
-
end
|
|
499
|
-
end
|
|
500
|
-
|
|
501
|
-
env[REMOTE_ADDR] = addr
|
|
502
|
-
end
|
|
503
|
-
|
|
504
|
-
# The legacy HTTP_VERSION header can be sent as a client header.
|
|
505
|
-
# Rack v4 may remove using HTTP_VERSION. If so, remove this line.
|
|
506
|
-
env[HTTP_VERSION] = env[SERVER_PROTOCOL] if @env_set_http_version
|
|
507
|
-
end
|
|
508
|
-
private :normalize_env
|
|
509
|
-
|
|
510
374
|
# @param header_key [#to_s]
|
|
511
375
|
# @return [Boolean]
|
|
512
376
|
#
|
|
@@ -520,56 +384,6 @@ module Puma
|
|
|
520
384
|
def illegal_header_value?(header_value)
|
|
521
385
|
!!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
|
|
522
386
|
end
|
|
523
|
-
private :illegal_header_key?, :illegal_header_value?
|
|
524
|
-
|
|
525
|
-
# Fixup any headers with `,` in the name to have `_` now. We emit
|
|
526
|
-
# headers with `,` in them during the parse phase to avoid ambiguity
|
|
527
|
-
# with the `-` to `_` conversion for critical headers. But here for
|
|
528
|
-
# compatibility, we'll convert them back. This code is written to
|
|
529
|
-
# avoid allocation in the common case (ie there are no headers
|
|
530
|
-
# with `,` in their names), that's why it has the extra conditionals.
|
|
531
|
-
#
|
|
532
|
-
# @note If a normalized version of a `,` header already exists, we ignore
|
|
533
|
-
# the `,` version. This prevents clobbering headers managed by proxies
|
|
534
|
-
# but not by clients (Like X-Forwarded-For).
|
|
535
|
-
#
|
|
536
|
-
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
|
537
|
-
# @version 5.0.3
|
|
538
|
-
#
|
|
539
|
-
def req_env_post_parse(env)
|
|
540
|
-
to_delete = nil
|
|
541
|
-
to_add = nil
|
|
542
|
-
|
|
543
|
-
env.each do |k,v|
|
|
544
|
-
if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
|
|
545
|
-
if to_delete
|
|
546
|
-
to_delete << k
|
|
547
|
-
else
|
|
548
|
-
to_delete = [k]
|
|
549
|
-
end
|
|
550
|
-
|
|
551
|
-
new_k = k.tr(",", "_")
|
|
552
|
-
if env.key?(new_k)
|
|
553
|
-
next
|
|
554
|
-
end
|
|
555
|
-
|
|
556
|
-
unless to_add
|
|
557
|
-
to_add = {}
|
|
558
|
-
end
|
|
559
|
-
|
|
560
|
-
to_add[new_k] = v
|
|
561
|
-
end
|
|
562
|
-
end
|
|
563
|
-
|
|
564
|
-
if to_delete # rubocop:disable Style/SafeNavigation
|
|
565
|
-
to_delete.each { |k| env.delete(k) }
|
|
566
|
-
end
|
|
567
|
-
|
|
568
|
-
if to_add
|
|
569
|
-
env.merge! to_add
|
|
570
|
-
end
|
|
571
|
-
end
|
|
572
|
-
private :req_env_post_parse
|
|
573
387
|
|
|
574
388
|
# Used in the lambda for env[ `Puma::Const::EARLY_HINTS` ]
|
|
575
389
|
# @param headers [Hash] the headers returned by the Rack application
|
|
@@ -666,7 +480,8 @@ module Puma
|
|
|
666
480
|
headers.each do |k, vs|
|
|
667
481
|
next if illegal_header_key?(k)
|
|
668
482
|
|
|
669
|
-
|
|
483
|
+
key = k.downcase
|
|
484
|
+
case key
|
|
670
485
|
when CONTENT_LENGTH2
|
|
671
486
|
next if illegal_header_value?(vs)
|
|
672
487
|
# nil.to_i is 0, nil&.to_i is nil
|
|
@@ -693,10 +508,10 @@ module Puma
|
|
|
693
508
|
if ary
|
|
694
509
|
ary.each do |v|
|
|
695
510
|
next if illegal_header_value?(v)
|
|
696
|
-
io_buffer.append
|
|
511
|
+
io_buffer.append key, colon, v, line_ending
|
|
697
512
|
end
|
|
698
513
|
else
|
|
699
|
-
io_buffer.append
|
|
514
|
+
io_buffer.append key, colon, line_ending
|
|
700
515
|
end
|
|
701
516
|
end
|
|
702
517
|
|