tipi 0.33 → 0.37.1
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/CHANGELOG.md +22 -0
- data/Gemfile.lock +10 -4
- data/LICENSE +1 -1
- data/TODO.md +11 -47
- data/df/agent.rb +63 -0
- data/df/etc_benchmark.rb +15 -0
- data/df/multi_agent_supervisor.rb +87 -0
- data/df/multi_client.rb +84 -0
- data/df/routing_benchmark.rb +60 -0
- data/df/sample_agent.rb +89 -0
- data/df/server.rb +54 -0
- data/df/sse_page.html +29 -0
- data/df/stress.rb +24 -0
- data/df/ws_page.html +38 -0
- data/e +0 -0
- data/examples/http_request_ws_server.rb +35 -0
- data/examples/http_server.rb +6 -6
- data/examples/http_server_form.rb +23 -0
- data/examples/http_unix_socket_server.rb +17 -0
- data/examples/http_ws_server.rb +10 -12
- data/examples/routing_server.rb +34 -0
- data/examples/ws_page.html +1 -2
- data/lib/tipi.rb +5 -1
- data/lib/tipi/digital_fabric.rb +7 -0
- data/lib/tipi/digital_fabric/agent.rb +225 -0
- data/lib/tipi/digital_fabric/agent_proxy.rb +265 -0
- data/lib/tipi/digital_fabric/executive.rb +100 -0
- data/lib/tipi/digital_fabric/executive/index.html +69 -0
- data/lib/tipi/digital_fabric/protocol.rb +90 -0
- data/lib/tipi/digital_fabric/request_adapter.rb +48 -0
- data/lib/tipi/digital_fabric/service.rb +230 -0
- data/lib/tipi/http1_adapter.rb +50 -14
- data/lib/tipi/http2_adapter.rb +4 -2
- data/lib/tipi/http2_stream.rb +20 -8
- data/lib/tipi/rack_adapter.rb +1 -1
- data/lib/tipi/version.rb +1 -1
- data/lib/tipi/websocket.rb +33 -29
- data/test/helper.rb +1 -2
- data/test/test_http_server.rb +10 -12
- data/test/test_request.rb +108 -0
- data/tipi.gemspec +7 -3
- metadata +57 -6
- data/lib/tipi/request.rb +0 -118
data/df/sample_agent.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'json'
|
6
|
+
require 'tipi/digital_fabric/protocol'
|
7
|
+
require 'tipi/digital_fabric/agent'
|
8
|
+
|
9
|
+
Protocol = DigitalFabric::Protocol
|
10
|
+
|
11
|
+
class SampleAgent < DigitalFabric::Agent
|
12
|
+
HTML_WS = IO.read(File.join(__dir__, 'ws_page.html'))
|
13
|
+
HTML_SSE = IO.read(File.join(__dir__, 'sse_page.html'))
|
14
|
+
|
15
|
+
def http_request(req)
|
16
|
+
path = req['headers'][':path']
|
17
|
+
case path
|
18
|
+
when '/agent'
|
19
|
+
send_df_message(Protocol.http_response(
|
20
|
+
req['id'],
|
21
|
+
'Hello, world!',
|
22
|
+
{},
|
23
|
+
true
|
24
|
+
))
|
25
|
+
when '/agent/ws'
|
26
|
+
send_df_message(Protocol.http_response(
|
27
|
+
req['id'],
|
28
|
+
HTML_WS,
|
29
|
+
{ 'Content-Type' => 'text/html' },
|
30
|
+
true
|
31
|
+
))
|
32
|
+
when '/agent/sse'
|
33
|
+
send_df_message(Protocol.http_response(
|
34
|
+
req['id'],
|
35
|
+
HTML_SSE,
|
36
|
+
{ 'Content-Type' => 'text/html' },
|
37
|
+
true
|
38
|
+
))
|
39
|
+
when '/agent/sse/events'
|
40
|
+
stream_sse_response(req)
|
41
|
+
else
|
42
|
+
send_df_message(Protocol.http_response(
|
43
|
+
req['id'],
|
44
|
+
nil,
|
45
|
+
{ ':status' => 400 },
|
46
|
+
true
|
47
|
+
))
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
def ws_request(req)
|
53
|
+
send_df_message(Protocol.ws_response(req['id'], {}))
|
54
|
+
|
55
|
+
10.times do
|
56
|
+
sleep 1
|
57
|
+
send_df_message(Protocol.ws_data(req['id'], Time.now.to_s))
|
58
|
+
end
|
59
|
+
send_df_message(Protocol.ws_close(req['id']))
|
60
|
+
end
|
61
|
+
|
62
|
+
def stream_sse_response(req)
|
63
|
+
send_df_message(Protocol.http_response(
|
64
|
+
req['id'],
|
65
|
+
nil,
|
66
|
+
{ 'Content-Type' => 'text/event-stream' },
|
67
|
+
false
|
68
|
+
))
|
69
|
+
10.times do
|
70
|
+
sleep 1
|
71
|
+
send_df_message(Protocol.http_response(
|
72
|
+
req['id'],
|
73
|
+
"data: #{Time.now}\n\n",
|
74
|
+
nil,
|
75
|
+
false
|
76
|
+
))
|
77
|
+
end
|
78
|
+
send_df_message(Protocol.http_response(
|
79
|
+
req['id'],
|
80
|
+
"retry: 0\n\n",
|
81
|
+
nil,
|
82
|
+
true
|
83
|
+
))
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
agent = SampleAgent.new('127.0.0.1', 4411, { path: '/agent' })
|
89
|
+
agent.run
|
data/df/server.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
require 'tipi/digital_fabric'
|
6
|
+
require 'tipi/digital_fabric/executive'
|
7
|
+
require 'json'
|
8
|
+
require 'fileutils'
|
9
|
+
FileUtils.cd(__dir__)
|
10
|
+
|
11
|
+
service = DigitalFabric::Service.new(token: 'foobar')
|
12
|
+
executive = DigitalFabric::Executive.new(service, { host: 'executive.realiteq.net' })
|
13
|
+
|
14
|
+
spin_loop(interval: 60) { GC.start }
|
15
|
+
|
16
|
+
class Polyphony::BaseException
|
17
|
+
attr_reader :caller_backtrace
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "pid: #{Process.pid}"
|
21
|
+
|
22
|
+
tcp_listener = spin do
|
23
|
+
opts = {
|
24
|
+
reuse_addr: true,
|
25
|
+
dont_linger: true,
|
26
|
+
}
|
27
|
+
puts 'Listening on localhost:4411'
|
28
|
+
server = Polyphony::Net.tcp_listen('0.0.0.0', 4411, opts)
|
29
|
+
server.accept_loop do |client|
|
30
|
+
spin do
|
31
|
+
service.incr_connection_count
|
32
|
+
Tipi.client_loop(client, opts) { |req| service.http_request(req) }
|
33
|
+
ensure
|
34
|
+
service.decr_connection_count
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
UNIX_SOCKET_PATH = '/tmp/df.sock'
|
40
|
+
|
41
|
+
unix_listener = spin do
|
42
|
+
puts "Listening on #{UNIX_SOCKET_PATH}"
|
43
|
+
FileUtils.rm(UNIX_SOCKET_PATH) if File.exists?(UNIX_SOCKET_PATH)
|
44
|
+
socket = UNIXServer.new(UNIX_SOCKET_PATH)
|
45
|
+
Tipi.accept_loop(socket, {}) { |req| service.http_request(req) }
|
46
|
+
end
|
47
|
+
|
48
|
+
begin
|
49
|
+
Fiber.await(tcp_listener, unix_listener)
|
50
|
+
rescue Interrupt
|
51
|
+
puts "Got SIGINT, shutting down gracefully"
|
52
|
+
service.graceful_shutdown
|
53
|
+
puts "post graceful shutdown"
|
54
|
+
end
|
data/df/sse_page.html
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<title>SSE Client</title>
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<h1>SSE Client</h1>
|
8
|
+
<script>
|
9
|
+
var connect = function () {
|
10
|
+
console.log("connecting...");
|
11
|
+
var eventSource = new EventSource("/agent/sse/events");
|
12
|
+
|
13
|
+
eventSource.addEventListener('open', function(e) {
|
14
|
+
console.log("connected");
|
15
|
+
document.querySelector('#status').innerText = 'connected';
|
16
|
+
return false;
|
17
|
+
}, false);
|
18
|
+
|
19
|
+
eventSource.addEventListener('message', function(e) {
|
20
|
+
document.querySelector('#msg').innerText = e.data;
|
21
|
+
}, false);
|
22
|
+
};
|
23
|
+
|
24
|
+
window.onload = connect;
|
25
|
+
</script>
|
26
|
+
<h1 id="status">disconnected</h1>
|
27
|
+
<h1 id="msg"></h1>
|
28
|
+
</body>
|
29
|
+
</html>
|
data/df/stress.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
FileUtils.cd(__dir__)
|
8
|
+
|
9
|
+
def monitor_process(cmd)
|
10
|
+
while true
|
11
|
+
puts "Starting #{cmd}"
|
12
|
+
Polyphony::Process.watch(cmd)
|
13
|
+
sleep 5
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
puts "pid: #{Process.pid}"
|
18
|
+
puts 'Starting stress test'
|
19
|
+
|
20
|
+
spin { monitor_process('ruby server.rb') }
|
21
|
+
spin { monitor_process('ruby multi_agent_supervisor.rb') }
|
22
|
+
spin { monitor_process('ruby multi_client.rb') }
|
23
|
+
|
24
|
+
sleep
|
data/df/ws_page.html
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<title>Websocket Client</title>
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<h1>WebSocket Client</h1>
|
8
|
+
<script>
|
9
|
+
var connect = function () {
|
10
|
+
console.log("connecting...")
|
11
|
+
var exampleSocket = new WebSocket("wss://dev.realiteq.net/agent");
|
12
|
+
|
13
|
+
exampleSocket.onopen = function (event) {
|
14
|
+
console.log("connected");
|
15
|
+
document.querySelector('#status').innerText = 'connected';
|
16
|
+
exampleSocket.send("Can you hear me?");
|
17
|
+
};
|
18
|
+
exampleSocket.onclose = function (event) {
|
19
|
+
console.log("disconnected");
|
20
|
+
document.querySelector('#status').innerText = 'disconnected';
|
21
|
+
setTimeout(function () {
|
22
|
+
// exampleSocket.removeAllListeners();
|
23
|
+
connect();
|
24
|
+
}, 1000);
|
25
|
+
}
|
26
|
+
exampleSocket.onmessage = function (event) {
|
27
|
+
console.log("got message", event.data);
|
28
|
+
document.querySelector('#msg').innerText = event.data;
|
29
|
+
console.log(event.data);
|
30
|
+
}
|
31
|
+
};
|
32
|
+
|
33
|
+
window.onload = connect;
|
34
|
+
</script>
|
35
|
+
<h1 id="status">disconnected</h1>
|
36
|
+
<h1 id="msg"></h1>
|
37
|
+
</body>
|
38
|
+
</html>
|
data/e
ADDED
File without changes
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
require 'tipi/websocket'
|
6
|
+
|
7
|
+
def ws_handler(conn)
|
8
|
+
timer = spin_loop(interval: 1) do
|
9
|
+
conn << Time.now.to_s
|
10
|
+
end
|
11
|
+
while (msg = conn.recv)
|
12
|
+
conn << "you said: #{msg}"
|
13
|
+
end
|
14
|
+
ensure
|
15
|
+
timer.stop
|
16
|
+
end
|
17
|
+
|
18
|
+
opts = {
|
19
|
+
reuse_addr: true,
|
20
|
+
dont_linger: true,
|
21
|
+
}
|
22
|
+
|
23
|
+
HTML = IO.read(File.join(__dir__, 'ws_page.html'))
|
24
|
+
|
25
|
+
puts "pid: #{Process.pid}"
|
26
|
+
puts 'Listening on port 4411...'
|
27
|
+
|
28
|
+
Tipi.serve('0.0.0.0', 4411, opts) do |req|
|
29
|
+
if req.upgrade_protocol == 'websocket'
|
30
|
+
conn = req.upgrade_to_websocket
|
31
|
+
ws_handler(conn)
|
32
|
+
else
|
33
|
+
req.respond(HTML, 'Content-Type' => 'text/html')
|
34
|
+
end
|
35
|
+
end
|
data/examples/http_server.rb
CHANGED
@@ -8,14 +8,14 @@ opts = {
|
|
8
8
|
dont_linger: true
|
9
9
|
}
|
10
10
|
|
11
|
+
puts "pid: #{Process.pid}"
|
12
|
+
puts 'Listening on port 4411...'
|
13
|
+
|
11
14
|
spin do
|
12
|
-
Tipi.serve('0.0.0.0',
|
15
|
+
Tipi.serve('0.0.0.0', 4411, opts) do |req|
|
13
16
|
req.respond("Hello world!\n")
|
14
17
|
rescue Exception => e
|
15
18
|
p e
|
16
19
|
end
|
17
|
-
|
18
|
-
|
19
|
-
puts "pid: #{Process.pid}"
|
20
|
-
puts 'Listening on port 1234...'
|
21
|
-
suspend
|
20
|
+
p 'done...'
|
21
|
+
end.await
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
|
6
|
+
opts = {
|
7
|
+
reuse_addr: true,
|
8
|
+
dont_linger: true
|
9
|
+
}
|
10
|
+
|
11
|
+
puts "pid: #{Process.pid}"
|
12
|
+
puts 'Listening on port 4411...'
|
13
|
+
|
14
|
+
spin do
|
15
|
+
Tipi.serve('0.0.0.0', 4411, opts) do |req|
|
16
|
+
body = req.read
|
17
|
+
body2 = req.read
|
18
|
+
req.respond("body: #{body} (body2: #{body2.inspect})\n")
|
19
|
+
rescue Exception => e
|
20
|
+
p e
|
21
|
+
end
|
22
|
+
p 'done...'
|
23
|
+
end.await
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
|
6
|
+
path = '/tmp/tipi.sock'
|
7
|
+
|
8
|
+
puts "pid: #{Process.pid}"
|
9
|
+
puts "Listening on #{path}"
|
10
|
+
|
11
|
+
FileUtils.rm(path) rescue nil
|
12
|
+
socket = UNIXServer.new(path)
|
13
|
+
Tipi.accept_loop(socket, {}) do |req|
|
14
|
+
req.respond("Hello world!\n")
|
15
|
+
rescue Exception => e
|
16
|
+
p e
|
17
|
+
end
|
data/examples/http_ws_server.rb
CHANGED
@@ -5,14 +5,14 @@ require 'tipi'
|
|
5
5
|
require 'tipi/websocket'
|
6
6
|
|
7
7
|
def ws_handler(conn)
|
8
|
-
timer =
|
9
|
-
|
10
|
-
conn << Time.now.to_s
|
11
|
-
end
|
8
|
+
timer = spin_loop(interval: 1) do
|
9
|
+
conn << Time.now.to_s
|
12
10
|
end
|
13
11
|
while (msg = conn.recv)
|
14
12
|
conn << "you said: #{msg}"
|
15
13
|
end
|
14
|
+
rescue Exception => e
|
15
|
+
p e
|
16
16
|
ensure
|
17
17
|
timer.stop
|
18
18
|
end
|
@@ -21,17 +21,15 @@ opts = {
|
|
21
21
|
reuse_addr: true,
|
22
22
|
dont_linger: true,
|
23
23
|
upgrade: {
|
24
|
-
websocket:
|
24
|
+
websocket: Tipi::Websocket.handler(&method(:ws_handler))
|
25
25
|
}
|
26
26
|
}
|
27
27
|
|
28
28
|
HTML = IO.read(File.join(__dir__, 'ws_page.html'))
|
29
29
|
|
30
|
-
spin do
|
31
|
-
Tipi.serve('0.0.0.0', 1234, opts) do |req|
|
32
|
-
req.respond(HTML, 'Content-Type' => 'text/html')
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
30
|
puts "pid: #{Process.pid}"
|
37
|
-
puts 'Listening on port
|
31
|
+
puts 'Listening on port 4411...'
|
32
|
+
|
33
|
+
Tipi.serve('0.0.0.0', 4411, opts) do |req|
|
34
|
+
req.respond(HTML, 'Content-Type' => 'text/html')
|
35
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
|
6
|
+
opts = {
|
7
|
+
reuse_addr: true,
|
8
|
+
dont_linger: true
|
9
|
+
}
|
10
|
+
|
11
|
+
puts "pid: #{Process.pid}"
|
12
|
+
puts 'Listening on port 4411...'
|
13
|
+
|
14
|
+
app = Tipi.route do |r|
|
15
|
+
r.root do
|
16
|
+
r.redirect '/hello'
|
17
|
+
end
|
18
|
+
r.on 'hello' do
|
19
|
+
r.get 'world' do
|
20
|
+
r.respond 'Hello world'
|
21
|
+
end
|
22
|
+
r.get do
|
23
|
+
r.respond 'Hello'
|
24
|
+
end
|
25
|
+
r.post do
|
26
|
+
puts 'Someone said Hello'
|
27
|
+
r.redirect '/'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
spin do
|
33
|
+
Tipi.serve('0.0.0.0', 4411, opts, &app)
|
34
|
+
end.await
|
data/examples/ws_page.html
CHANGED
@@ -6,14 +6,13 @@
|
|
6
6
|
<body>
|
7
7
|
<script>
|
8
8
|
var connect = function () {
|
9
|
-
var exampleSocket = new WebSocket("
|
9
|
+
var exampleSocket = new WebSocket("wss://dev.realiteq.net/");
|
10
10
|
|
11
11
|
exampleSocket.onopen = function (event) {
|
12
12
|
document.querySelector('#status').innerText = 'connected';
|
13
13
|
exampleSocket.send("Can you hear me?");
|
14
14
|
};
|
15
15
|
exampleSocket.onclose = function (event) {
|
16
|
-
console.log('onclose');
|
17
16
|
document.querySelector('#status').innerText = 'disconnected';
|
18
17
|
setTimeout(function () {
|
19
18
|
// exampleSocket.removeAllListeners();
|
data/lib/tipi.rb
CHANGED
@@ -40,7 +40,7 @@ module Tipi
|
|
40
40
|
adapter = protocol_adapter(client, opts)
|
41
41
|
adapter.each(&handler)
|
42
42
|
ensure
|
43
|
-
client.close
|
43
|
+
client.close rescue nil
|
44
44
|
end
|
45
45
|
|
46
46
|
def protocol_adapter(socket, opts)
|
@@ -49,5 +49,9 @@ module Tipi
|
|
49
49
|
klass = use_http2 ? HTTP2Adapter : HTTP1Adapter
|
50
50
|
klass.new(socket, opts)
|
51
51
|
end
|
52
|
+
|
53
|
+
def route(&block)
|
54
|
+
proc { |req| req.route(&block) }
|
55
|
+
end
|
52
56
|
end
|
53
57
|
end
|