raptor 0.1.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 +7 -0
- data/.buildkite/pipeline.yml +36 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +86 -0
- data/Rakefile +28 -0
- data/exe/raptor +8 -0
- data/ext/raptor_http/extconf.rb +7 -0
- data/ext/raptor_http/raptor_http.c +1248 -0
- data/ext/raptor_http2/extconf.rb +7 -0
- data/ext/raptor_http2/huffman_table.h +4888 -0
- data/ext/raptor_http2/raptor_http2.c +772 -0
- data/lib/raptor/binder.rb +249 -0
- data/lib/raptor/cli.rb +171 -0
- data/lib/raptor/cluster.rb +357 -0
- data/lib/raptor/http2.rb +416 -0
- data/lib/raptor/reactor.rb +411 -0
- data/lib/raptor/request.rb +992 -0
- data/lib/raptor/server.rb +167 -0
- data/lib/raptor/stats.rb +94 -0
- data/lib/raptor/version.rb +6 -0
- data/lib/raptor.rb +13 -0
- data/sig/generated/raptor/binder.rbs +162 -0
- data/sig/generated/raptor/cli.rbs +71 -0
- data/sig/generated/raptor/cluster.rbs +171 -0
- data/sig/generated/raptor/http2.rbs +145 -0
- data/sig/generated/raptor/reactor.rbs +251 -0
- data/sig/generated/raptor/request.rbs +477 -0
- data/sig/generated/raptor/server.rbs +88 -0
- data/sig/generated/raptor/stats.rbs +78 -0
- data/sig/generated/raptor/version.rbs +5 -0
- data/sig/generated/raptor.rbs +9 -0
- metadata +160 -0
data/lib/raptor/http2.rb
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "stringio"
|
|
5
|
+
|
|
6
|
+
require "rack"
|
|
7
|
+
|
|
8
|
+
require_relative "raptor_http2"
|
|
9
|
+
|
|
10
|
+
module Raptor
|
|
11
|
+
# Handles HTTP/2 request processing and Rack application integration.
|
|
12
|
+
#
|
|
13
|
+
# Http2 manages the HTTP/2 protocol lifecycle including frame processing,
|
|
14
|
+
# HPACK header compression, stream management, and response writing.
|
|
15
|
+
# It integrates with the same reactor, ractor pool, and thread pool
|
|
16
|
+
# pipeline used by HTTP/1.1 connections.
|
|
17
|
+
#
|
|
18
|
+
class Http2
|
|
19
|
+
FLAG_END_STREAM = 0x1
|
|
20
|
+
FLAG_END_HEADERS = 0x4
|
|
21
|
+
FLAG_ACK = 0x1
|
|
22
|
+
FLAG_PRIORITY = 0x20
|
|
23
|
+
|
|
24
|
+
SERVER_PROTOCOL = "HTTP/2"
|
|
25
|
+
RACK_HEADER_PREFIX = "rack."
|
|
26
|
+
HOP_BY_HOP_HEADERS = Set.new(%w[connection transfer-encoding keep-alive upgrade proxy-connection]).freeze
|
|
27
|
+
|
|
28
|
+
# @rbs @app: ^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped]
|
|
29
|
+
# @rbs @server_port: Integer
|
|
30
|
+
|
|
31
|
+
# Creates a new Http2 handler.
|
|
32
|
+
#
|
|
33
|
+
# @param app [#call] the Rack application to dispatch requests to
|
|
34
|
+
# @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
|
|
35
|
+
# @return [void]
|
|
36
|
+
#
|
|
37
|
+
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port) -> void
|
|
38
|
+
def initialize(app, server_port)
|
|
39
|
+
@app = app
|
|
40
|
+
@server_port = server_port
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Builds the initial server SETTINGS frame to send on connection establishment.
|
|
44
|
+
#
|
|
45
|
+
# @return [String] the encoded SETTINGS frame
|
|
46
|
+
#
|
|
47
|
+
# @rbs () -> String
|
|
48
|
+
def self.build_server_settings_frame
|
|
49
|
+
parser = Http2Parser.new
|
|
50
|
+
settings_payload = parser.build_settings(
|
|
51
|
+
max_concurrent_streams: 100,
|
|
52
|
+
initial_window_size: 65_535
|
|
53
|
+
)
|
|
54
|
+
parser.build_frame(:settings, 0, 0, settings_payload)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Processes HTTP/2 frames from the connection buffer.
|
|
58
|
+
#
|
|
59
|
+
# Parses frames, handles HPACK decoding, tracks stream state, and returns
|
|
60
|
+
# updated connection state along with any outgoing protocol frames and
|
|
61
|
+
# completed stream requests. Ractor-safe.
|
|
62
|
+
#
|
|
63
|
+
# @param data [Hash] the connection state including buffer and HPACK table
|
|
64
|
+
# @return [Hash] updated state with outgoing_frames and completed_requests
|
|
65
|
+
#
|
|
66
|
+
# @rbs (Hash[Symbol, untyped] data) -> Hash[Symbol, untyped]
|
|
67
|
+
def self.process_frames(data)
|
|
68
|
+
parser = Http2Parser.new
|
|
69
|
+
buffer = data[:buffer]
|
|
70
|
+
hpack_table = data[:hpack_table] || []
|
|
71
|
+
streams = data[:http2_streams] ? data[:http2_streams].dup : {}
|
|
72
|
+
outgoing_frames = []
|
|
73
|
+
completed_requests = []
|
|
74
|
+
connection_window = data[:http2_window] || 65_535
|
|
75
|
+
preface_received = data[:http2_preface_received] || false
|
|
76
|
+
|
|
77
|
+
unless preface_received
|
|
78
|
+
if buffer.bytesize >= 24 && buffer.byteslice(0, 24) == Http2Parser.connection_preface
|
|
79
|
+
buffer = buffer.byteslice(24..-1) || ""
|
|
80
|
+
preface_received = true
|
|
81
|
+
else
|
|
82
|
+
return build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, connection_window, preface_received)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
loop do
|
|
87
|
+
parsed = parser.parse_frame(buffer)
|
|
88
|
+
break unless parsed
|
|
89
|
+
|
|
90
|
+
frame, consumed = parsed
|
|
91
|
+
buffer = buffer.byteslice(consumed..-1) || ""
|
|
92
|
+
|
|
93
|
+
case frame[:type]
|
|
94
|
+
when :settings
|
|
95
|
+
if (frame[:flags] & FLAG_ACK).zero?
|
|
96
|
+
outgoing_frames << parser.build_frame(:settings, FLAG_ACK, 0, nil)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
when :headers
|
|
100
|
+
stream_id = frame[:stream_id]
|
|
101
|
+
header_payload = frame[:payload]
|
|
102
|
+
|
|
103
|
+
if (frame[:flags] & FLAG_PRIORITY) != 0
|
|
104
|
+
header_payload = header_payload.byteslice(5..-1) || ""
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
decoded_headers, hpack_table = parser.parse_headers(header_payload, hpack_table)
|
|
108
|
+
stream = streams[stream_id] || {}
|
|
109
|
+
stream = stream.merge(headers: decoded_headers)
|
|
110
|
+
|
|
111
|
+
if (frame[:flags] & FLAG_END_STREAM) != 0
|
|
112
|
+
stream = stream.merge(end_stream: true)
|
|
113
|
+
completed_requests << {
|
|
114
|
+
stream_id: stream_id,
|
|
115
|
+
headers: decoded_headers,
|
|
116
|
+
body: stream[:body] || ""
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
streams.delete(stream_id)
|
|
120
|
+
else
|
|
121
|
+
streams[stream_id] = stream
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
when :data
|
|
125
|
+
stream_id = frame[:stream_id]
|
|
126
|
+
stream = streams[stream_id] || {}
|
|
127
|
+
existing_body = stream[:body] || ""
|
|
128
|
+
stream = stream.merge(body: existing_body + frame[:payload])
|
|
129
|
+
|
|
130
|
+
if frame[:payload].bytesize.positive?
|
|
131
|
+
connection_window -= frame[:payload].bytesize
|
|
132
|
+
if connection_window < 32_768
|
|
133
|
+
increment = 65_535 - connection_window
|
|
134
|
+
wu_payload = [increment].pack("N")
|
|
135
|
+
outgoing_frames << parser.build_frame(:window_update, 0, 0, wu_payload)
|
|
136
|
+
outgoing_frames << parser.build_frame(:window_update, 0, stream_id, wu_payload)
|
|
137
|
+
connection_window += increment
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if (frame[:flags] & FLAG_END_STREAM) != 0
|
|
142
|
+
stream_headers = stream[:headers] || []
|
|
143
|
+
completed_requests << {
|
|
144
|
+
stream_id: stream_id,
|
|
145
|
+
headers: stream_headers,
|
|
146
|
+
body: stream[:body]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
streams.delete(stream_id)
|
|
150
|
+
else
|
|
151
|
+
streams[stream_id] = stream
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
when :window_update
|
|
155
|
+
parser.parse_window_update(frame[:payload])
|
|
156
|
+
|
|
157
|
+
when :ping
|
|
158
|
+
if (frame[:flags] & FLAG_ACK).zero?
|
|
159
|
+
outgoing_frames << parser.build_frame(:ping, FLAG_ACK, 0, frame[:payload])
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
when :goaway
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
when :rst_stream
|
|
166
|
+
streams.delete(frame[:stream_id])
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, connection_window, preface_received)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Builds a frozen result hash from the current processing state.
|
|
174
|
+
#
|
|
175
|
+
# @param data [Hash] original connection state
|
|
176
|
+
# @param buffer [String] remaining unparsed data
|
|
177
|
+
# @param hpack_table [Array] updated HPACK dynamic table
|
|
178
|
+
# @param streams [Hash] updated stream states
|
|
179
|
+
# @param outgoing_frames [Array<String>] frames to write to the socket
|
|
180
|
+
# @param completed_requests [Array<Hash>] fully received stream requests
|
|
181
|
+
# @param connection_window [Integer] current connection flow control window
|
|
182
|
+
# @param preface_received [Boolean] whether the connection preface has been received
|
|
183
|
+
# @return [Hash] frozen result hash
|
|
184
|
+
#
|
|
185
|
+
# @rbs (Hash[Symbol, untyped] data, String buffer, Array[untyped] hpack_table, Hash[Integer, Hash[Symbol, untyped]] streams, Array[String] outgoing_frames, Array[Hash[Symbol, untyped]] completed_requests, Integer connection_window, bool preface_received) -> Hash[Symbol, untyped]
|
|
186
|
+
def self.build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, connection_window, preface_received)
|
|
187
|
+
Ractor.make_shareable({
|
|
188
|
+
id: data[:id],
|
|
189
|
+
protocol: :http2,
|
|
190
|
+
buffer: buffer || "",
|
|
191
|
+
hpack_table: hpack_table,
|
|
192
|
+
http2_streams: streams,
|
|
193
|
+
http2_window: connection_window,
|
|
194
|
+
http2_preface_received: preface_received,
|
|
195
|
+
outgoing_frames: outgoing_frames,
|
|
196
|
+
completed_requests: completed_requests,
|
|
197
|
+
remote_addr: data[:remote_addr],
|
|
198
|
+
url_scheme: data[:url_scheme]
|
|
199
|
+
})
|
|
200
|
+
end
|
|
201
|
+
private_class_method :build_result
|
|
202
|
+
|
|
203
|
+
# Handles a parsed HTTP/2 request from the ractor pool.
|
|
204
|
+
#
|
|
205
|
+
# Writes outgoing protocol frames to the socket, updates reactor state,
|
|
206
|
+
# and dispatches completed stream requests to the thread pool.
|
|
207
|
+
#
|
|
208
|
+
# @param result [Hash] the parsed result from the ractor pool
|
|
209
|
+
# @param reactor [Reactor] the reactor managing the connection
|
|
210
|
+
# @param thread_pool [AtomicThreadPool] thread pool for Rack app dispatch
|
|
211
|
+
# @return [void]
|
|
212
|
+
#
|
|
213
|
+
# @rbs (Hash[Symbol, untyped] result, Reactor reactor, AtomicThreadPool thread_pool) -> void
|
|
214
|
+
def handle_parsed_request(result, reactor, thread_pool)
|
|
215
|
+
socket = reactor.socket_for(result[:id])
|
|
216
|
+
return unless socket
|
|
217
|
+
|
|
218
|
+
mutex = reactor.mutex_for(result[:id])
|
|
219
|
+
|
|
220
|
+
if result[:outgoing_frames]&.any?
|
|
221
|
+
mutex.synchronize do
|
|
222
|
+
result[:outgoing_frames].each { |frame| socket.write(frame) rescue nil }
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
reactor.update_http2_state(result)
|
|
227
|
+
|
|
228
|
+
result[:completed_requests]&.each do |request|
|
|
229
|
+
stream_id = request[:stream_id]
|
|
230
|
+
remote_addr = result[:remote_addr] || "127.0.0.1"
|
|
231
|
+
|
|
232
|
+
thread_pool << proc do
|
|
233
|
+
dispatch_stream_request(
|
|
234
|
+
socket, mutex, stream_id,
|
|
235
|
+
request[:headers], request[:body],
|
|
236
|
+
remote_addr: remote_addr
|
|
237
|
+
)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
private
|
|
243
|
+
|
|
244
|
+
# Dispatches a completed stream request to the Rack app and writes
|
|
245
|
+
# the response back as HTTP/2 frames.
|
|
246
|
+
#
|
|
247
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
248
|
+
# @param mutex [Mutex] mutex for serializing writes to the connection socket
|
|
249
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
250
|
+
# @param headers [Array<Array(String, String)>] request headers
|
|
251
|
+
# @param body [String] request body
|
|
252
|
+
# @param remote_addr [String] the client IP address
|
|
253
|
+
# @return [void]
|
|
254
|
+
#
|
|
255
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
256
|
+
def dispatch_stream_request(socket, mutex, stream_id, headers, body, remote_addr:)
|
|
257
|
+
env = build_rack_env(headers, body, remote_addr: remote_addr)
|
|
258
|
+
status, response_headers, response_body = @app.call(env)
|
|
259
|
+
|
|
260
|
+
write_http2_response(socket, mutex, stream_id, status, response_headers, response_body)
|
|
261
|
+
rescue
|
|
262
|
+
write_http2_error_response(socket, mutex, stream_id)
|
|
263
|
+
raise
|
|
264
|
+
ensure
|
|
265
|
+
response_body.close if response_body.respond_to?(:close)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Writes a Rack response as HTTP/2 frames to the socket.
|
|
269
|
+
#
|
|
270
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
271
|
+
# @param mutex [Mutex] mutex for serializing writes to the connection socket
|
|
272
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
273
|
+
# @param status [Integer] HTTP status code
|
|
274
|
+
# @param headers [Hash] response headers from the Rack application
|
|
275
|
+
# @param body [Object] response body responding to each
|
|
276
|
+
# @return [void]
|
|
277
|
+
#
|
|
278
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
|
|
279
|
+
def write_http2_response(socket, mutex, stream_id, status, headers, body)
|
|
280
|
+
parser = Http2Parser.new
|
|
281
|
+
|
|
282
|
+
header_pairs = [[":status", status.to_s]]
|
|
283
|
+
headers.each do |name, value|
|
|
284
|
+
lowered = name.downcase
|
|
285
|
+
next if lowered.start_with?(RACK_HEADER_PREFIX)
|
|
286
|
+
next if HOP_BY_HOP_HEADERS.include?(lowered)
|
|
287
|
+
|
|
288
|
+
if value.is_a?(Array)
|
|
289
|
+
value.each { |val| header_pairs << [lowered, val.to_s] }
|
|
290
|
+
else
|
|
291
|
+
header_pairs << [lowered, value.to_s]
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
encoded_headers = parser.encode_headers(header_pairs)
|
|
296
|
+
body_chunks = []
|
|
297
|
+
body.each { |chunk| body_chunks << chunk unless chunk.empty? }
|
|
298
|
+
|
|
299
|
+
mutex.synchronize do
|
|
300
|
+
if body_chunks.empty?
|
|
301
|
+
socket.write(parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded_headers))
|
|
302
|
+
else
|
|
303
|
+
socket.write(parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded_headers))
|
|
304
|
+
|
|
305
|
+
body_chunks.each_with_index do |chunk, index|
|
|
306
|
+
last = index == body_chunks.size - 1
|
|
307
|
+
flags = last ? FLAG_END_STREAM : 0
|
|
308
|
+
socket.write(parser.build_frame(:data, flags, stream_id, chunk))
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
rescue IOError, Errno::EPIPE
|
|
313
|
+
# Connection closed
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Writes a 500 error response as HTTP/2 frames.
|
|
317
|
+
#
|
|
318
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
319
|
+
# @param mutex [Mutex] mutex for serializing writes to the connection socket
|
|
320
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
321
|
+
# @return [void]
|
|
322
|
+
#
|
|
323
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id) -> void
|
|
324
|
+
def write_http2_error_response(socket, mutex, stream_id)
|
|
325
|
+
parser = Http2Parser.new
|
|
326
|
+
encoded = parser.encode_headers([[":status", "500"]])
|
|
327
|
+
|
|
328
|
+
mutex.synchronize do
|
|
329
|
+
socket.write(parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded))
|
|
330
|
+
end
|
|
331
|
+
rescue IOError, Errno::EPIPE
|
|
332
|
+
# Connection closed
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Builds a Rack environment hash from HTTP/2 headers and body.
|
|
336
|
+
#
|
|
337
|
+
# Translates HTTP/2 pseudo-headers into Rack-compatible environment keys
|
|
338
|
+
# and populates all required Rack env entries.
|
|
339
|
+
#
|
|
340
|
+
# @param headers [Array<Array(String, String)>] HTTP/2 header pairs
|
|
341
|
+
# @param body [String] the request body
|
|
342
|
+
# @param remote_addr [String] the client IP address
|
|
343
|
+
# @return [Hash] fully populated Rack environment hash
|
|
344
|
+
#
|
|
345
|
+
# @rbs (Array[[String, String]] headers, String body, remote_addr: String) -> Hash[String, untyped]
|
|
346
|
+
def build_rack_env(headers, body, remote_addr:)
|
|
347
|
+
env = {}
|
|
348
|
+
|
|
349
|
+
headers.each do |name, value|
|
|
350
|
+
if name.start_with?(":")
|
|
351
|
+
case name
|
|
352
|
+
when ":method" then env[Rack::REQUEST_METHOD] = value
|
|
353
|
+
when ":path"
|
|
354
|
+
path, query = value.split("?", 2)
|
|
355
|
+
env[Rack::PATH_INFO] = path
|
|
356
|
+
env[Rack::QUERY_STRING] = query || ""
|
|
357
|
+
when ":scheme" then env[Rack::RACK_URL_SCHEME] = value
|
|
358
|
+
when ":authority" then env[Rack::HTTP_HOST] = value
|
|
359
|
+
end
|
|
360
|
+
elsif name == "content-type"
|
|
361
|
+
env["CONTENT_TYPE"] = value
|
|
362
|
+
elsif name == "content-length"
|
|
363
|
+
env["CONTENT_LENGTH"] = value
|
|
364
|
+
else
|
|
365
|
+
rack_key = "HTTP_#{name.upcase.tr("-", "_")}"
|
|
366
|
+
env[rack_key] = value
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
env[Rack::SERVER_PROTOCOL] = SERVER_PROTOCOL
|
|
371
|
+
env[Rack::RACK_VERSION] = Rack::VERSION
|
|
372
|
+
env[Rack::RACK_INPUT] = StringIO.new(body).set_encoding(Encoding::ASCII_8BIT)
|
|
373
|
+
env[Rack::RACK_ERRORS] = $stderr
|
|
374
|
+
env[Rack::RACK_RESPONSE_FINISHED] = []
|
|
375
|
+
env[Rack::RACK_IS_HIJACK] = false
|
|
376
|
+
|
|
377
|
+
env[Rack::SCRIPT_NAME] = "" unless env.key?(Rack::SCRIPT_NAME)
|
|
378
|
+
env[Rack::PATH_INFO] = "" unless env.key?(Rack::PATH_INFO)
|
|
379
|
+
env[Rack::QUERY_STRING] = "" unless env.key?(Rack::QUERY_STRING)
|
|
380
|
+
|
|
381
|
+
if body.bytesize.positive? && !env.key?("CONTENT_LENGTH")
|
|
382
|
+
env["CONTENT_LENGTH"] = body.bytesize.to_s
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
env["REMOTE_ADDR"] = remote_addr
|
|
386
|
+
|
|
387
|
+
populate_server_name_and_port(env)
|
|
388
|
+
|
|
389
|
+
env
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Populates SERVER_NAME and SERVER_PORT from the HTTP_HOST header.
|
|
393
|
+
#
|
|
394
|
+
# @param env [Hash] the Rack environment to populate
|
|
395
|
+
# @return [void]
|
|
396
|
+
#
|
|
397
|
+
# @rbs (Hash[String, untyped] env) -> void
|
|
398
|
+
def populate_server_name_and_port(env)
|
|
399
|
+
http_host = env[Rack::HTTP_HOST]
|
|
400
|
+
|
|
401
|
+
if http_host
|
|
402
|
+
if http_host.start_with?("[")
|
|
403
|
+
host = http_host[/\A\[([^\]]+)\]/, 1]
|
|
404
|
+
port = http_host[/\]:(\d+)\z/, 1]
|
|
405
|
+
else
|
|
406
|
+
host, port = http_host.split(":", 2)
|
|
407
|
+
end
|
|
408
|
+
env[Rack::SERVER_NAME] ||= host
|
|
409
|
+
env[Rack::SERVER_PORT] ||= port || @server_port.to_s
|
|
410
|
+
else
|
|
411
|
+
env[Rack::SERVER_NAME] ||= "localhost"
|
|
412
|
+
env[Rack::SERVER_PORT] ||= @server_port.to_s
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|