tipi 0.33 → 0.37.1

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