tipi 0.32 → 0.37

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/Gemfile.lock +10 -4
  4. data/LICENSE +1 -1
  5. data/TODO.md +13 -47
  6. data/bin/tipi +13 -0
  7. data/df/agent.rb +63 -0
  8. data/df/etc_benchmark.rb +15 -0
  9. data/df/multi_agent_supervisor.rb +87 -0
  10. data/df/multi_client.rb +84 -0
  11. data/df/routing_benchmark.rb +60 -0
  12. data/df/sample_agent.rb +89 -0
  13. data/df/server.rb +54 -0
  14. data/df/sse_page.html +29 -0
  15. data/df/stress.rb +24 -0
  16. data/df/ws_page.html +38 -0
  17. data/e +0 -0
  18. data/examples/http_request_ws_server.rb +35 -0
  19. data/examples/http_server.rb +6 -6
  20. data/examples/http_server_forked.rb +4 -5
  21. data/examples/http_server_form.rb +23 -0
  22. data/examples/http_server_throttled_accept.rb +23 -0
  23. data/examples/http_unix_socket_server.rb +17 -0
  24. data/examples/http_ws_server.rb +10 -12
  25. data/examples/routing_server.rb +34 -0
  26. data/examples/ws_page.html +1 -2
  27. data/lib/tipi.rb +7 -5
  28. data/lib/tipi/configuration.rb +1 -1
  29. data/lib/tipi/digital_fabric.rb +7 -0
  30. data/lib/tipi/digital_fabric/agent.rb +225 -0
  31. data/lib/tipi/digital_fabric/agent_proxy.rb +265 -0
  32. data/lib/tipi/digital_fabric/executive.rb +100 -0
  33. data/lib/tipi/digital_fabric/executive/index.html +69 -0
  34. data/lib/tipi/digital_fabric/protocol.rb +90 -0
  35. data/lib/tipi/digital_fabric/request_adapter.rb +48 -0
  36. data/lib/tipi/digital_fabric/service.rb +230 -0
  37. data/lib/tipi/http1_adapter.rb +50 -16
  38. data/lib/tipi/http2_adapter.rb +5 -3
  39. data/lib/tipi/http2_stream.rb +19 -7
  40. data/lib/tipi/rack_adapter.rb +11 -3
  41. data/lib/tipi/version.rb +1 -1
  42. data/lib/tipi/websocket.rb +33 -29
  43. data/test/helper.rb +1 -2
  44. data/test/test_http_server.rb +3 -2
  45. data/test/test_request.rb +108 -0
  46. data/tipi.gemspec +7 -3
  47. metadata +59 -7
  48. data/lib/tipi/request.rb +0 -118
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0fd31b18e0c98a8beb780933f7adef48e0e4403a765cc64f37ef02515d94723
4
- data.tar.gz: 1cc72a408cd8fe9b4d1528bd7b1200f3e7a71098596f5b18812d2d75b6120936
3
+ metadata.gz: 4129b10a7f6b5fb92e4e4784bf8cfd12ea2659840b616b7627bf28d1dc423e87
4
+ data.tar.gz: 7f5f71c4a870e33c7de84fd292568b3aca582dcc1adc9496d99e636328b3df4b
5
5
  SHA512:
