tipi 0.43 → 0.45

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 (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