tipi 0.43 → 0.45

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +1 -3
  4. data/CHANGELOG.md +12 -0
  5. data/Gemfile +3 -1
  6. data/Gemfile.lock +14 -7
  7. data/README.md +184 -8
  8. data/Rakefile +1 -7
  9. data/benchmarks/bm_http1_parser.rb +1 -1
  10. data/bin/benchmark +0 -0
  11. data/bin/h1pd +0 -0
  12. data/bm.png +0 -0
  13. data/df/agent.rb +1 -1
  14. data/df/sample_agent.rb +2 -2
  15. data/df/server_utils.rb +1 -1
  16. data/examples/hello.rb +5 -0
  17. data/examples/http_server.js +1 -1
  18. data/examples/http_server_graceful.rb +1 -1
  19. data/examples/https_server.rb +41 -18
  20. data/examples/rack_server_forked.rb +26 -0
  21. data/examples/rack_server_https_forked.rb +1 -1
  22. data/examples/websocket_demo.rb +1 -1
  23. data/lib/tipi/acme.rb +46 -39
  24. data/lib/tipi/cli.rb +79 -16
  25. data/lib/tipi/config_dsl.rb +13 -13
  26. data/lib/tipi/configuration.rb +2 -2
  27. data/lib/tipi/controller/bare_polyphony.rb +0 -0
  28. data/lib/tipi/controller/bare_stock.rb +10 -0
  29. data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
  30. data/lib/tipi/controller/web_polyphony.rb +351 -0
  31. data/lib/tipi/controller/web_stock.rb +631 -0
  32. data/lib/tipi/controller.rb +12 -0
  33. data/lib/tipi/digital_fabric/agent.rb +3 -3
  34. data/lib/tipi/digital_fabric/agent_proxy.rb +11 -5
  35. data/lib/tipi/digital_fabric/executive.rb +1 -1
  36. data/lib/tipi/digital_fabric/protocol.rb +1 -1
  37. data/lib/tipi/digital_fabric/service.rb +8 -8
  38. data/lib/tipi/handler.rb +2 -2
  39. data/lib/tipi/http1_adapter.rb +32 -27
  40. data/lib/tipi/http2_adapter.rb +10 -10
  41. data/lib/tipi/http2_stream.rb +14 -14
  42. data/lib/tipi/rack_adapter.rb +2 -2
  43. data/lib/tipi/response_extensions.rb +1 -1
  44. data/lib/tipi/supervisor.rb +75 -0
  45. data/lib/tipi/version.rb +1 -1
  46. data/lib/tipi/websocket.rb +3 -3
  47. data/lib/tipi.rb +4 -83
  48. data/test/coverage.rb +2 -2
  49. data/test/test_http_server.rb +14 -14
  50. data/tipi.gemspec +3 -2
  51. metadata +30 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7a42c93f436a24b74aef2fc9de54d7f01b587ec42b2897d0b0c24a9090776d8
4
- data.tar.gz: 1fa532504e9c79d620306b4ddfbd01bf1d1dc39b563ec2a2c3cfec9cb00a1b64
3
+ metadata.gz: e6abb7798a69adc36e18dda822f7ff025f9f9e286eba50ccaf3905a74f29ef32
4
+ data.tar.gz: 41ee73bf589d2d4bd764aa58cb71e43477bc52caae708bd05ae4ae6372275415
5
5
  SHA512:
6
- metadata.gz: 93282cde73eca46289cf212e76c90e7390dcd9e48e8594bd756d5f59ae3bc5fed41e61d216c85cca9c9d2c8fca1061ee0159f8cc0ca575fe0b7149a21b1edc24
7
- data.tar.gz: 6fd1dc4de05351d0b7da91a7344798145f31659f4663f451355222ce63bb8504fb123c562199820b4f217bc14d064bb529fcc0c9ea495863856bd79f073a491b
6
+ metadata.gz: b23c0bfe27b9e6ff203508ab1a24ecbf10d954996ce9af2045a7d427ccec5746e9ef9df146353e4c134a56c12c4f2518141b46fe342123613c3da713a086a90f
7
+ data.tar.gz: a2f0fdc533ab39a23dad733535a9371a44a5809485c04114d0e054f4cb4bcb47b0cb27a266d5d58a7e01b8db06daf79a3aa944151de50e61da33f824e4651e43
@@ -0,0 +1 @@
1
+ github: ciconia
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest]
11
- ruby: [2.6, 2.7]
11
+ ruby: [2.6, 2.7, 3.0]
12
12
 