6
- metadata.gz: d4fb34dbec4798561322546a90f8104f3e91bd806923ba6d73e3267c06ab05499104431bf15f1abb0984d014626a062267305370470d80a7c61776a2f0c0bd8b
7
- data.tar.gz: b0b7d9ec9e0536acef6f9675d592d7c48123bf591468326178b96ef673f194c44a43dcd57ed7f860d78acfe27f71192378f328b5531efff73731cd4b3875c3ee
6
+ metadata.gz: c1bfcbf1cdf4fa2868554e690a71543d89a37bf2d8cfa27cf604e453fb411d5b1dd2e4bc10678cbe79db6a94238ffef1a09e53771f7e8d8e71f2512b87f7265a
7
+ data.tar.gz: 4f476a65800ffb6cbcdfde6e10ed9e1dd7b9fcc4ddd69ad2babed1a747cba4f94e05659cbd235c1f7e71e1595851d77896262d45d1036d216d866b7bdc3f914a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ ## 0.37 2021-02-15
2
+
3
+ * Update upgrade mechanism to work with updated Qeweney API
4
+
5
+ ## 0.36 2021-02-12
6
+
7
+ * Use `Qeweney::Status` constants
8
+
9
+ ## 0.35 2021-02-10
10
+
11
+ * Extract Request class into separate [qeweney](https://github.com/digital-fabric/qeweney) gem
12
+
13
+ ## 0.34 2021-02-07
14
+
15
+ * Implement digital fabric service and agents
16
+ * Add multipart and urlencoded form data parsing
17
+ * Improve request body reading behaviour
18
+ * Add more `Request` information methods
19
+ * Add access to connection for HTTP2 requests
20
+ * Allow calling `Request#send_chunk` with empty chunk
21
+ * Add support for handling protocol upgrades from within request handler
22
+
23
+ ## 0.33 2020-11-20
24
+
25
+ * Update code for Polyphony 0.47.5
26
+ * Add support for Rack::File body to Tipi::RackAdapter
27
+
1
28
  ## 0.32 2020-08-14
2
29
 
3
30
  * Respond with array of strings instead of concatenating for HTTP 1
data/Gemfile.lock CHANGED
@@ -1,10 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tipi (0.32)
4
+ tipi (0.37)
5
5
  http-2 (~> 0.10.0)
6
6
  http_parser.rb (~> 0.6.0)
7
- polyphony (~> 0.45)
7
+ msgpack (~> 1.4.2)
8
+ polyphony (~> 0.51.0)
9
+ qeweney (~> 0.5)
8
10
  rack (>= 2.0.8, < 2.3.0)
9
11
  websocket (~> 1.2.8)
10
12
 
@@ -14,6 +16,7 @@ GEM
14
16
  ansi (1.5.0)
15
17
  builder (3.2.4)
16
18
  docile (1.3.2)
19
+ escape_utils (1.2.1)
17
20
  http-2 (0.10.2)
18
21
  http_parser.rb (0.6.0)
19
22
  json (2.3.1)
@@ -24,7 +27,10 @@ GEM
24
27
  builder
25
28
  minitest (>= 5.0)
26
29
  ruby-progressbar
27
- polyphony (0.45.2)
30
+ msgpack (1.4.2)
31
+ polyphony (0.51.0)
32
+ qeweney (0.5)
33
+ escape_utils (~> 1.2.1)
28
34
  rack (2.2.3)
29
35
  rake (12.3.3)
30
36
  ruby-progressbar (1.10.1)
@@ -33,7 +39,7 @@ GEM
33
39
  json (>= 1.8, < 3)
34
40
  simplecov-html (~> 0.10.0)
35
41
  simplecov-html (0.10.2)
36
- websocket (1.2.8)
42
+ websocket (1.2.9)
37
43
 
38
44
  PLATFORMS
39
45
  ruby
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2018 Sharon Rosner
3
+ Copyright (c) 2021 Sharon Rosner
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/TODO.md CHANGED
@@ -1,10 +1,20 @@
1
+ # Digital Fabric
2
+
3
+ Problems to fix:
4
+
5
+ - Memory leak (in server? multi agent? multi client?)
6
+
1
7
  # Roadmap
2
8
 
3
9
  - Update README (get rid of non-http stuff)
4
10
  - Improve Rack spec compliance, add tests
5
- - Homogenize HTTP 1 and HTTP 2 headers - upcase ? downcase ?
11
+ - Homogenize HTTP 1 and HTTP 2 headers - downcase symbols
6
12
 
7
- ## 0.30
13
+ - Use `http-2-next` instead of `http-2` for http/2
14
+ - https://gitlab.com/honeyryderchuck/http-2-next
15
+ - Open an issue there, ask what's the difference between the two gems?
16
+
17
+ ## 0.35
8
18
 
9
19
  - Add more poly CLI commands and options:
10
20
 
@@ -16,51 +26,7 @@
16
26
  - set port to bind to
17
27
  - set forking process count
18
28
 
19
- ## 0.31 Working Sinatra application
29
+ ## 0.36 Working Sinatra application
20
30
 
21
31
  - app with database access (postgresql)
22
32
  - benchmarks!
23
-
24
- # HTTP Client Agent
25
-
26
- The concurrency model and the fact that we want to serve the response object on
27
- receiving headers and let the user lazily read the response body, means we'll
28
- need to change the API to accept a block:
29
-
30
- ```ruby
31
- # current API
32
- resp = Agent.get('http://acme.org')
33
- puts resp.body
34
-
35
- # proposed API
36
- Agent.get('http://acme.org') do |resp|
37
- puts resp.body
38
- end
39
- ```
40
-
41
- While the block is running, the connection adapter is acquired. Once the block
42
- is done running, the request (and response) can be discarded. The problem with
43
- that if we spin up a coprocess from that block we risk all kinds of race
44
- conditions and weird behaviours.
45
-
46
- A compromise might be to allow the two: doing a `get` without providing a block
47
- will return a response object that already has the body (i.e. the entire
48
- response has already been received). Doing a `get` with a block will invoke the
49
- block once headers are received, letting the user's code stream the body:
50
-
51
- ```ruby
52
- def request(ctx, &block)
53
- ...
54
- connection_manager.acquire do |adapter|
55
- response = adapter.request(ctx)
56
- if block
57
- block.(response)
58
- else
59
- # wait for body
60
- response.body
61
- end
62
- response
63
- end
64
- end
65
- ```
66
-
data/bin/tipi CHANGED
@@ -7,7 +7,20 @@ require File.expand_path('../lib/tipi/configuration', __dir__)
7
7
  config = {}
8
8
  #config[:forked] = 4
9
9
 
10
+ puts DATA.read
11
+ puts
12
+
10
13
  configuration_manager = spin { Tipi::Configuration.supervise_config }
11
14
 
12
15
  configuration_manager << config
13
16
  configuration_manager.await
17
+
18
+ __END__
19
+
20
+ ooo
21
+ oo
22
+ o
23
+ \|/
24
+ / \ Tipi - A better web server for a better world
25
+ /___\
26
+
data/df/agent.rb ADDED
@@ -0,0 +1,63 @@
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
+ def initialize(id, server_url)
13
+ @id = id
14
+ super(server_url, { host: "#{id}.realiteq.net" }, 'foobar')
15
+ @name = "agent-#{@id}"
16
+ end
17
+
18
+ def http_request(req)
19
+ return streaming_http_request(req) if req.path == '/streaming'
20
+ return form_http_request(req) if req.path == '/form'
21
+
22
+ req.respond({ id: @id, time: Time.now.to_i }.to_json)
23
+ end
24
+
25
+ def streaming_http_request(req)
26
+ req.send_headers({ 'Content-Type': 'text/json' })
27
+
28
+ 60.times do
29
+ sleep 1
30
+ do_some_activity
31
+ req.send_chunk({ id: @id, time: Time.now.to_i }.to_json)
32
+ end
33
+
34
+ req.finish
35
+ rescue Polyphony::Terminate
36
+ req.respond(' * shutting down *') if Fiber.current.graceful_shutdown?
37
+ rescue Exception => e
38
+ p e
39
+ puts e.backtrace.join("\n")
40
+ end
41
+
42
+ def form_http_request(req)
43
+ body = req.read
44
+ form_data = Tipi::Request.parse_form_data(body, req.headers)
45
+ req.respond({ form_data: form_data, headers: req.headers }.to_json, { 'Content-Type': 'text/json' })
46
+ end
47
+
48
+ def do_some_activity
49
+ File.open('/tmp/df-test.log', 'a+') { |f| sleep rand; f.puts "#{Time.now} #{@name} #{generate_data(2**8)}" }
50
+ end
51
+
52
+ def generate_data(length)
53
+ charset = Array('A'..'Z') + Array('a'..'z') + Array('0'..'9')
54
+ Array.new(length) { charset.sample }.join
55
+ end
56
+ end
57
+
58
+ # id = ARGV[0]
59
+ # puts "Starting agent #{id} pid: #{Process.pid}"
60
+
61
+ # spin_loop(interval: 60) { GC.start }
62
+ # SampleAgent.new(id, '/tmp/df.sock').run
63
+ # SampleAgent.new(id, 'localhost:4411').run
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ def generate
6
+ SecureRandom.uuid
7
+ end
8
+
9
+ count = 100000
10
+
11
+ GC.disable
12
+ t0 = Time.now
13
+ count.times { generate }
14
+ elapsed = Time.now - t0
15
+ puts "rate: #{count / elapsed}/s"
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'json'
6
+
7
+ require 'fileutils'
8
+ FileUtils.cd(__dir__)
9
+
10
+ require_relative 'agent'
11
+
12
+ class AgentManager
13
+ def initialize
14
+ @running_agents = {}
15
+ @pending_actions = Queue.new
16
+ @processor = spin_loop { process_pending_action }
17
+ end
18
+
19
+ def process_pending_action
20
+ action = @pending_actions.shift
21
+ case action[:kind]
22
+ when :start
23
+ start_agent(action[:spec])
24
+ when :stop
25
+ stop_agent(action[:spec])
26
+ end
27
+ end
28
+
29
+ def start_agent(spec)
30
+ return if @running_agents[spec]
31
+
32
+ @running_agents[spec] = spin do
33
+ while true
34
+ launch_agent_from_spec(spec)
35
+ sleep 1
36
+ end
37
+ ensure
38
+ @running_agents.delete(spec)
39
+ end
40
+ end
41
+
42
+ def stop_agent(spec)
43
+ fiber = @running_agents[spec]
44
+ return unless fiber
45
+
46
+ fiber.terminate
47
+ fiber.await
48
+ end
49
+
50
+ def update
51
+ return unless @pending_actions.empty?
52
+
53
+ current_specs = @running_agents.keys
54
+ updated_specs = agent_specs
55
+
56
+ to_start = updated_specs - current_specs
57
+ to_stop = current_specs - current_specs
58
+
59
+ to_start.each { |s| @pending_actions << { kind: :start, spec: s } }
60
+ to_stop.each { |s| @pending_actions << { kind: :stop, spec: s } }
61
+ end
62
+
63
+ def run
64
+ every(2) { update }
65
+ end
66
+ end
67
+
68
+ class RealityAgentManager < AgentManager
69
+ def agent_specs
70
+ (1..400).map { |i| { id: i } }
71
+ end
72
+
73
+ def launch_agent_from_spec(spec)
74
+ # Polyphony::Process.watch("ruby agent.rb #{spec[:id]}")
75
+ Polyphony::Process.watch do
76
+ spin_loop(interval: 60) { GC.start }
77
+ agent = SampleAgent.new(spec[:id], '/tmp/df.sock')
78
+ puts "Starting agent #{spec[:id]} pid: #{Process.pid}"
79
+ agent.run
80
+ end
81
+ end
82
+ end
83
+
84
+ puts "Agent manager pid: #{Process.pid}"
85
+
86
+ manager = RealityAgentManager.new
87
+ manager.run
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'http/parser'
6
+
7
+ class Client
8
+ def initialize(id, host, port, http_host, interval)
9
+ @id = id
10
+ @host = host
11
+ @port = port
12
+ @http_host = http_host
13
+ @interval = interval.to_f
14
+ @interval_delta = @interval / 2
15
+ end
16
+
17
+ def run
18
+ while true
19
+ connect && issue_requests
20
+ sleep 5
21
+ end
22
+ end
23
+
24
+ def connect
25
+ @socket = Polyphony::Net.tcp_connect(@host, @port)
26
+ rescue SystemCallError
27
+ false
28
+ end
29
+
30
+ REQUEST = <<~HTTP
31
+ GET / HTTP/1.1
32
+ Host: %s
33
+
34
+ HTTP
35
+
36
+ def issue_requests
37
+ @parser = Http::Parser.new
38
+ @parser.on_message_complete = proc { @got_reply = true }
39
+ @parser.on_body = proc { |chunk| @response = chunk }
40
+
41
+ while true
42
+ do_request
43
+ sleep rand((@interval - @interval_delta)..(@interval + @interval_delta))
44
+ end
45
+ rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED => e
46
+ # fail quitely
47
+ ensure
48
+ @parser = nil
49
+ end
50
+
51
+ def do_request
52
+ @got_reply = nil
53
+ @response = nil
54
+ @socket << format(REQUEST, @http_host)
55
+ wait_for_response
56
+ # if @parser.status_code != 200
57
+ # puts "Got status code #{@parser.status_code} from #{@http_host} => #{@parser.headers && @parser.headers['X-Request-ID']}"
58
+ # end
59
+ # puts "#{Time.now} [client-#{@id}] #{@http_host} => #{@response || '<error>'}"
60
+ end
61
+
62
+ def wait_for_response
63
+ @socket.recv_loop do |data|
64
+ @parser << data
65
+ return @response if @got_reply
66
+ end
67
+ end
68
+ end
69
+
70
+ def spin_client(id, host)
71
+ spin do
72
+ client = Client.new(id, 'localhost', 4411, host, 30)
73
+ client.run
74
+ end
75
+ end
76
+
77
+ spin_loop(interval: 60) { GC.start }
78
+
79
+ 10000.times { |id| spin_client(id, "#{rand(1..400)}.realiteq.net") }
80
+
81
+ trap('SIGINT') { exit! }
82
+
83
+ puts "Multi client pid: #{Process.pid}"
84
+ sleep