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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile.lock +10 -4
  4. data/LICENSE +1 -1
  5. data/TODO.md +11 -47
  6. data/df/agent.rb +63 -0
  7. data/df/etc_benchmark.rb +15 -0
  8. data/df/multi_agent_supervisor.rb +87 -0
  9. data/df/multi_client.rb +84 -0
  10. data/df/routing_benchmark.rb +60 -0
  11. data/df/sample_agent.rb +89 -0
  12. data/df/server.rb +54 -0
  13. data/df/sse_page.html +29 -0
  14. data/df/stress.rb +24 -0
  15. data/df/ws_page.html +38 -0
  16. data/e +0 -0
  17. data/examples/http_request_ws_server.rb +35 -0
  18. data/examples/http_server.rb +6 -6
  19. data/examples/http_server_form.rb +23 -0
  20. data/examples/http_unix_socket_server.rb +17 -0
  21. data/examples/http_ws_server.rb +10 -12
  22. data/examples/routing_server.rb +34 -0
  23. data/examples/ws_page.html +1 -2
  24. data/lib/tipi.rb +5 -1
  25. data/lib/tipi/digital_fabric.rb +7 -0
  26. data/lib/tipi/digital_fabric/agent.rb +225 -0
  27. data/lib/tipi/digital_fabric/agent_proxy.rb +265 -0
  28. data/lib/tipi/digital_fabric/executive.rb +100 -0
  29. data/lib/tipi/digital_fabric/executive/index.html +69 -0
  30. data/lib/tipi/digital_fabric/protocol.rb +90 -0
  31. data/lib/tipi/digital_fabric/request_adapter.rb +48 -0
  32. data/lib/tipi/digital_fabric/service.rb +230 -0
  33. data/lib/tipi/http1_adapter.rb +50 -14
  34. data/lib/tipi/http2_adapter.rb +4 -2
  35. data/lib/tipi/http2_stream.rb +20 -8
  36. data/lib/tipi/rack_adapter.rb +1 -1
  37. data/lib/tipi/version.rb +1 -1
  38. data/lib/tipi/websocket.rb +33 -29
  39. data/test/helper.rb +1 -2
  40. data/test/test_http_server.rb +10 -12
  41. data/test/test_request.rb +108 -0
  42. data/tipi.gemspec +7 -3
  43. metadata +57 -6
  44. data/lib/tipi/request.rb +0 -118
@@ -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
@@ -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', 1234, opts) do |req|
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
- end
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
@@ -5,14 +5,14 @@ require 'tipi'
5
5
  require 'tipi/websocket'
6
6
 
7
7
  def ws_handler(conn)
8
- timer = spin do
9
- throttled_loop(1) do
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: Polyphony::Websocket.handler(&method(:ws_handler))
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 1234...'
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
@@ -6,14 +6,13 @@
6
6
  <body>
7
7
  <script>
8
8
  var connect = function () {
9
- var exampleSocket = new WebSocket("ws://localhost:1234");
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