tipi 0.43 → 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 +1 -3
- data/CHANGELOG.md +12 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +14 -7
- data/README.md +184 -8
- data/Rakefile +1 -7
- data/benchmarks/bm_http1_parser.rb +1 -1
- 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_utils.rb +1 -1
- data/examples/hello.rb +5 -0
- 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 +46 -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/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 +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 +8 -8
- data/lib/tipi/handler.rb +2 -2
- data/lib/tipi/http1_adapter.rb +32 -27
- data/lib/tipi/http2_adapter.rb +10 -10
- data/lib/tipi/http2_stream.rb +14 -14
- 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/test_http_server.rb +14 -14
- data/tipi.gemspec +3 -2
- metadata +30 -5
@@ -90,7 +90,7 @@ module DigitalFabric
|
|
90
90
|
rescue Exception
|
91
91
|
[nil, nil]
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
def get_stats
|
95
95
|
calculate_stats
|
96
96
|
end
|
@@ -123,14 +123,14 @@ module DigitalFabric
|
|
123
123
|
|
124
124
|
puts format('slow request (%.1f): %p', latency, req.headers)
|
125
125
|
end
|
126
|
-
|
126
|
+
|
127
127
|
def http_request(req, allow_df_upgrade = false)
|
128
128
|
@current_request_count += 1
|
129
129
|
@counters[:http_requests] += 1
|
130
130
|
@counters[:connections] += 1 if req.headers[':first']
|
131
131
|
|
132
132
|
return upgrade_request(req, allow_df_upgrade) if req.upgrade_protocol
|
133
|
-
|
133
|
+
|
134
134
|
inject_request_headers(req)
|
135
135
|
agent = find_agent(req)
|
136
136
|
unless agent
|
@@ -159,7 +159,7 @@ module DigitalFabric
|
|
159
159
|
req.headers['x-forwarded-for'] = conn.peeraddr(false)[2]
|
160
160
|
req.headers['x-forwarded-proto'] ||= conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
|
161
161
|
end
|
162
|
-
|
162
|
+
|
163
163
|
def upgrade_request(req, allow_df_upgrade)
|
164
164
|
case (protocol = req.upgrade_protocol)
|
165
165
|
when 'df'
|
@@ -178,7 +178,7 @@ module DigitalFabric
|
|
178
178
|
agent.http_upgrade(req, protocol)
|
179
179
|
end
|
180
180
|
end
|
181
|
-
|
181
|
+
|
182
182
|
def df_upgrade(req)
|
183
183
|
# we don't want to count connected agents
|
184
184
|
@current_request_count -= 1
|
@@ -191,7 +191,7 @@ module DigitalFabric
|
|
191
191
|
ensure
|
192
192
|
@current_request_count += 1
|
193
193
|
end
|
194
|
-
|
194
|
+
|
195
195
|
def mount(route, agent)
|
196
196
|
if route[:path]
|
197
197
|
route[:path_regexp] = path_regexp(route[:path])
|
@@ -200,7 +200,7 @@ module DigitalFabric
|
|
200
200
|
@agents[agent] = route
|
201
201
|
@routing_changed = true
|
202
202
|
end
|
203
|
-
|
203
|
+
|
204
204
|
def unmount(agent)
|
205
205
|
route = @agents[agent]
|
206
206
|
return unless route
|
@@ -211,7 +211,7 @@ module DigitalFabric
|
|
211
211
|
end
|
212
212
|
|
213
213
|
INVALID_HOST = 'INVALID_HOST'
|
214
|
-
|
214
|
+
|
215
215
|
def find_agent(req)
|
216
216
|
compile_agent_routes if @routing_changed
|
217
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
@@ -17,24 +17,27 @@ module Tipi
|
|
17
17
|
@first = true
|
18
18
|
@parser = H1P::Parser.new(@conn)
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def each(&block)
|
22
22
|
while true
|
23
23
|
headers = @parser.parse_headers
|
24
24
|
break unless headers
|
25
|
-
|
25
|
+
|
26
26
|
# handle_request returns true if connection is not persistent or was
|
27
27
|
# upgraded
|
28
28
|
break if handle_request(headers, &block)
|
29
29
|
end
|
30
|
-
rescue H1P::Error
|
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
|
+
#
|
31
34
|
# ignore
|
32
35
|
rescue SystemCallError, IOError
|
33
36
|
# ignore
|
34
37
|
ensure
|
35
38
|
finalize_client_loop
|
36
39
|
end
|
37
|
-
|
40
|
+
|
38
41
|
def handle_request(headers, &block)
|
39
42
|
scheme = (proto = headers['x-forwarded-proto']) ?
|
40
43
|
proto.downcase : scheme_from_connection
|
@@ -44,9 +47,9 @@ module Tipi
|
|
44
47
|
headers[':first'] = true
|
45
48
|
@first = nil
|
46
49
|
end
|
47
|
-
|
50
|
+
|
48
51
|
return true if upgrade_connection(headers, &block)
|
49
|
-
|
52
|
+
|
50
53
|
request = Qeweney::Request.new(headers, self)
|
51
54
|
if !@parser.complete?
|
52
55
|
request.buffer_body_chunk(@parser.read_body_chunk(true))
|
@@ -63,14 +66,14 @@ module Tipi
|
|
63
66
|
return connection && connection != 'close'
|
64
67
|
end
|
65
68
|
end
|
66
|
-
|
69
|
+
|
67
70
|
def finalize_client_loop
|
68
71
|
@parser = nil
|
69
72
|
@splicing_pipe = nil
|
70
73
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
71
74
|
@conn.close
|
72
75
|
end
|
73
|
-
|
76
|
+
|
74
77
|
# Reads a body chunk for the current request. Transfers control to the parse
|
75
78
|
# loop, and resumes once the parse_loop has fired the on_body callback
|
76
79
|
def get_body_chunk(request, buffered_only = false)
|
@@ -84,11 +87,11 @@ module Tipi
|
|
84
87
|
def complete?(request)
|
85
88
|
@parser.complete?
|
86
89
|
end
|
87
|
-
|
90
|
+
|
88
91
|
def protocol
|
89
92
|
@protocol
|
90
93
|
end
|
91
|
-
|
94
|
+
|
92
95
|
# Upgrades the connection to a different protocol, if the 'Upgrade' header is
|
93
96
|
# given. By default the only supported upgrade protocol is HTTP2. Additional
|
94
97
|
# protocols, notably WebSocket, can be specified by passing a hash to the
|
@@ -114,28 +117,28 @@ module Tipi
|
|
114
117
|
def upgrade_connection(headers, &block)
|
115
118
|
upgrade_protocol = headers['upgrade']
|
116
119
|
return nil unless upgrade_protocol
|
117
|
-
|
120
|
+
|
118
121
|
upgrade_protocol = upgrade_protocol.downcase.to_sym
|
119
122
|
upgrade_handler = @opts[:upgrade] && @opts[:upgrade][upgrade_protocol]
|
120
123
|
return upgrade_with_handler(upgrade_handler, headers) if upgrade_handler
|
121
124
|
return upgrade_to_http2(headers, &block) if upgrade_protocol == :h2c
|
122
|
-
|
125
|
+
|
123
126
|
nil
|
124
127
|
end
|
125
|
-
|
128
|
+
|
126
129
|
def upgrade_with_handler(handler, headers)
|
127
130
|
@parser = nil
|
128
131
|
handler.(self, headers)
|
129
132
|
true
|
130
133
|
end
|
131
|
-
|
134
|
+
|
132
135
|
def upgrade_to_http2(headers, &block)
|
133
136
|
headers = http2_upgraded_headers(headers)
|
134
137
|
body = @parser.read_body
|
135
138
|
HTTP2Adapter.upgrade_each(@conn, @opts, headers, body, &block)
|
136
139
|
true
|
137
140
|
end
|
138
|
-
|
141
|
+
|
139
142
|
# Returns headers for HTTP2 upgrade
|
140
143
|
# @param headers [Hash] request headers
|
141
144
|
# @return [Hash] headers for HTTP2 upgrade
|
@@ -153,10 +156,10 @@ module Tipi
|
|
153
156
|
def scheme_from_connection
|
154
157
|
@conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
|
155
158
|
end
|
156
|
-
|
159
|
+
|
157
160
|
# response API
|
158
161
|
|
159
|
-
CRLF = "\r\n"
|
162
|
+
CRLF = "\r\n"
|
160
163
|
CRLF_ZERO_CRLF_CRLF = "\r\n0\r\n\r\n"
|
161
164
|
|
162
165
|
# Sends response including headers and body. Waits for the request to complete
|
@@ -174,17 +177,19 @@ module Tipi
|
|
174
177
|
end
|
175
178
|
end
|
176
179
|
|
180
|
+
CHUNK_LENGTH_PROC = ->(len) { "#{len.to_s(16)}\r\n" }
|
181
|
+
|
177
182
|
def respond_from_io(request, io, headers, chunk_size = 2**14)
|
178
183
|
formatted_headers = format_headers(headers, true, true)
|
179
184
|
request.tx_incr(formatted_headers.bytesize)
|
180
|
-
|
185
|
+
|
181
186
|
# assume chunked encoding
|
182
187
|
Thread.current.backend.splice_chunks(
|
183
188
|
io,
|
184
189
|
@conn,
|
185
190
|
formatted_headers,
|
186
191
|
"0\r\n\r\n",
|
187
|
-
|
192
|
+
CHUNK_LENGTH_PROC,
|
188
193
|
"\r\n",
|
189
194
|
chunk_size
|
190
195
|
)
|
@@ -206,7 +211,7 @@ module Tipi
|
|
206
211
|
def http1_1?(request)
|
207
212
|
request.headers[':protocol'] == 'http/1.1'
|
208
213
|
end
|
209
|
-
|
214
|
+
|
210
215
|
# Sends a response body chunk. If no headers were sent, default headers are
|
211
216
|
# sent using #send_headers. if the done option is true(thy), an empty chunk
|
212
217
|
# will be sent to signal response completion to the client.
|
@@ -223,7 +228,7 @@ module Tipi
|
|
223
228
|
request.tx_incr(data.bytesize)
|
224
229
|
@conn.write(data)
|
225
230
|
end
|
226
|
-
|
231
|
+
|
227
232
|
def send_chunk_from_io(request, io, r, w, chunk_size)
|
228
233
|
len = w.splice(io, chunk_size)
|
229
234
|
if len > 0
|
@@ -245,12 +250,12 @@ module Tipi
|
|
245
250
|
request.tx_incr(5)
|
246
251
|
@conn << "0\r\n\r\n"
|
247
252
|
end
|
248
|
-
|
253
|
+
|
249
254
|
def close
|
250
255
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
251
256
|
@conn.close
|
252
257
|
end
|
253
|
-
|
258
|
+
|
254
259
|
private
|
255
260
|
|
256
261
|
INTERNAL_HEADER_REGEXP = /^:/.freeze
|
@@ -267,13 +272,13 @@ module Tipi
|
|
267
272
|
lines = format_status_line(body, status, chunked)
|
268
273
|
headers.each do |k, v|
|
269
274
|
next if k =~ INTERNAL_HEADER_REGEXP
|
270
|
-
|
275
|
+
|
271
276
|
collect_header_lines(lines, k, v)
|
272
277
|
end
|
273
278
|
lines << CRLF
|
274
279
|
lines
|
275
280
|
end
|
276
|
-
|
281
|
+
|
277
282
|
def format_status_line(body, status, chunked)
|
278
283
|
if !body
|
279
284
|
empty_status_line(status)
|
@@ -281,7 +286,7 @@ module Tipi
|
|
281
286
|
with_body_status_line(status, body, chunked)
|
282
287
|
end
|
283
288
|
end
|
284
|
-
|
289
|
+
|
285
290
|
def empty_status_line(status)
|
286
291
|
if status == 204
|
287
292
|
+"HTTP/1.1 #{status}\r\n"
|
@@ -289,7 +294,7 @@ module Tipi
|
|
289
294
|
+"HTTP/1.1 #{status}\r\nContent-Length: 0\r\n"
|
290
295
|
end
|
291
296
|
end
|
292
|
-
|
297
|
+
|
293
298
|
def with_body_status_line(status, body, chunked)
|
294
299
|
if chunked
|
295
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
@@ -8,7 +8,7 @@ module Tipi
|
|
8
8
|
class HTTP2StreamHandler
|
9
9
|
attr_accessor :__next__
|
10
10
|
attr_reader :conn
|
11
|
-
|
11
|
+
|
12
12
|
def initialize(adapter, stream, conn, first, &block)
|
13
13
|
@adapter = adapter
|
14
14
|
@stream = stream
|
@@ -32,7 +32,7 @@ module Tipi
|
|
32
32
|
stream.on(:data, &method(:on_data))
|
33
33
|
stream.on(:half_close, &method(:on_half_close))
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
def run(&block)
|
37
37
|
request = receive
|
38
38
|
error = nil
|
@@ -45,7 +45,7 @@ module Tipi
|
|
45
45
|
ensure
|
46
46
|
@connection_fiber.schedule error
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
def on_headers(headers)
|
50
50
|
@request = Qeweney::Request.new(headers.to_h, self)
|
51
51
|
@request.rx_incr(@adapter.get_rx_count)
|
@@ -59,7 +59,7 @@ module Tipi
|
|
59
59
|
|
60
60
|
def on_data(data)
|
61
61
|
data = data.to_s # chunks might be wrapped in a HTTP2::Buffer
|
62
|
-
|
62
|
+
|
63
63
|
(@buffered_chunks ||= []) << data
|
64
64
|
@get_body_chunk_fiber&.schedule
|
65
65
|
end
|
@@ -68,7 +68,7 @@ module Tipi
|
|
68
68
|
@get_body_chunk_fiber&.schedule
|
69
69
|
@complete = true
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
def protocol
|
73
73
|
'h2'
|
74
74
|
end
|
@@ -84,7 +84,7 @@ module Tipi
|
|
84
84
|
@buffered_chunks ||= []
|
85
85
|
return @buffered_chunks.shift unless @buffered_chunks.empty?
|
86
86
|
return nil if @complete
|
87
|
-
|
87
|
+
|
88
88
|
begin
|
89
89
|
@get_body_chunk_fiber = Fiber.current
|
90
90
|
suspend
|
@@ -112,7 +112,7 @@ module Tipi
|
|
112
112
|
def complete?(request)
|
113
113
|
@complete
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
# response API
|
117
117
|
def respond(request, chunk, headers)
|
118
118
|
headers[':status'] ||= Qeweney::Status::OK
|
@@ -149,10 +149,10 @@ module Tipi
|
|
149
149
|
end
|
150
150
|
end
|
151
151
|
end
|
152
|
-
|
152
|
+
|
153
153
|
def send_headers(request, headers, empty_response: false)
|
154
154
|
return if @headers_sent
|
155
|
-
|
155
|
+
|
156
156
|
headers[':status'] ||= (empty_response ? Qeweney::Status::NO_CONTENT : Qeweney::Status::OK).to_s
|
157
157
|
with_transfer_count(request) do
|
158
158
|
@stream.headers(transform_headers(headers), end_stream: false)
|
@@ -161,10 +161,10 @@ module Tipi
|
|
161
161
|
rescue HTTP2::Error::StreamClosed
|
162
162
|
# ignore
|
163
163
|
end
|
164
|
-
|
164
|
+
|
165
165
|
def send_chunk(request, chunk, done: false)
|
166
166
|
send_headers({}, false) unless @headers_sent
|
167
|
-
|
167
|
+
|
168
168
|
if chunk
|
169
169
|
with_transfer_count(request) do
|
170
170
|
@stream.data(chunk, end_stream: done)
|
@@ -175,7 +175,7 @@ module Tipi
|
|
175
175
|
rescue HTTP2::Error::StreamClosed
|
176
176
|
# ignore
|
177
177
|
end
|
178
|
-
|
178
|
+
|
179
179
|
def finish(request)
|
180
180
|
if @headers_sent
|
181
181
|
@stream.close
|
@@ -188,10 +188,10 @@ module Tipi
|
|
188
188
|
rescue HTTP2::Error::StreamClosed
|
189
189
|
# ignore
|
190
190
|
end
|
191
|
-
|
191
|
+
|
192
192
|
def stop
|
193
193
|
return if @complete
|
194
|
-
|
194
|
+
|
195
195
|
@stream.close
|
196
196
|
@stream_fiber.schedule(Polyphony::MoveOn.new)
|
197
197
|
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)
|