tipi 0.40 → 0.45
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/.github/FUNDING.yml +1 -0
- data/.github/workflows/test.yml +3 -1
- data/.gitignore +5 -1
- data/CHANGELOG.md +35 -0
- data/Gemfile +7 -1
- data/Gemfile.lock +55 -29
- data/README.md +184 -8
- data/Rakefile +1 -3
- data/benchmarks/bm_http1_parser.rb +85 -0
- data/bin/benchmark +37 -0
- data/bin/h1pd +6 -0
- data/bin/tipi +3 -21
- data/bm.png +0 -0
- data/df/agent.rb +1 -1
- data/df/sample_agent.rb +2 -2
- data/df/server.rb +16 -102
- data/df/server_utils.rb +175 -0
- data/examples/full_service.rb +13 -0
- data/examples/hello.rb +5 -0
- data/examples/http1_parser.rb +55 -0
- data/examples/http_server.js +1 -1
- data/examples/http_server.rb +15 -3
- data/examples/http_server_graceful.rb +1 -1
- data/examples/http_server_static.rb +6 -18
- data/examples/https_server.rb +41 -15
- data/examples/rack_server_forked.rb +26 -0
- data/examples/rack_server_https_forked.rb +1 -1
- data/examples/servername_cb.rb +37 -0
- data/examples/websocket_demo.rb +1 -1
- data/lib/tipi/acme.rb +315 -0
- data/lib/tipi/cli.rb +93 -0
- data/lib/tipi/config_dsl.rb +13 -13
- data/lib/tipi/configuration.rb +2 -2
- data/{e → lib/tipi/controller/bare_polyphony.rb} +0 -0
- data/lib/tipi/controller/bare_stock.rb +10 -0
- data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
- data/lib/tipi/controller/web_polyphony.rb +351 -0
- data/lib/tipi/controller/web_stock.rb +631 -0
- data/lib/tipi/controller.rb +12 -0
- data/lib/tipi/digital_fabric/agent.rb +10 -8
- data/lib/tipi/digital_fabric/agent_proxy.rb +26 -12
- data/lib/tipi/digital_fabric/executive.rb +7 -3
- data/lib/tipi/digital_fabric/protocol.rb +19 -4
- data/lib/tipi/digital_fabric/request_adapter.rb +0 -4
- data/lib/tipi/digital_fabric/service.rb +84 -56
- data/lib/tipi/handler.rb +2 -2
- data/lib/tipi/http1_adapter.rb +86 -125
- data/lib/tipi/http2_adapter.rb +29 -16
- data/lib/tipi/http2_stream.rb +52 -56
- data/lib/tipi/rack_adapter.rb +2 -53
- data/lib/tipi/response_extensions.rb +2 -2
- data/lib/tipi/supervisor.rb +75 -0
- data/lib/tipi/version.rb +1 -1
- data/lib/tipi/websocket.rb +3 -3
- data/lib/tipi.rb +8 -5
- data/test/coverage.rb +2 -2
- data/test/helper.rb +60 -12
- data/test/test_http_server.rb +14 -41
- data/test/test_request.rb +2 -29
- data/tipi.gemspec +12 -8
- metadata +88 -28
- data/examples/automatic_certificate.rb +0 -193
@@ -32,13 +32,13 @@ module DigitalFabric
|
|
32
32
|
@fiber = Fiber.current
|
33
33
|
@service.mount(route, self)
|
34
34
|
@mounted = true
|
35
|
-
keep_alive_timer = spin_loop(interval: 5) { keep_alive }
|
35
|
+
# keep_alive_timer = spin_loop("#{@fiber.tag}-keep_alive", interval: 5) { keep_alive }
|
36
36
|
process_incoming_messages(false)
|
37
37
|
rescue GracefulShutdown
|
38
38
|
puts "Proxy got graceful shutdown, left: #{@requests.size} requests" if @requests.size > 0
|
39
|
-
process_incoming_messages(true)
|
39
|
+
move_on_after(15) { process_incoming_messages(true) }
|
40
40
|
ensure
|
41
|
-
keep_alive_timer&.stop
|
41
|
+
# keep_alive_timer&.stop
|
42
42
|
unmount
|
43
43
|
end
|
44
44
|
|
@@ -56,11 +56,11 @@ module DigitalFabric
|
|
56
56
|
def unmount
|
57
57
|
return unless @mounted
|
58
58
|
|
59
|
-
@service.unmount(self)
|
59
|
+
@service.unmount(self)
|
60
60
|
@mounted = nil
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
63
|
+
def send_shutdown
|
64
64
|
send_df_message(Protocol.shutdown)
|
65
65
|
@fiber.raise GracefulShutdown.new
|
66
66
|
end
|
@@ -98,6 +98,8 @@ module DigitalFabric
|
|
98
98
|
return
|
99
99
|
when Protocol::UNMOUNT
|
100
100
|
return unmount
|
101
|
+
when Protocol::STATS_REQUEST
|
102
|
+
return handle_stats_request(message[Protocol::Attribute::ID])
|
101
103
|
end
|
102
104
|
|
103
105
|
handler = @requests[message[Protocol::Attribute::ID]]
|
@@ -142,13 +144,20 @@ module DigitalFabric
|
|
142
144
|
t0 = Time.now
|
143
145
|
t1 = nil
|
144
146
|
with_request do |id|
|
145
|
-
|
147
|
+
msg = Protocol.http_request(id, req.headers, req.next_chunk(true), req.complete?)
|
148
|
+
send_df_message(msg)
|
146
149
|
while (message = receive)
|
150
|
+
kind = message[Protocol::Attribute::KIND]
|
147
151
|
unless t1
|
148
152
|
t1 = Time.now
|
149
|
-
|
153
|
+
if kind == Protocol::HTTP_RESPONSE
|
154
|
+
headers = message[Protocol::Attribute::HttpResponse::HEADERS]
|
155
|
+
status = (headers && headers[':status']) || 200
|
156
|
+
if status < Qeweney::Status::BAD_REQUEST
|
157
|
+
@service.record_latency_measurement(t1 - t0, req)
|
158
|
+
end
|
159
|
+
end
|
150
160
|
end
|
151
|
-
kind = message[Protocol::Attribute::KIND]
|
152
161
|
attributes = message[Protocol::Attribute::HttpRequest::HEADERS..-1]
|
153
162
|
return if http_request_message(id, req, kind, attributes)
|
154
163
|
end
|
@@ -187,6 +196,11 @@ module DigitalFabric
|
|
187
196
|
send_df_message(Protocol.transfer_count(key, rx, tx))
|
188
197
|
end
|
189
198
|
|
199
|
+
def handle_stats_request(id)
|
200
|
+
stats = @service.get_stats
|
201
|
+
send_df_message(Protocol.stats_response(id, stats))
|
202
|
+
end
|
203
|
+
|
190
204
|
HTTP_RESPONSE_UPGRADE_HEADERS = { ':status' => Qeweney::Status::SWITCHING_PROTOCOLS }
|
191
205
|
|
192
206
|
def http_custom_upgrade(id, req, headers)
|
@@ -197,7 +211,7 @@ module DigitalFabric
|
|
197
211
|
req.send_headers(upgrade_headers, true)
|
198
212
|
|
199
213
|
conn = req.adapter.conn
|
200
|
-
reader = spin do
|
214
|
+
reader = spin("#{Fiber.current.tag}.#{id}") do
|
201
215
|
conn.recv_loop do |data|
|
202
216
|
send_df_message(Protocol.conn_data(id, data))
|
203
217
|
end
|
@@ -233,7 +247,7 @@ module DigitalFabric
|
|
233
247
|
else
|
234
248
|
req.send_headers(headers) if headers && !req.headers_sent?
|
235
249
|
req.send_chunk(body, done: complete) if body or complete
|
236
|
-
|
250
|
+
|
237
251
|
if complete && transfer_count_key
|
238
252
|
rx, tx = req.transfer_counts
|
239
253
|
send_transfer_count(transfer_count_key, rx, tx)
|
@@ -277,7 +291,7 @@ module DigitalFabric
|
|
277
291
|
response = receive
|
278
292
|
case response[0]
|
279
293
|
when Protocol::WS_RESPONSE
|
280
|
-
headers = response[2] || {}
|
294
|
+
headers = response[2] || {}
|
281
295
|
status = headers[':status'] || Qeweney::Status::SWITCHING_PROTOCOLS
|
282
296
|
if status != Qeweney::Status::SWITCHING_PROTOCOLS
|
283
297
|
req.respond(nil, headers)
|
@@ -294,7 +308,7 @@ module DigitalFabric
|
|
294
308
|
end
|
295
309
|
|
296
310
|
def run_websocket_connection(id, websocket)
|
297
|
-
reader = spin do
|
311
|
+
reader = spin("#{Fiber.current}.#{id}-ws") do
|
298
312
|
websocket.recv_loop do |data|
|
299
313
|
send_df_message(Protocol.ws_data(id, data))
|
300
314
|
end
|
@@ -15,7 +15,7 @@ module DigitalFabric
|
|
15
15
|
route[:executive] = true
|
16
16
|
@service.mount(route, self)
|
17
17
|
@current_request_count = 0
|
18
|
-
@updater = spin_loop(interval: 10) { update_service_stats }
|
18
|
+
# @updater = spin_loop(:executive_updater, interval: 10) { update_service_stats }
|
19
19
|
update_service_stats
|
20
20
|
end
|
21
21
|
|
@@ -33,9 +33,13 @@ module DigitalFabric
|
|
33
33
|
req.respond(message.to_json, { 'Content-Type' => 'text.json' })
|
34
34
|
when '/stream/stats'
|
35
35
|
stream_stats(req)
|
36
|
+
when '/upload'
|
37
|
+
req.respond("body: #{req.read.inspect}")
|
36
38
|
else
|
37
39
|
req.respond('Invalid path', { ':status' => Qeweney::Status::NOT_FOUND })
|
38
40
|
end
|
41
|
+
rescue => e
|
42
|
+
puts "Error: #{e.inspect}"
|
39
43
|
ensure
|
40
44
|
@current_request_count -= 1
|
41
45
|
end
|
@@ -43,7 +47,7 @@ module DigitalFabric
|
|
43
47
|
def stream_stats(req)
|
44
48
|
req.send_headers({ 'Content-Type' => 'text/event-stream' })
|
45
49
|
|
46
|
-
|
50
|
+
every(10) do
|
47
51
|
message = last_service_stats
|
48
52
|
req.send_chunk(format_sse_event(message.to_json))
|
49
53
|
end
|
@@ -79,7 +83,7 @@ module DigitalFabric
|
|
79
83
|
raise 'Invalid output from top (cpu)'
|
80
84
|
end
|
81
85
|
cpu_utilization = 100 - Regexp.last_match(1).to_i
|
82
|
-
|
86
|
+
|
83
87
|
unless top =~ TOP_MEM_REGEXP && Regexp.last_match(1) =~ TOP_MEM_FREE_REGEXP
|
84
88
|
raise 'Invalid output from top (mem)'
|
85
89
|
end
|
@@ -22,6 +22,9 @@ module DigitalFabric
|
|
22
22
|
|
23
23
|
TRANSFER_COUNT = 'transfer_count'
|
24
24
|
|
25
|
+
STATS_REQUEST = 'stats_request'
|
26
|
+
STATS_RESPONSE = 'stats_response'
|
27
|
+
|
25
28
|
SEND_TIMEOUT = 15
|
26
29
|
RECV_TIMEOUT = SEND_TIMEOUT + 5
|
27
30
|
|
@@ -69,6 +72,10 @@ module DigitalFabric
|
|
69
72
|
RX = 2
|
70
73
|
TX = 3
|
71
74
|
end
|
75
|
+
|
76
|
+
module Stats
|
77
|
+
STATS = 2
|
78
|
+
end
|
72
79
|
end
|
73
80
|
|
74
81
|
class << self
|
@@ -95,8 +102,8 @@ module DigitalFabric
|
|
95
102
|
DF_UPGRADE_RESPONSE
|
96
103
|
end
|
97
104
|
|
98
|
-
def http_request(id,
|
99
|
-
[ HTTP_REQUEST, id,
|
105
|
+
def http_request(id, headers, buffered_chunk, complete)
|
106
|
+
[ HTTP_REQUEST, id, headers, buffered_chunk, complete ]
|
100
107
|
end
|
101
108
|
|
102
109
|
def http_response(id, body, headers, complete, transfer_count_key = nil)
|
@@ -134,14 +141,22 @@ module DigitalFabric
|
|
134
141
|
def ws_data(id, data)
|
135
142
|
[ WS_DATA, id, data ]
|
136
143
|
end
|
137
|
-
|
144
|
+
|
138
145
|
def ws_close(id)
|
139
|
-
[WS_CLOSE, id ]
|
146
|
+
[ WS_CLOSE, id ]
|
140
147
|
end
|
141
148
|
|
142
149
|
def transfer_count(key, rx, tx)
|
143
150
|
[ TRANSFER_COUNT, key, rx, tx ]
|
144
151
|
end
|
152
|
+
|
153
|
+
def stats_request(id)
|
154
|
+
[ STATS_REQUEST, id ]
|
155
|
+
end
|
156
|
+
|
157
|
+
def stats_response(id, stats)
|
158
|
+
[ STATS_RESPONSE, id, stats ]
|
159
|
+
end
|
145
160
|
end
|
146
161
|
end
|
147
162
|
end
|
@@ -17,10 +17,6 @@ module DigitalFabric
|
|
17
17
|
@agent.get_http_request_body(@id, 1)
|
18
18
|
end
|
19
19
|
|
20
|
-
def consume_request(request)
|
21
|
-
@agent.get_http_request_body(@id, nil)
|
22
|
-
end
|
23
|
-
|
24
20
|
def respond(request, body, headers)
|
25
21
|
@agent.send_df_message(
|
26
22
|
Protocol.http_response(@id, body, headers, true)
|
@@ -13,26 +13,22 @@ module DigitalFabric
|
|
13
13
|
@token = token
|
14
14
|
@agents = {}
|
15
15
|
@routes = {}
|
16
|
-
@waiting_lists = {} # hash mapping routes to arrays of requests waiting for an agent to mount
|
17
16
|
@counters = {
|
18
17
|
connections: 0,
|
19
18
|
http_requests: 0,
|
20
19
|
errors: 0
|
21
20
|
}
|
22
21
|
@connection_count = 0
|
22
|
+
@current_request_count = 0
|
23
23
|
@http_latency_accumulator = 0
|
24
24
|
@http_latency_counter = 0
|
25
|
+
@http_latency_max = 0
|
25
26
|
@last_counters = @counters.merge(stamp: Time.now.to_f - 1)
|
26
27
|
@fiber = Fiber.current
|
27
|
-
@timer = Polyphony::Timer.new(resolution:
|
28
|
-
|
29
|
-
stats_updater = spin { @timer.every(10) { update_stats } }
|
30
|
-
@stats = {}
|
31
|
-
|
32
|
-
@current_request_count = 0
|
28
|
+
# @timer = Polyphony::Timer.new('service_timer', resolution: 5)
|
33
29
|
end
|
34
30
|
|
35
|
-
def
|
31
|
+
def calculate_stats
|
36
32
|
now = Time.now.to_f
|
37
33
|
elapsed = now - @last_counters[:stamp]
|
38
34
|
connections = @counters[:connections] - @last_counters[:connections]
|
@@ -40,23 +36,65 @@ module DigitalFabric
|
|
40
36
|
errors = @counters[:errors] - @last_counters[:errors]
|
41
37
|
@last_counters = @counters.merge(stamp: now)
|
42
38
|
|
43
|
-
average_latency = @http_latency_counter
|
44
|
-
@http_latency_accumulator / @http_latency_counter
|
45
|
-
0
|
39
|
+
average_latency = @http_latency_counter == 0 ? 0 :
|
40
|
+
@http_latency_accumulator / @http_latency_counter
|
46
41
|
@http_latency_accumulator = 0
|
47
42
|
@http_latency_counter = 0
|
48
|
-
|
49
|
-
@
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
43
|
+
max_latency = @http_latency_max
|
44
|
+
@http_latency_max = 0
|
45
|
+
|
46
|
+
cpu, rss = pid_cpu_and_rss(Process.pid)
|
47
|
+
|
48
|
+
backend_stats = Thread.backend.stats
|
49
|
+
op_rate = backend_stats[:op_count] / elapsed
|
50
|
+
switch_rate = backend_stats[:switch_count] / elapsed
|
51
|
+
poll_rate = backend_stats[:poll_count] / elapsed
|
52
|
+
|
53
|
+
object_space_stats = ObjectSpace.count_objects
|
54
|
+
|
55
|
+
{
|
56
|
+
service: {
|
57
|
+
agent_count: @agents.size,
|
58
|
+
connection_count: @connection_count,
|
59
|
+
connection_rate: connections / elapsed,
|
60
|
+
error_rate: errors / elapsed,
|
61
|
+
http_request_rate: http_requests / elapsed,
|
62
|
+
latency_avg: average_latency,
|
63
|
+
latency_max: max_latency,
|
64
|
+
pending_requests: @current_request_count,
|
65
|
+
},
|
66
|
+
backend: {
|
67
|
+
op_rate: op_rate,
|
68
|
+
pending_ops: backend_stats[:pending_ops],
|
69
|
+
poll_rate: poll_rate,
|
70
|
+
runqueue_size: backend_stats[:runqueue_size],
|
71
|
+
runqueue_high_watermark: backend_stats[:runqueue_max_length],
|
72
|
+
switch_rate: switch_rate,
|
73
|
+
|
74
|
+
},
|
75
|
+
process: {
|
76
|
+
cpu_usage: cpu,
|
77
|
+
rss: rss.to_f / 1024,
|
78
|
+
objects_total: object_space_stats[:TOTAL],
|
79
|
+
objects_free: object_space_stats[:FREE]
|
80
|
+
}
|
57
81
|
}
|
58
82
|
end
|
59
83
|
|
84
|
+
def pid_cpu_and_rss(pid)
|
85
|
+
s = `ps -p #{pid} -o %cpu,rss`
|
86
|
+
cpu, rss = s.lines[1].chomp.strip.split(' ')
|
87
|
+
[cpu.to_f, rss.to_i]
|
88
|
+
rescue Polyphony::BaseException
|
89
|
+
raise
|
90
|
+
rescue Exception
|
91
|
+
[nil, nil]
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_stats
|
95
|
+
calculate_stats
|
96
|
+
end
|
97
|
+
|
60
98
|
def incr_connection_count
|
61
99
|
@connection_count += 1
|
62
100
|
end
|
@@ -77,23 +115,25 @@ module DigitalFabric
|
|
77
115
|
count
|
78
116
|
end
|
79
117
|
|
80
|
-
def record_latency_measurement(latency)
|
118
|
+
def record_latency_measurement(latency, req)
|
81
119
|
@http_latency_accumulator += latency
|
82
120
|
@http_latency_counter += 1
|
121
|
+
@http_latency_max = latency if latency > @http_latency_max
|
122
|
+
return if latency < 1.0
|
123
|
+
|
124
|
+
puts format('slow request (%.1f): %p', latency, req.headers)
|
83
125
|
end
|
84
|
-
|
85
|
-
def http_request(req)
|
126
|
+
|
127
|
+
def http_request(req, allow_df_upgrade = false)
|
86
128
|
@current_request_count += 1
|
87
129
|
@counters[:http_requests] += 1
|
88
130
|
@counters[:connections] += 1 if req.headers[':first']
|
89
131
|
|
90
|
-
return upgrade_request(req) if req.upgrade_protocol
|
91
|
-
|
132
|
+
return upgrade_request(req, allow_df_upgrade) if req.upgrade_protocol
|
133
|
+
|
92
134
|
inject_request_headers(req)
|
93
135
|
agent = find_agent(req)
|
94
136
|
unless agent
|
95
|
-
return req.respond('pong') if req.query[:q] == 'ping'
|
96
|
-
|
97
137
|
@counters[:errors] += 1
|
98
138
|
return req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
|
99
139
|
end
|
@@ -119,11 +159,15 @@ module DigitalFabric
|
|
119
159
|
req.headers['x-forwarded-for'] = conn.peeraddr(false)[2]
|
120
160
|
req.headers['x-forwarded-proto'] ||= conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
|
121
161
|
end
|
122
|
-
|
123
|
-
def upgrade_request(req)
|
162
|
+
|
163
|
+
def upgrade_request(req, allow_df_upgrade)
|
124
164
|
case (protocol = req.upgrade_protocol)
|
125
165
|
when 'df'
|
126
|
-
|
166
|
+
if allow_df_upgrade
|
167
|
+
df_upgrade(req)
|
168
|
+
else
|
169
|
+
req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
|
170
|
+
end
|
127
171
|
else
|
128
172
|
agent = find_agent(req)
|
129
173
|
unless agent
|
@@ -134,16 +178,20 @@ module DigitalFabric
|
|
134
178
|
agent.http_upgrade(req, protocol)
|
135
179
|
end
|
136
180
|
end
|
137
|
-
|
181
|
+
|
138
182
|
def df_upgrade(req)
|
183
|
+
# we don't want to count connected agents
|
184
|
+
@current_request_count -= 1
|
139
185
|
if req.headers['df-token'] != @token
|
140
186
|
return req.respond(nil, ':status' => Qeweney::Status::FORBIDDEN)
|
141
187
|
end
|
142
188
|
|
143
189
|
req.adapter.conn << Protocol.df_upgrade_response
|
144
190
|
AgentProxy.new(self, req)
|
191
|
+
ensure
|
192
|
+
@current_request_count += 1
|
145
193
|
end
|
146
|
-
|
194
|
+
|
147
195
|
def mount(route, agent)
|
148
196
|
if route[:path]
|
149
197
|
route[:path_regexp] = path_regexp(route[:path])
|
@@ -151,13 +199,8 @@ module DigitalFabric
|
|
151
199
|
@executive = agent if route[:executive]
|
152
200
|
@agents[agent] = route
|
153
201
|
@routing_changed = true
|
154
|
-
|
155
|
-
if (waiting = @waiting_lists[route])
|
156
|
-
waiting.each { |f| f.schedule(agent) }
|
157
|
-
@waiting_lists.delete(route)
|
158
|
-
end
|
159
202
|
end
|
160
|
-
|
203
|
+
|
161
204
|
def unmount(agent)
|
162
205
|
route = @agents[agent]
|
163
206
|
return unless route
|
@@ -165,12 +208,10 @@ module DigitalFabric
|
|
165
208
|
@executive = nil if route[:executive]
|
166
209
|
@agents.delete(agent)
|
167
210
|
@routing_changed = true
|
168
|
-
|
169
|
-
@waiting_lists[route] ||= []
|
170
211
|
end
|
171
212
|
|
172
213
|
INVALID_HOST = 'INVALID_HOST'
|
173
|
-
|
214
|
+
|
174
215
|
def find_agent(req)
|
175
216
|
compile_agent_routes if @routing_changed
|
176
217
|
|
@@ -182,12 +223,6 @@ module DigitalFabric
|
|
182
223
|
end
|
183
224
|
return @routes[route] if route
|
184
225
|
|
185
|
-
# # search for a known route for an agent that recently unmounted
|
186
|
-
# route, wait_list = @waiting_lists.find do |route, _|
|
187
|
-
# (host == route[:host]) || (path =~ route[:path_regexp])
|
188
|
-
# end
|
189
|
-
# return wait_for_agent(wait_list) if route
|
190
|
-
|
191
226
|
nil
|
192
227
|
end
|
193
228
|
|
@@ -202,13 +237,6 @@ module DigitalFabric
|
|
202
237
|
@route_keys = @routes.keys
|
203
238
|
end
|
204
239
|
|
205
|
-
def wait_for_agent(wait_list)
|
206
|
-
wait_list << Fiber.current
|
207
|
-
@timer.move_on_after(10) { suspend }
|
208
|
-
ensure
|
209
|
-
wait_list.delete(self)
|
210
|
-
end
|
211
|
-
|
212
240
|
def path_regexp(path)
|
213
241
|
/^#{path}/
|
214
242
|
end
|
@@ -216,8 +244,8 @@ module DigitalFabric
|
|
216
244
|
def graceful_shutdown
|
217
245
|
@shutdown = true
|
218
246
|
@agents.keys.each do |agent|
|
219
|
-
if agent.respond_to?(:
|
220
|
-
agent.
|
247
|
+
if agent.respond_to?(:send_shutdown)
|
248
|
+
agent.send_shutdown
|
221
249
|
else
|
222
250
|
@agents.delete(agent)
|
223
251
|
end
|
data/lib/tipi/handler.rb
CHANGED
@@ -20,14 +20,14 @@ module Tipi
|
|
20
20
|
ensure
|
21
21
|
socket.close
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
ALPN_PROTOCOLS = %w[h2 http/1.1].freeze
|
25
25
|
H2_PROTOCOL = 'h2'
|
26
26
|
|
27
27
|
def protocol_adapter(socket, opts)
|
28
28
|
use_http2 = socket.respond_to?(:alpn_protocol) &&
|
29
29
|
socket.alpn_protocol == H2_PROTOCOL
|
30
|
-
|
30
|
+
|
31
31
|
klass = use_http2 ? HTTP2Adapter : HTTP1Adapter
|
32
32
|
klass.new(socket, opts)
|
33
33
|
end
|