13
13
  name: >-
14
14
  ${{matrix.os}}, ${{matrix.ruby}}
@@ -25,7 +25,5 @@ jobs:
25
25
  POLYPHONY_USE_LIBEV=1 bundle install
26
26
  - name: Show Linux kernel version
27
27
  run: uname -r
28
- - name: Compile C-extension
29
- run: bundle exec rake compile
30
28
  - name: Run tests
31
29
  run: bundle exec rake test
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 0.45 2021-10-25
2
+
3
+ - Remove `http_parser.rb` dependency (#14) - thanks @SwagDevOps
4
+ - Use `argv` argument in `Tipi.opts_from_argv` (#13) - thanks @SwagDevOps
5
+ - Ignore `ArgumentError` in `#parse_headers`
6
+
7
+ ## 0.44 2021-09-29
8
+
9
+ - Implement compatibility mode for HTTP/1 (WIP)
10
+ - Add option parsing for CLI tool
11
+ - Implement supervisor-controller-worker model in CLI tool
12
+
1
13
  ## 0.43 2021-08-20
2
14
 
3
15
  - Extract HTTP/1 parser into a separate gem:
data/Gemfile CHANGED
@@ -1,7 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
- %w{polyphony qeweney h1p}.each do |dep|
4
+
5
+ # for local development
6
+ %w{polyphony ever qeweney h1p}.each do |dep|
5
7
  dir = "../#{dep}"
6
8
  gem(dep, path: dir) if File.directory?(dir)
7
9
  end
data/Gemfile.lock CHANGED
@@ -1,3 +1,8 @@
1
+ PATH
2
+ remote: ../ever
3
+ specs:
4
+ ever (0.1)
5
+
1
6
  PATH
2
7
  remote: ../h1p
3
8
  specs:
@@ -6,7 +11,7 @@ PATH
6
11
  PATH
7
12
  remote: ../polyphony
8
13
  specs:
9
- polyphony (0.70)
14
+ polyphony (0.71)
10
15
 
11
16
  PATH
12
17
  remote: ../qeweney
@@ -17,14 +22,15 @@ PATH
17
22
  PATH
18
23
  remote: .
19
24
  specs:
20
- tipi (0.43)
25
+ tipi (0.45)
21
26
  acme-client (~> 2.0.8)
27
+ ever (~> 0.1)
22
28
  extralite (~> 1.2)
23
29
  h1p (~> 0.2)
24
30
  http-2 (~> 0.11)
25
31
  localhost (~> 1.1.4)
26
32
  msgpack (~> 1.4.2)
27
- polyphony (~> 0.69)
33
+ polyphony (~> 0.71)
28
34
  qeweney (~> 0.14)
29
35
  rack (>= 2.0.8, < 2.3.0)
30
36
  websocket (~> 1.2.8)
@@ -32,14 +38,14 @@ PATH
32
38
  GEM
33
39
  remote: https://rubygems.org/
34
40
  specs:
35
- acme-client (2.0.8)
41
+ acme-client (2.0.9)
36
42
  faraday (>= 0.17, < 2.0.0)
37
43
  cuba (3.9.3)
38
44
  rack (>= 1.6.0)
39
45
  docile (1.4.0)
40
46
  escape_utils (1.2.1)
41
- extralite (1.3)
42
- faraday (1.7.0)
47
+ extralite (1.4)
48
+ faraday (1.8.0)
43
49
  faraday-em_http (~> 1.0)
44
50
  faraday-em_synchrony (~> 1.0)
45
51
  faraday-excon (~> 1.1)
@@ -60,7 +66,7 @@ GEM
60
66
  faraday-rack (1.0.0)
61
67
  http-2 (0.11.0)
62
68
  json (2.5.1)
63
- localhost (1.1.8)
69
+ localhost (1.1.9)
64
70
  memory_profiler (1.0.0)
65
71
  minitest (5.11.3)
66
72
  msgpack (1.4.2)
@@ -80,6 +86,7 @@ PLATFORMS
80
86
 
81
87
  DEPENDENCIES
82
88
  cuba (~> 3.9.3)
89
+ ever!
83
90
  h1p!
84
91
  memory_profiler (~> 1.0.0)
85
92
  minitest (~> 5.11.3)
data/README.md CHANGED
@@ -3,27 +3,203 @@
3
3
  # Tipi - the All-in-one Web Server for Ruby Apps
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/tipi.svg)](http://rubygems.org/gems/tipi)
6
- [![Modulation Test](https://github.com/digital-fabric/tipi/workflows/Tests/badge.svg)](https://github.com/digital-fabric/tipi/actions?query=workflow%3ATests)
6
+ [![Tipi Test](https://github.com/digital-fabric/tipi/workflows/Tests/badge.svg)](https://github.com/digital-fabric/tipi/actions?query=workflow%3ATests)
7
7
  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/tipi/blob/master/LICENSE)
8
8
 
9
9
  ## What is Tipi?
10
10
 
11
11
  Tipi is an integrated, feature-complete HTTP/S server for Ruby applications.
12
- Tipi is built on Polyphony, a robust, high-performance library for building
13
- highly-concurrent applications in Ruby. Tipi can be used to serve any Rack
14
- application or set of static files directly without having to employ a
15
- reverse-proxy such as Nginx.
12
+ Tipi is built on top of
13
+ [Polyphony](https://github.com/digital-fabric/polyphony), a robust,
14
+ high-performance library for building highly-concurrent applications in Ruby.
15
+ Tipi can be used to serve any Rack application or set of static files directly
16
+ without having to employ a reverse-proxy such as Nginx.
16
17
 
17
18
  ## Features
18
19
 
19
20
  * High-performance, highly concurrent web server based on
20
21
  [Polyphony](https://github.com/digital-fabric/polyphony)
21
- * Full support for HTTP, HTTP/2, WebSocket protocols
22
+ * Full support for HTTP/1, HTTP/2, WebSocket protocols
22
23
  * Built-in SSL termination for secure, encrypted connections
23
- * Automatic ALPN protocol selection
24
+ * **Automatic SSL certificates** using ACME providers such as Let's Encrypt (WIP)
25
+ * Automatic ALPN protocol selection for serving HTTP/2
24
26
  * Request and response body streaming for efficient downloads and uploads
25
27
  * Full support for Rack-based apps
26
28
 
29
+ ## Benchmarks
30
+
31
+ > Caveat emptor: the following results were obtained with an ad-hoc, manual
32
+ > process. I am not really familiar with the servers I compared Tipi against,
33
+ > and I ran them in their default configuration (apart from setting the number
34
+ > of workers). Take these results with a bunch of salt.
35
+
36
+ <img src="bm.png" style="width: 480px">
37
+
38
+ | |Tipi|Puma|Falcon|Unicorn|
39
+ |-|---:|---:|-----:|------:|
40
+ |HTTP/1.1|138629|34573|40714|7438|
41
+ |HTTPS/2|56762|n/a|34226|n/a|
42
+
43
+ ### Methodology
44
+
45
+ - All servers ran the same "Hello world" [Rack
46
+ application](https://github.com/digital-fabric/tipi/blob/master/examples/hello.ru)
47
+ - Each server was run with 4 forked worker processes:
48
+ - Tipi: `tipi -w4 -flocalhost:10080:10443 examples/hello.ru`
49
+ - [Puma](https://github.com/puma/puma): `puma -w 4 examples/hello.ru`
50
+ - [Falcon](https://github.com/socketry/falcon/): `falcon -n 4 -b http://localhost:9292/ -c examples/hello.ru`
51
+ - [Unicorn](https://yhbt.net/unicorn/): `unicorn -c u.conf examples/hello.ru`
52
+ with the configuration file containing the directive `worker_processes 4`
53
+ - The benchmark results were obtained using `wrk -d60 -t4 -c64 <url>`
54
+ - All servers were run on Ruby 2.7.2p137
55
+ - Machine specs: i5-8350U@1.7GHzx8 CPU, 8GB of RAM, running Linux kernel version 5.13.7
56
+ - Puma does not support HTTP/2.
57
+ - As far as I could tell Unicorn does not support SSL termination.
58
+
59
+ ## Running Tipi
60
+
61
+ To run Tipi, run the included `tipi` command. Alternatively you can add tipi as
62
+ a dependency to your Gemfile, then run `bundle exec tipi`. By default
63
+
64
+ Tipi can be used to drive Rack apps or alternatively any app using the
65
+ [Qeweney](https://github.com/digital-fabric/qeweney) request-response interface.
66
+
67
+ ### Running Rack apps
68
+
69
+ Use the `tipi` command to start your app:
70
+
71
+ ```bash
72
+ $ bundle exec tipi myapp.ru
73
+ ```
74
+
75
+ ### Running Qeweney apps
76
+
77
+ ```bash
78
+ $ bundle exec tipi myapp.rb
79
+ ```
80
+
81
+ The app script file should define an `app` method that returns a proc/lambda
82
+ taking a single `Qeweney::Request` argument. Here's an example:
83
+
84
+ ```ruby
85
+ # frozen_string_literal: true
86
+
87
+ def app
88
+ ->(req) { req.respond('Hello, world!', 'Content-Type' => 'text/plain') }
89
+ end
90
+ ```
91
+
92
+ ## Setting server listening options
93
+
94
+ By default, Tipi serves plain HTTP on port 1234, but you can easily change that
95
+ by providing command line options as follows:
96
+
97
+ ### HTTP
98
+
99
+ To listen for plain HTTP, use the `-l`/`--listen` option and specify a port
100
+ number:
101
+
102
+ ```bash
103
+ $ bundle exec tipi -l9292 myapp.ru
104
+ ```
105
+
106
+ ### HTTPS
107
+
108
+ To listen for HTTPS connections, use the `-s`/`--secure` option and specify a
109
+ host name and a port:
110
+
111
+ ```bash
112
+ $ bundle exec tipi -sexample.com:9292 myapp.ru
113
+ ```
114
+
115
+ ### Full service listening
116
+
117
+ The Tipi full service listens for both HTTP and HTTPS and supports automatic
118
+ certificate provisioning. To use the full service, use the `-f`/`--full` option,
119
+ and specify the domain name, the HTTP port, and the HTTPS port, e.g.:
120
+
121
+ ```bash
122
+ $ bundle exec tipi -fmysite.org:10080:10443 myapp.ru
123
+
124
+ #If serving multiple domains, you can use * as place holder
125
+ $ bundle exec tipi -f*:10080:10443 myapp.ru
126
+ ```
127
+
128
+ If `localhost` is specified as the domain, Tipi will automatically generate a
129
+ localhost certificate.
130
+
131
+ ## Concurrency settings
132
+
133
+ By default, the `tipi` command starts a single controller and uses
134
+ [Polyphony](https://github.com/digital-fabric/polyphony) to run each connection
135
+ on its own fiber. This means that you will have a single process running on a
136
+ single thread (on a single CPU core). In order to parallelize your app and
137
+ employ multiple CPU cores, you can tell Tipi to fork multiple worker processes
138
+ to run your app. The number of workers is controlled using the `-w`/`--workers`
139
+ option:
140
+
141
+ ```bash
142
+ # fork 4 worker processes
143
+ $ bundle exec tipi -w4 myapp.ru
144
+ ```
145
+
146
+ You can also set Tipi to spawn multiple threads in each worker when in
147
+ compatibility mode (see below.)
148
+
149
+ ## Compatibility mode
150
+
151
+ > Note: compatibility mode is still being developed, and currently only supports
152
+ > HTTP/1 connections.
153
+
154
+ In some apps, using Polyphony is not possible, due to incompatibilities between
155
+ it and other third-party dependencies. In order to be able to run these apps,
156
+ Tipi provides a compatibility mode that does not use Polyphony for concurrency,
157
+ but instead uses a thread-per-connection concurrency model. You can also fork
158
+ multiple workers, each running multiple threads, if so desired. Note that the
159
+ concurrency level is the maximum number workers multiplied by the number of
160
+ threads per worker:
161
+
162
+ ```
163
+ concurrency = worker_count * threads_per_worker
164
+ ```
165
+
166
+ To run Tipi in compatibility mode, use the `-c`/`--compatibility` option, e.g.:
167
+
168
+ ```bash
169
+ # 4 workers * 8 threads = 32 max concurrency
170
+ $ bundle exec tipi -c -w4 -t8 myapp.ru
171
+ ```
172
+
173
+ ## Worker process supervision
174
+
175
+ Tipi employs a supervisor-controller-worker process supervision model, which
176
+ minimizes the memory consumption of forked workers, and which facilitates
177
+ graceful reloading after updating the application code.
178
+
179
+ This supervision model is made of three levels:
180
+
181
+ - Supervisor - Starts and stops the controller process
182
+ - Controller - loads the application code and forks workers
183
+ - Worker - listens for connections, handles incoming requests
184
+
185
+ (If the worker count is 1, the Controller and Worker roles are merged into a
186
+ single process.)
187
+
188
+ This model allows Tipi to fork workers after loading the app code, and use a
189
+ much simpler way to perform graceful restarts:
190
+
191
+ - The supervisor starts a new controller process (which may fork one or more
192
+ worker processes).
193
+ - Sleep for a certain amount of time (currently 1 second.)
194
+ - Stop the old controller process.
195
+ - Each worker process is gracefully stopped and allowed to finish all pending
196
+ requests, then shutdown all open connections.
197
+
198
+ ## Performing a graceful restart
199
+
200
+ A graceful restart performed by sending `SIGUSR2` to the supervisor process.
201
+
27
202
  ## Documentation
28
203
 
29
- Documentation for Tipi is coming soon...
204
+ Documentation for Tipi's API is coming soon...
205
+
data/Rakefile CHANGED
@@ -3,13 +3,7 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/clean"
5
5
 
6
- require "rake/extensiontask"
7
- Rake::ExtensionTask.new("tipi_ext") do |ext|
8
- ext.ext_dir = "ext/tipi"
9
- end
10
-
11
- task :recompile => [:clean, :compile]
12
- task :default => [:compile, :test]
6
+ task :default => [:test]
13
7
 
14
8
  task :test do
15
9
  exec 'ruby test/run.rb'
@@ -26,7 +26,7 @@ end
26
26
  def benchmark_other_http1_parser(iterations)
27
27
  STDOUT << "http_parser.rb: "
28
28
  require 'http_parser.rb'
29
-
29
+
30
30
  i, o = IO.pipe
31
31
  parser = Http::Parser.new
32
32
  done = false
data/bin/benchmark CHANGED
File without changes
data/bin/h1pd CHANGED
File without changes
data/bm.png ADDED
Binary file
data/df/agent.rb CHANGED
@@ -30,7 +30,7 @@ class SampleAgent < DigitalFabric::Agent
30
30
  do_some_activity
31
31
  req.send_chunk({ id: @id, time: Time.now.to_i }.to_json)
32
32
  end
33
-
33
+
34
34
  req.finish
35
35
  rescue Polyphony::Terminate
36
36
  req.respond(' * shutting down *') if Fiber.current.graceful_shutdown?
data/df/sample_agent.rb CHANGED
@@ -46,7 +46,7 @@ class SampleAgent < DigitalFabric::Agent
46
46
  true
47
47
  ))
48
48
  end
49
-
49
+
50
50
  end
51
51
 
52
52
  def ws_request(req)
@@ -82,7 +82,7 @@ class SampleAgent < DigitalFabric::Agent
82
82
  true
83
83
  ))
84
84
  end
85
-
85
+
86
86
  end
87
87
 
88
88
  agent = SampleAgent.new('127.0.0.1', 4411, { path: '/agent' })
data/df/server_utils.rb CHANGED
@@ -68,7 +68,7 @@ def listen_https
68
68
  cert = certificates.shift
69
69
  log "SSL Certificate expires: #{cert.not_after.inspect}"
70
70
  ctx.add_certificate(cert, private_key, certificates)
71
- ctx.ciphers = 'ECDH+aRSA'
71
+ # ctx.ciphers = 'ECDH+aRSA'
72
72
  ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
73
73
  ctx.min_version = OpenSSL::SSL::SSL3_VERSION
74
74
  ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
data/examples/hello.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ def app
4
+ ->(req) { req.respond('Hello, world!', 'Content-Type' => 'text/plain') }
5
+ end
@@ -12,7 +12,7 @@ const server = http.createServer((req, res) => {
12
12
  // request_url: req.url,
13
13
  // headers: req.headers
14
14
  // };
15
-
15
+
16
16
  // res.writeHead(200, { 'Content-Type': 'application/json' });
17
17
  // res.end(JSON.stringify(requestCopy));
18
18
 
@@ -24,4 +24,4 @@ puts "pid: #{Process.pid}"
24
24
  puts 'Send HUP to stop gracefully'
25
25
  puts 'Listening on port 1234...'
26
26
 
27
- suspend
27
+ suspend
@@ -10,27 +10,50 @@ authority = Localhost::Authority.fetch
10
10
  opts = {
11
11
  reuse_addr: true,
12
12
  dont_linger: true,
13
- secure_context: authority.server_context
14
13
  }
15
14
 
16
15
  puts "pid: #{Process.pid}"
17
16
  puts 'Listening on port 1234...'
18
- Tipi.serve('0.0.0.0', 1234, opts) do |req|
19
- p path: req.path
20
- if req.path == '/stream'
21
- req.send_headers('Foo' => 'Bar')
22
- sleep 0.5
23
- req.send_chunk("foo\n")
24
- sleep 0.5
25
- req.send_chunk("bar\n", done: true)
26
- elsif req.path == '/upload'
27
- body = req.read
28
- req.respond("Body: #{body.inspect} (#{body.bytesize} bytes)")
29
- else
30
- req.respond("Hello world!\n")
17
+
18
+ ctx = authority.server_context
19
+ server = Polyphony::Net.tcp_listen('0.0.0.0', 1234, opts)
20
+ loop do
21
+ socket = server.accept
22
+ client = OpenSSL::SSL::SSLSocket.new(socket, ctx)
23
+ client.sync_close = true
24
+ spin do
25
+ state = {}
26
+ accept_thread = Thread.new do
27
+ puts "call client accept"
28
+ client.accept
29
+ state[:result] = :ok
30
+ rescue Exception => e
31
+ puts error: e
32
+ state[:result] = e
33
+ end
34
+ "wait for accept thread"
35
+ accept_thread.join
36
+ "accept thread done"
37
+ if state[:result].is_a?(Exception)
38
+ puts "Exception in SSL handshake: #{state[:result].inspect}"
39
+ next
40
+ end
41
+ Tipi.client_loop(client, opts) do |req|
42
+ p path: req.path
43
+ if req.path == '/stream'
44
+ req.send_headers('Foo' => 'Bar')
45
+ sleep 0.5
46
+ req.send_chunk("foo\n")
47
+ sleep 0.5
48
+ req.send_chunk("bar\n", done: true)
49
+ elsif req.path == '/upload'
50
+ body = req.read
51
+ req.respond("Body: #{body.inspect} (#{body.bytesize} bytes)")
52
+ else
53
+ req.respond("Hello world!\n")
54
+ end
55
+ end
56
+ ensure
57
+ client ? client.close : socket.close
31
58
  end
32
- # req.send_headers
33
- # req.send_chunk("Method: #{req.method}\n")
34
- # req.send_chunk("Path: #{req.path}\n")
35
- # req.send_chunk("Query: #{req.query.inspect}\n", done: true)
36
59
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'tipi'
5
+
6
+ app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
7
+ unless File.file?(app_path)
8
+ STDERR.puts "Please provide rack config file (there are some in the examples directory.)"
9
+ exit!
10
+ end
11
+
12
+ app = Tipi::RackAdapter.load(app_path)
13
+ opts = { reuse_addr: true, dont_linger: true }
14
+
15
+ server = Tipi.listen('0.0.0.0', 1234, opts)
16
+ puts 'listening on port 1234'
17
+
18
+ child_pids = []
19
+ 4.times do
20
+ child_pids << Polyphony.fork do
21
+ puts "forked pid: #{Process.pid}"
22
+ server.each(&app)
23
+ end
24
+ end
25
+
26
+ child_pids.each { |pid| Thread.current.backend.waitpid(pid) }
@@ -25,4 +25,4 @@ child_pids = []
25
25
  end
26
26
  end
27
27
 
28
- child_pids.each { |pid| Thread.current.backend.waitpid(pid) }
28
+ child_pids.each { |pid| Thread.current.backend.waitpid(pid) }
@@ -26,7 +26,7 @@ class WebsocketClient
26
26
  def receive
27
27
  parsed = @reader.next
28
28
  return parsed if parsed
29
-
29
+
30
30
  @socket.read_loop do |data|
31
31
  @reader << data
32
32
  parsed = @reader.next