tipi 0.40 → 0.45
Sign up to get free protection for your applications and to get access to all the features.
- 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
|