tipi 0.42 → 0.47
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 +1 -3
- data/CHANGELOG.md +27 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +35 -29
- data/README.md +184 -8
- data/Rakefile +1 -7
- data/benchmarks/bm_http1_parser.rb +45 -21
- data/bin/benchmark +0 -0
- data/bin/h1pd +0 -0
- data/bm.png +0 -0
- data/df/agent.rb +1 -1
- data/df/sample_agent.rb +2 -2
- data/df/server.rb +2 -0
- data/df/server_utils.rb +12 -15
- data/examples/hello.rb +5 -0
- data/examples/hello.ru +3 -3
- data/examples/http_server.js +1 -1
- data/examples/http_server_graceful.rb +1 -1
- data/examples/https_server.rb +41 -18
- data/examples/rack_server_forked.rb +26 -0
- data/examples/rack_server_https_forked.rb +1 -1
- data/examples/websocket_demo.rb +1 -1
- data/lib/tipi/acme.rb +51 -39
- data/lib/tipi/cli.rb +79 -16
- data/lib/tipi/config_dsl.rb +13 -13
- data/lib/tipi/configuration.rb +2 -2
- data/lib/tipi/controller/bare_polyphony.rb +0 -0
- data/lib/tipi/controller/bare_stock.rb +10 -0
- data/lib/tipi/controller/extensions.rb +37 -0
- data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
- data/lib/tipi/controller/web_polyphony.rb +353 -0
- data/lib/tipi/controller/web_stock.rb +635 -0
- data/lib/tipi/controller.rb +12 -0
- data/lib/tipi/digital_fabric/agent.rb +3 -3
- data/lib/tipi/digital_fabric/agent_proxy.rb +11 -5
- data/lib/tipi/digital_fabric/executive.rb +1 -1
- data/lib/tipi/digital_fabric/protocol.rb +1 -1
- data/lib/tipi/digital_fabric/service.rb +12 -8
- data/lib/tipi/handler.rb +2 -2
- data/lib/tipi/http1_adapter.rb +36 -30
- data/lib/tipi/http2_adapter.rb +10 -10
- data/lib/tipi/http2_stream.rb +14 -15
- data/lib/tipi/rack_adapter.rb +2 -2
- data/lib/tipi/response_extensions.rb +1 -1
- 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 +4 -83
- data/test/coverage.rb +2 -2
- data/test/helper.rb +0 -1
- data/test/test_http_server.rb +14 -14
- data/test/test_request.rb +1 -1
- data/tipi.gemspec +6 -7
- metadata +58 -53
- data/ext/tipi/extconf.rb +0 -13
- data/ext/tipi/http1_parser.c +0 -823
- data/ext/tipi/http1_parser.h +0 -18
- data/ext/tipi/tipi_ext.c +0 -5
- data/security/http1.rb +0 -12
- data/test/test_http1_parser.rb +0 -586
@@ -50,6 +50,8 @@ module DigitalFabric
|
|
50
50
|
switch_rate = backend_stats[:switch_count] / elapsed
|
51
51
|
poll_rate = backend_stats[:poll_count] / elapsed
|
52
52
|
|
53
|
+
object_space_stats = ObjectSpace.count_objects
|
54
|
+
|
53
55
|
{
|
54
56
|
service: {
|
55
57
|
agent_count: @agents.size,
|
@@ -73,6 +75,8 @@ module DigitalFabric
|
|
73
75
|
process: {
|
74
76
|
cpu_usage: cpu,
|
75
77
|
rss: rss.to_f / 1024,
|
78
|
+
objects_total: object_space_stats[:TOTAL],
|
79
|
+
objects_free: object_space_stats[:FREE]
|
76
80
|
}
|
77
81
|
}
|
78
82
|
end
|
@@ -86,7 +90,7 @@ module DigitalFabric
|
|
86
90
|
rescue Exception
|
87
91
|
[nil, nil]
|
88
92
|
end
|
89
|
-
|
93
|
+
|
90
94
|
def get_stats
|
91
95
|
calculate_stats
|
92
96
|
end
|
@@ -119,14 +123,14 @@ module DigitalFabric
|
|
119
123
|
|
120
124
|
puts format('slow request (%.1f): %p', latency, req.headers)
|
121
125
|
end
|
122
|
-
|
126
|
+
|
123
127
|
def http_request(req, allow_df_upgrade = false)
|
124
128
|
@current_request_count += 1
|
125
129
|
@counters[:http_requests] += 1
|
126
130
|
@counters[:connections] += 1 if req.headers[':first']
|
127
131
|
|
128
132
|
return upgrade_request(req, allow_df_upgrade) if req.upgrade_protocol
|
129
|
-
|
133
|
+
|
130
134
|
inject_request_headers(req)
|
131
135
|
agent = find_agent(req)
|
132
136
|
unless agent
|
@@ -155,7 +159,7 @@ module DigitalFabric
|
|
155
159
|
req.headers['x-forwarded-for'] = conn.peeraddr(false)[2]
|
156
160
|
req.headers['x-forwarded-proto'] ||= conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
|
157
161
|
end
|
158
|
-
|
162
|
+
|
159
163
|
def upgrade_request(req, allow_df_upgrade)
|
160
164
|
case (protocol = req.upgrade_protocol)
|
161
165
|
when 'df'
|
@@ -174,7 +178,7 @@ module DigitalFabric
|
|
174
178
|
agent.http_upgrade(req, protocol)
|
175
179
|
end
|
176
180
|
end
|
177
|
-
|
181
|
+
|
178
182
|
def df_upgrade(req)
|
179
183
|
# we don't want to count connected agents
|
180
184
|
@current_request_count -= 1
|
@@ -187,7 +191,7 @@ module DigitalFabric
|
|
187
191
|
ensure
|
188
192
|
@current_request_count += 1
|
189
193
|
end
|
190
|
-
|
194
|
+
|
191
195
|
def mount(route, agent)
|
192
196
|
if route[:path]
|
193
197
|
route[:path_regexp] = path_regexp(route[:path])
|
@@ -196,7 +200,7 @@ module DigitalFabric
|
|
196
200
|
@agents[agent] = route
|
197
201
|
@routing_changed = true
|
198
202
|
end
|
199
|
-
|
203
|
+
|
200
204
|
def unmount(agent)
|
201
205
|
route = @agents[agent]
|
202
206
|
return unless route
|
@@ -207,7 +211,7 @@ module DigitalFabric
|
|
207
211
|
end
|
208
212
|
|
209
213
|
INVALID_HOST = 'INVALID_HOST'
|
210
|
-
|
214
|
+
|
211
215
|
def find_agent(req)
|
212
216
|
compile_agent_routes if @routing_changed
|
213
217
|
|
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
|
data/lib/tipi/http1_adapter.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require_relative './http2_adapter'
|
3
|
+
require 'h1p'
|
5
4
|
require 'qeweney/request'
|
6
5
|
|
6
|
+
require_relative './http2_adapter'
|
7
|
+
|
7
8
|
module Tipi
|
8
9
|
# HTTP1 protocol implementation
|
9
10
|
class HTTP1Adapter
|
@@ -14,26 +15,29 @@ module Tipi
|
|
14
15
|
@conn = conn
|
15
16
|
@opts = opts
|
16
17
|
@first = true
|
17
|
-
@parser =
|
18
|
+
@parser = H1P::Parser.new(@conn, :server)
|
18
19
|
end
|
19
|
-
|
20
|
+
|
20
21
|
def each(&block)
|
21
22
|
while true
|
22
23
|
headers = @parser.parse_headers
|
23
24
|
break unless headers
|
24
|
-
|
25
|
+
|
25
26
|
# handle_request returns true if connection is not persistent or was
|
26
27
|
# upgraded
|
27
28
|
break if handle_request(headers, &block)
|
28
29
|
end
|
29
|
-
rescue
|
30
|
+
rescue H1P::Error, ArgumentError
|
31
|
+
# an ArgumentError might be raised in the parser if an invalid input
|
32
|
+
# string is given as the HTTP method (String#upcase will raise on invalid HTTP string)
|
33
|
+
#
|
30
34
|
# ignore
|
31
35
|
rescue SystemCallError, IOError
|
32
36
|
# ignore
|
33
37
|
ensure
|
34
38
|
finalize_client_loop
|
35
39
|
end
|
36
|
-
|
40
|
+
|
37
41
|
def handle_request(headers, &block)
|
38
42
|
scheme = (proto = headers['x-forwarded-proto']) ?
|
39
43
|
proto.downcase : scheme_from_connection
|
@@ -43,9 +47,9 @@ module Tipi
|
|
43
47
|
headers[':first'] = true
|
44
48
|
@first = nil
|
45
49
|
end
|
46
|
-
|
50
|
+
|
47
51
|
return true if upgrade_connection(headers, &block)
|
48
|
-
|
52
|
+
|
49
53
|
request = Qeweney::Request.new(headers, self)
|
50
54
|
if !@parser.complete?
|
51
55
|
request.buffer_body_chunk(@parser.read_body_chunk(true))
|
@@ -62,14 +66,14 @@ module Tipi
|
|
62
66
|
return connection && connection != 'close'
|
63
67
|
end
|
64
68
|
end
|
65
|
-
|
69
|
+
|
66
70
|
def finalize_client_loop
|
67
71
|
@parser = nil
|
68
72
|
@splicing_pipe = nil
|
69
73
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
70
74
|
@conn.close
|
71
75
|
end
|
72
|
-
|
76
|
+
|
73
77
|
# Reads a body chunk for the current request. Transfers control to the parse
|
74
78
|
# loop, and resumes once the parse_loop has fired the on_body callback
|
75
79
|
def get_body_chunk(request, buffered_only = false)
|
@@ -83,11 +87,11 @@ module Tipi
|
|
83
87
|
def complete?(request)
|
84
88
|
@parser.complete?
|
85
89
|
end
|
86
|
-
|
90
|
+
|
87
91
|
def protocol
|
88
92
|
@protocol
|
89
93
|
end
|
90
|
-
|
94
|
+
|
91
95
|
# Upgrades the connection to a different protocol, if the 'Upgrade' header is
|
92
96
|
# given. By default the only supported upgrade protocol is HTTP2. Additional
|
93
97
|
# protocols, notably WebSocket, can be specified by passing a hash to the
|
@@ -113,28 +117,28 @@ module Tipi
|
|
113
117
|
def upgrade_connection(headers, &block)
|
114
118
|
upgrade_protocol = headers['upgrade']
|
115
119
|
return nil unless upgrade_protocol
|
116
|
-
|
120
|
+
|
117
121
|
upgrade_protocol = upgrade_protocol.downcase.to_sym
|
118
122
|
upgrade_handler = @opts[:upgrade] && @opts[:upgrade][upgrade_protocol]
|
119
123
|
return upgrade_with_handler(upgrade_handler, headers) if upgrade_handler
|
120
124
|
return upgrade_to_http2(headers, &block) if upgrade_protocol == :h2c
|
121
|
-
|
125
|
+
|
122
126
|
nil
|
123
127
|
end
|
124
|
-
|
128
|
+
|
125
129
|
def upgrade_with_handler(handler, headers)
|
126
130
|
@parser = nil
|
127
131
|
handler.(self, headers)
|
128
132
|
true
|
129
133
|
end
|
130
|
-
|
134
|
+
|
131
135
|
def upgrade_to_http2(headers, &block)
|
132
136
|
headers = http2_upgraded_headers(headers)
|
133
137
|
body = @parser.read_body
|
134
138
|
HTTP2Adapter.upgrade_each(@conn, @opts, headers, body, &block)
|
135
139
|
true
|
136
140
|
end
|
137
|
-
|
141
|
+
|
138
142
|
# Returns headers for HTTP2 upgrade
|
139
143
|
# @param headers [Hash] request headers
|
140
144
|
# @return [Hash] headers for HTTP2 upgrade
|
@@ -152,10 +156,10 @@ module Tipi
|
|
152
156
|
def scheme_from_connection
|
153
157
|
@conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
|
154
158
|
end
|
155
|
-
|
159
|
+
|
156
160
|
# response API
|
157
161
|
|
158
|
-
CRLF = "\r\n"
|
162
|
+
CRLF = "\r\n"
|
159
163
|
CRLF_ZERO_CRLF_CRLF = "\r\n0\r\n\r\n"
|
160
164
|
|
161
165
|
# Sends response including headers and body. Waits for the request to complete
|
@@ -173,17 +177,19 @@ module Tipi
|
|
173
177
|
end
|
174
178
|
end
|
175
179
|
|
180
|
+
CHUNK_LENGTH_PROC = ->(len) { "#{len.to_s(16)}\r\n" }
|
181
|
+
|
176
182
|
def respond_from_io(request, io, headers, chunk_size = 2**14)
|
177
183
|
formatted_headers = format_headers(headers, true, true)
|
178
184
|
request.tx_incr(formatted_headers.bytesize)
|
179
|
-
|
185
|
+
|
180
186
|
# assume chunked encoding
|
181
187
|
Thread.current.backend.splice_chunks(
|
182
188
|
io,
|
183
189
|
@conn,
|
184
190
|
formatted_headers,
|
185
191
|
"0\r\n\r\n",
|
186
|
-
|
192
|
+
CHUNK_LENGTH_PROC,
|
187
193
|
"\r\n",
|
188
194
|
chunk_size
|
189
195
|
)
|
@@ -205,7 +211,7 @@ module Tipi
|
|
205
211
|
def http1_1?(request)
|
206
212
|
request.headers[':protocol'] == 'http/1.1'
|
207
213
|
end
|
208
|
-
|
214
|
+
|
209
215
|
# Sends a response body chunk. If no headers were sent, default headers are
|
210
216
|
# sent using #send_headers. if the done option is true(thy), an empty chunk
|
211
217
|
# will be sent to signal response completion to the client.
|
@@ -222,7 +228,7 @@ module Tipi
|
|
222
228
|
request.tx_incr(data.bytesize)
|
223
229
|
@conn.write(data)
|
224
230
|
end
|
225
|
-
|
231
|
+
|
226
232
|
def send_chunk_from_io(request, io, r, w, chunk_size)
|
227
233
|
len = w.splice(io, chunk_size)
|
228
234
|
if len > 0
|
@@ -244,12 +250,12 @@ module Tipi
|
|
244
250
|
request.tx_incr(5)
|
245
251
|
@conn << "0\r\n\r\n"
|
246
252
|
end
|
247
|
-
|
253
|
+
|
248
254
|
def close
|
249
255
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
250
256
|
@conn.close
|
251
257
|
end
|
252
|
-
|
258
|
+
|
253
259
|
private
|
254
260
|
|
255
261
|
INTERNAL_HEADER_REGEXP = /^:/.freeze
|
@@ -266,13 +272,13 @@ module Tipi
|
|
266
272
|
lines = format_status_line(body, status, chunked)
|
267
273
|
headers.each do |k, v|
|
268
274
|
next if k =~ INTERNAL_HEADER_REGEXP
|
269
|
-
|
275
|
+
|
270
276
|
collect_header_lines(lines, k, v)
|
271
277
|
end
|
272
278
|
lines << CRLF
|
273
279
|
lines
|
274
280
|
end
|
275
|
-
|
281
|
+
|
276
282
|
def format_status_line(body, status, chunked)
|
277
283
|
if !body
|
278
284
|
empty_status_line(status)
|
@@ -280,7 +286,7 @@ module Tipi
|
|
280
286
|
with_body_status_line(status, body, chunked)
|
281
287
|
end
|
282
288
|
end
|
283
|
-
|
289
|
+
|
284
290
|
def empty_status_line(status)
|
285
291
|
if status == 204
|
286
292
|
+"HTTP/1.1 #{status}\r\n"
|
@@ -288,7 +294,7 @@ module Tipi
|
|
288
294
|
+"HTTP/1.1 #{status}\r\nContent-Length: 0\r\n"
|
289
295
|
end
|
290
296
|
end
|
291
|
-
|
297
|
+
|
292
298
|
def with_body_status_line(status, body, chunked)
|
293
299
|
if chunked
|
294
300
|
+"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
|
data/lib/tipi/http2_adapter.rb
CHANGED
@@ -21,7 +21,7 @@ module Tipi
|
|
21
21
|
adapter = new(socket, opts, headers, body)
|
22
22
|
adapter.each(&block)
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def initialize(conn, opts, upgrade_headers = nil, upgrade_body = nil)
|
26
26
|
@conn = conn
|
27
27
|
@opts = opts
|
@@ -36,7 +36,7 @@ module Tipi
|
|
36
36
|
@interface.on(:frame, &method(:send_frame))
|
37
37
|
@streams = {}
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
def send_frame(data)
|
41
41
|
if @transfer_count_request
|
42
42
|
@transfer_count_request.tx_incr(data.bytesize)
|
@@ -47,14 +47,14 @@ module Tipi
|
|
47
47
|
rescue Exception => e
|
48
48
|
@connection_fiber.transfer e
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
UPGRADE_MESSAGE = <<~HTTP.gsub("\n", "\r\n")
|
52
52
|
HTTP/1.1 101 Switching Protocols
|
53
53
|
Connection: Upgrade
|
54
54
|
Upgrade: h2c
|
55
|
-
|
55
|
+
|
56
56
|
HTTP
|
57
|
-
|
57
|
+
|
58
58
|
def upgrade
|
59
59
|
@conn << UPGRADE_MESSAGE
|
60
60
|
@tx += UPGRADE_MESSAGE.bytesize
|
@@ -63,7 +63,7 @@ module Tipi
|
|
63
63
|
ensure
|
64
64
|
@upgrade_headers = nil
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
# Iterates over incoming requests
|
68
68
|
def each(&block)
|
69
69
|
@interface.on(:stream) { |stream| start_stream(stream, &block) }
|
@@ -84,26 +84,26 @@ module Tipi
|
|
84
84
|
@rx = 0
|
85
85
|
count
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
def get_tx_count
|
89
89
|
count = @tx
|
90
90
|
@tx = 0
|
91
91
|
count
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
def start_stream(stream, &block)
|
95
95
|
stream = HTTP2StreamHandler.new(self, stream, @conn, @first, &block)
|
96
96
|
@first = nil if @first
|
97
97
|
@streams[stream] = true
|
98
98
|
end
|
99
|
-
|
99
|
+
|
100
100
|
def finalize_client_loop
|
101
101
|
@interface = nil
|
102
102
|
@streams.each_key(&:stop)
|
103
103
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
104
104
|
@conn.close
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
def close
|
108
108
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
109
109
|
@conn.close
|
data/lib/tipi/http2_stream.rb
CHANGED
@@ -6,9 +6,8 @@ require 'qeweney/request'
|
|
6
6
|
module Tipi
|
7
7
|
# Manages an HTTP 2 stream
|
8
8
|
class HTTP2StreamHandler
|
9
|
-
attr_accessor :__next__
|
10
9
|
attr_reader :conn
|
11
|
-
|
10
|
+
|
12
11
|
def initialize(adapter, stream, conn, first, &block)
|
13
12
|
@adapter = adapter
|
14
13
|
@stream = stream
|
@@ -32,7 +31,7 @@ module Tipi
|
|
32
31
|
stream.on(:data, &method(:on_data))
|
33
32
|
stream.on(:half_close, &method(:on_half_close))
|
34
33
|
end
|
35
|
-
|
34
|
+
|
36
35
|
def run(&block)
|
37
36
|
request = receive
|
38
37
|
error = nil
|
@@ -45,7 +44,7 @@ module Tipi
|
|
45
44
|
ensure
|
46
45
|
@connection_fiber.schedule error
|
47
46
|
end
|
48
|
-
|
47
|
+
|
49
48
|
def on_headers(headers)
|
50
49
|
@request = Qeweney::Request.new(headers.to_h, self)
|
51
50
|
@request.rx_incr(@adapter.get_rx_count)
|
@@ -59,7 +58,7 @@ module Tipi
|
|
59
58
|
|
60
59
|
def on_data(data)
|
61
60
|
data = data.to_s # chunks might be wrapped in a HTTP2::Buffer
|
62
|
-
|
61
|
+
|
63
62
|
(@buffered_chunks ||= []) << data
|
64
63
|
@get_body_chunk_fiber&.schedule
|
65
64
|
end
|
@@ -68,7 +67,7 @@ module Tipi
|
|
68
67
|
@get_body_chunk_fiber&.schedule
|
69
68
|
@complete = true
|
70
69
|
end
|
71
|
-
|
70
|
+
|
72
71
|
def protocol
|
73
72
|
'h2'
|
74
73
|
end
|
@@ -84,7 +83,7 @@ module Tipi
|
|
84
83
|
@buffered_chunks ||= []
|
85
84
|
return @buffered_chunks.shift unless @buffered_chunks.empty?
|
86
85
|
return nil if @complete
|
87
|
-
|
86
|
+
|
88
87
|
begin
|
89
88
|
@get_body_chunk_fiber = Fiber.current
|
90
89
|
suspend
|
@@ -112,7 +111,7 @@ module Tipi
|
|
112
111
|
def complete?(request)
|
113
112
|
@complete
|
114
113
|
end
|
115
|
-
|
114
|
+
|
116
115
|
# response API
|
117
116
|
def respond(request, chunk, headers)
|
118
117
|
headers[':status'] ||= Qeweney::Status::OK
|
@@ -149,10 +148,10 @@ module Tipi
|
|
149
148
|
end
|
150
149
|
end
|
151
150
|
end
|
152
|
-
|
151
|
+
|
153
152
|
def send_headers(request, headers, empty_response: false)
|
154
153
|
return if @headers_sent
|
155
|
-
|
154
|
+
|
156
155
|
headers[':status'] ||= (empty_response ? Qeweney::Status::NO_CONTENT : Qeweney::Status::OK).to_s
|
157
156
|
with_transfer_count(request) do
|
158
157
|
@stream.headers(transform_headers(headers), end_stream: false)
|
@@ -161,10 +160,10 @@ module Tipi
|
|
161
160
|
rescue HTTP2::Error::StreamClosed
|
162
161
|
# ignore
|
163
162
|
end
|
164
|
-
|
163
|
+
|
165
164
|
def send_chunk(request, chunk, done: false)
|
166
165
|
send_headers({}, false) unless @headers_sent
|
167
|
-
|
166
|
+
|
168
167
|
if chunk
|
169
168
|
with_transfer_count(request) do
|
170
169
|
@stream.data(chunk, end_stream: done)
|
@@ -175,7 +174,7 @@ module Tipi
|
|
175
174
|
rescue HTTP2::Error::StreamClosed
|
176
175
|
# ignore
|
177
176
|
end
|
178
|
-
|
177
|
+
|
179
178
|
def finish(request)
|
180
179
|
if @headers_sent
|
181
180
|
@stream.close
|
@@ -188,10 +187,10 @@ module Tipi
|
|
188
187
|
rescue HTTP2::Error::StreamClosed
|
189
188
|
# ignore
|
190
189
|
end
|
191
|
-
|
190
|
+
|
192
191
|
def stop
|
193
192
|
return if @complete
|
194
|
-
|
193
|
+
|
195
194
|
@stream.close
|
196
195
|
@stream_fiber.schedule(Polyphony::MoveOn.new)
|
197
196
|
end
|
data/lib/tipi/rack_adapter.rb
CHANGED
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'polyphony'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Tipi
|
7
|
+
module Supervisor
|
8
|
+
class << self
|
9
|
+
def run(opts)
|
10
|
+
puts "Start supervisor pid: #{Process.pid}"
|
11
|
+
@opts = opts
|
12
|
+
@controller_watcher = start_controller_watcher
|
13
|
+
supervise_loop
|
14
|
+
end
|
15
|
+
|
16
|
+
def start_controller_watcher
|
17
|
+
spin do
|
18
|
+
cmd = controller_cmd
|
19
|
+
puts "Starting controller..."
|
20
|
+
pid = Kernel.spawn(*cmd)
|
21
|
+
@controller_pid = pid
|
22
|
+
puts "Controller pid: #{pid}"
|
23
|
+
_pid, status = Polyphony.backend_waitpid(pid)
|
24
|
+
puts "Controller has terminated with status: #{status.inspect}"
|
25
|
+
terminated = true
|
26
|
+
ensure
|
27
|
+
if pid && !terminated
|
28
|
+
puts "Terminate controller #{pid.inspect}"
|
29
|
+
Polyphony::Process.kill_process(pid)
|
30
|
+
end
|
31
|
+
Fiber.current.parent << pid
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def controller_cmd
|
36
|
+
[
|
37
|
+
'ruby',
|
38
|
+
File.join(__dir__, 'controller.rb'),
|
39
|
+
@opts.to_json
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
def supervise_loop
|
44
|
+
this_fiber = Fiber.current
|
45
|
+
trap('SIGUSR2') { this_fiber << :replace_controller }
|
46
|
+
loop do
|
47
|
+
case (msg = receive)
|
48
|
+
when :replace_controller
|
49
|
+
replace_controller
|
50
|
+
when Integer
|
51
|
+
pid = msg
|
52
|
+
if pid == @controller_pid
|
53
|
+
puts 'Detected dead controller. Restarting...'
|
54
|
+
exit!
|
55
|
+
@controller_watcher.restart
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise "Invalid message received: #{msg.inspect}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def replace_controller
|
64
|
+
puts "Replacing controller"
|
65
|
+
old_watcher = @controller_watcher
|
66
|
+
@controller_watcher = start_controller_watcher
|
67
|
+
|
68
|
+
# TODO: we'll want to get some kind of signal from the new controller once it's ready
|
69
|
+
sleep 1
|
70
|
+
|
71
|
+
old_watcher.terminate(true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/tipi/version.rb
CHANGED
data/lib/tipi/websocket.rb
CHANGED
@@ -20,12 +20,12 @@ module Tipi
|
|
20
20
|
@version = headers['sec-websocket-version'].to_i
|
21
21
|
@reader = ::WebSocket::Frame::Incoming::Server.new(version: @version)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def recv
|
25
25
|
if (msg = @reader.next)
|
26
26
|
return msg.to_s
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
@conn.recv_loop do |data|
|
30
30
|
@reader << data
|
31
31
|
if (msg = @reader.next)
|
@@ -48,7 +48,7 @@ module Tipi
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
OutgoingFrame = ::WebSocket::Frame::Outgoing::Server
|
53
53
|
|
54
54
|
def send(data)
|