tipi 0.41 → 0.46

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +3 -1
  4. data/.gitignore +3 -1
  5. data/CHANGELOG.md +34 -0
  6. data/Gemfile +7 -1
  7. data/Gemfile.lock +53 -33
  8. data/README.md +184 -8
  9. data/Rakefile +1 -7
  10. data/benchmarks/bm_http1_parser.rb +85 -0
  11. data/bin/benchmark +37 -0
  12. data/bin/h1pd +6 -0
  13. data/bin/tipi +3 -21
  14. data/bm.png +0 -0
  15. data/df/agent.rb +1 -1
  16. data/df/sample_agent.rb +2 -2
  17. data/df/server.rb +3 -1
  18. data/df/server_utils.rb +48 -46
  19. data/examples/full_service.rb +13 -0
  20. data/examples/hello.rb +5 -0
  21. data/examples/hello.ru +3 -3
  22. data/examples/http1_parser.rb +10 -8
  23. data/examples/http_server.js +1 -1
  24. data/examples/http_server.rb +4 -1
  25. data/examples/http_server_graceful.rb +1 -1
  26. data/examples/https_server.rb +41 -15
  27. data/examples/rack_server_forked.rb +26 -0
  28. data/examples/rack_server_https_forked.rb +1 -1
  29. data/examples/servername_cb.rb +37 -0
  30. data/examples/websocket_demo.rb +1 -1
  31. data/lib/tipi/acme.rb +320 -0
  32. data/lib/tipi/cli.rb +93 -0
  33. data/lib/tipi/config_dsl.rb +13 -13
  34. data/lib/tipi/configuration.rb +2 -2
  35. data/lib/tipi/controller/bare_polyphony.rb +0 -0
  36. data/lib/tipi/controller/bare_stock.rb +10 -0
  37. data/lib/tipi/controller/extensions.rb +37 -0
  38. data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
  39. data/lib/tipi/controller/web_polyphony.rb +353 -0
  40. data/lib/tipi/controller/web_stock.rb +635 -0
  41. data/lib/tipi/controller.rb +12 -0
  42. data/lib/tipi/digital_fabric/agent.rb +5 -5
  43. data/lib/tipi/digital_fabric/agent_proxy.rb +15 -8
  44. data/lib/tipi/digital_fabric/executive.rb +7 -3
  45. data/lib/tipi/digital_fabric/protocol.rb +3 -3
  46. data/lib/tipi/digital_fabric/request_adapter.rb +0 -4
  47. data/lib/tipi/digital_fabric/service.rb +17 -18
  48. data/lib/tipi/handler.rb +2 -2
  49. data/lib/tipi/http1_adapter.rb +85 -124
  50. data/lib/tipi/http2_adapter.rb +29 -16
  51. data/lib/tipi/http2_stream.rb +52 -57
  52. data/lib/tipi/rack_adapter.rb +2 -2
  53. data/lib/tipi/response_extensions.rb +1 -1
  54. data/lib/tipi/supervisor.rb +75 -0
  55. data/lib/tipi/version.rb +1 -1
  56. data/lib/tipi/websocket.rb +3 -3
  57. data/lib/tipi.rb +9 -7
  58. data/test/coverage.rb +2 -2
  59. data/test/helper.rb +60 -12
  60. data/test/test_http_server.rb +14 -41
  61. data/test/test_request.rb +2 -29
  62. data/tipi.gemspec +10 -10
  63. metadata +80 -54
  64. data/examples/automatic_certificate.rb +0 -193
  65. data/ext/tipi/extconf.rb +0 -12
  66. data/ext/tipi/http1_parser.c +0 -534
  67. data/ext/tipi/http1_parser.h +0 -18
  68. data/ext/tipi/tipi_ext.c +0 -5
  69. data/lib/tipi/http1_adapter_new.rb +0 -293
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a83c181c05d8d8c8de50f236feab65b800b1bd266c2f0a4d4696d4853ac39fd
4
- data.tar.gz: d9a84fc9f72c3a5565f587e35fb4ed206c83c863fbb0b4b470449919a687463b
3
+ metadata.gz: e174ac4f391f2a5d8a6bc023c8dbe800cb28c7c6ab6ce0f5b30a5a741adfbba3
4
+ data.tar.gz: 20f4e08c4295e43b4e530005894573170d11488e33ffdb7bd7ac863ea9b24d10
5
5
  SHA512:
6
- metadata.gz: e9c13f387c3431888d9d43c88d5bd00831c4e2e8976bf8ae4e1acc479b8d50fa888cd58af0fc433da2dab8e3ea09b99ca0adbdcc61abc71457a805c26aa03687
7
- data.tar.gz: df01a54c7c46928c67da321cfc830d8f33e8af7fa8197cd5930f3de73838efd4849d51b10af35b1a5297f7f484b296ff2ed6146dbb8d66476bf0b714f1dbfd68
6
+ metadata.gz: 44824970035774527e456959c6e8249bd697696ef942e2229b4fc9faa192ab2433c7e78f714af3ac98a4d21fc1e764de718ecd41c9e1374a9d06ab090230ab2f
7
+ data.tar.gz: 46549f1ae238fc25fe878bb59258da44757ed05e84177f113a40f00d962f43947d9bbd44f78f1e97be386001d34fadfba8679a5d0a6c647f4dca618769a8116d
@@ -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}}
@@ -23,5 +23,7 @@ jobs:
23
23
  run: |
24
24
  gem install bundler
25
25
  POLYPHONY_USE_LIBEV=1 bundle install
26
+ - name: Show Linux kernel version
27
+ run: uname -r
26
28
  - name: Run tests
27
29
  run: bundle exec rake test
data/.gitignore CHANGED
@@ -55,5 +55,7 @@ build-iPhoneSimulator/
55
55
  # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
56
  # .rubocop-https?--*
57
57
  log
58
+ log.*
58
59
 
59
- lib/tipi_ext*
60
+ lib/tipi_ext*
61
+ examples/certificate_store.db
data/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ ## 0.46 2022-02-01
2
+
3
+ - Allow setting valid hosts
4
+ - Change interface of Qeweney apps to use #run (#15)
5
+ - Close server listener before terminating connections
6
+
7
+ ## 0.45 2021-10-25
8
+
9
+ - Remove `http_parser.rb` dependency (#14) - thanks @SwagDevOps
10
+ - Use `argv` argument in `Tipi.opts_from_argv` (#13) - thanks @SwagDevOps
11
+ - Ignore `ArgumentError` in `#parse_headers`
12
+
13
+ ## 0.44 2021-09-29
14
+
15
+ - Implement compatibility mode for HTTP/1 (WIP)
16
+ - Add option parsing for CLI tool
17
+ - Implement supervisor-controller-worker model in CLI tool
18
+
19
+ ## 0.43 2021-08-20
20
+
21
+ - Extract HTTP/1 parser into a separate gem:
22
+ [H1P](https://github.com/digital-fabric/h1p)
23
+
24
+ ## 0.42 2021-08-16
25
+
26
+ - HTTP/1 parser: disable UTF-8 parsing for all but header values
27
+ - Add support for parsing HTTP/1 from callable source
28
+ - Introduce full_service API for automatic HTTPS
29
+ - Introduce automatic SSL certificate provisioning
30
+ - Improve handling of exceptions
31
+ - Various fixes to DF service and agent pxoy
32
+ - Fix upgrading to HTTP2 with a request body
33
+ - Switch to new HTTP/1 parser
34
+
1
35
  ## 0.41 2021-07-26
2
36
 
3
37
  - Fix Rack adapter (#11)
data/Gemfile CHANGED
@@ -1,3 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gemspec
3
+ gemspec
4
+
5
+ # for local development
6
+ %w{polyphony ever qeweney h1p}.each do |dep|
7
+ dir = "../#{dep}"
8
+ gem(dep, path: dir) if File.directory?(dir)
9
+ end
data/Gemfile.lock CHANGED
@@ -1,64 +1,82 @@
1
+ PATH
2
+ remote: ../ever
3
+ specs:
4
+ ever (0.1)
5
+
6
+ PATH
7
+ remote: ../h1p
8
+ specs:
9
+ h1p (0.2)
10
+
11
+ PATH
12
+ remote: ../polyphony
13
+ specs:
14
+ polyphony (0.73.1)
15
+
16
+ PATH
17
+ remote: ../qeweney
18
+ specs:
19
+ qeweney (0.16)
20
+ escape_utils (~> 1.2.1)
21
+
1
22
  PATH
2
23
  remote: .
3
24
  specs:
4
- tipi (0.41)
25
+ tipi (0.46)
5
26
  acme-client (~> 2.0.8)
6
- http-2 (~> 0.10.0)
7
- http_parser.rb (~> 0.6.0)
27
+ ever (~> 0.1)
28
+ extralite (~> 1.2)
29
+ h1p (~> 0.2)
30
+ http-2 (~> 0.11)
31
+ localhost (~> 1.1.4)
8
32
  msgpack (~> 1.4.2)
9
- polyphony (~> 0.64)
10
- qeweney (~> 0.11)
33
+ polyphony (~> 0.71)
34
+ qeweney (~> 0.14)
11
35
  rack (>= 2.0.8, < 2.3.0)
12
36
  websocket (~> 1.2.8)
13
37
 
14
38
  GEM
15
39
  remote: https://rubygems.org/
16
40
  specs:
17
- acme-client (2.0.8)
41
+ acme-client (2.0.9)
18
42
  faraday (>= 0.17, < 2.0.0)
19
- ansi (1.5.0)
20
- builder (3.2.4)
21
43
  cuba (3.9.3)
22
44
  rack (>= 1.6.0)
23
45
  docile (1.4.0)
24
46
  escape_utils (1.2.1)
25
- faraday (1.5.1)
47
+ extralite (1.11)
48
+ faraday (1.9.3)
26
49
  faraday-em_http (~> 1.0)
27
50
  faraday-em_synchrony (~> 1.0)
28
51
  faraday-excon (~> 1.1)
29
- faraday-httpclient (~> 1.0.1)
52
+ faraday-httpclient (~> 1.0)
53
+ faraday-multipart (~> 1.0)
30
54
  faraday-net_http (~> 1.0)
31
- faraday-net_http_persistent (~> 1.1)
55
+ faraday-net_http_persistent (~> 1.0)
32
56
  faraday-patron (~> 1.0)
33
- multipart-post (>= 1.2, < 3)
57
+ faraday-rack (~> 1.0)
58
+ faraday-retry (~> 1.0)
34
59
  ruby2_keywords (>= 0.0.4)
35
60
  faraday-em_http (1.0.0)
36
61
  faraday-em_synchrony (1.0.0)
37
62
  faraday-excon (1.1.0)
38
63
  faraday-httpclient (1.0.1)
64
+ faraday-multipart (1.0.3)
65
+ multipart-post (>= 1.2, < 3)
39
66
  faraday-net_http (1.0.1)
40
67
  faraday-net_http_persistent (1.2.0)
41
68
  faraday-patron (1.0.0)
42
- http-2 (0.10.2)
43
- http_parser.rb (0.6.0)
69
+ faraday-rack (1.0.0)
70
+ faraday-retry (1.0.3)
71
+ http-2 (0.11.0)
44
72
  json (2.5.1)
45
- localhost (1.1.8)
73
+ localhost (1.1.9)
74
+ memory_profiler (1.0.0)
46
75
  minitest (5.11.3)
47
- minitest-reporters (1.4.3)
48
- ansi
49
- builder
50
- minitest (>= 5.0)
51
- ruby-progressbar
52
- msgpack (1.4.2)
76
+ msgpack (1.4.4)
53
77
  multipart-post (2.1.1)
54
- polyphony (0.64)
55
- qeweney (0.11)
56
- escape_utils (~> 1.2.1)
57
78
  rack (2.2.3)
58
- rake (12.3.3)
59
- rake-compiler (1.1.1)
60
- rake
61
- ruby-progressbar (1.11.0)
79
+ rake (13.0.6)
62
80
  ruby2_keywords (0.0.5)
63
81
  simplecov (0.17.1)
64
82
  docile (~> 1.1)
@@ -72,13 +90,15 @@ PLATFORMS
72
90
 
73
91
  DEPENDENCIES
74
92
  cuba (~> 3.9.3)
75
- localhost (~> 1.1.4)
93
+ ever!
94
+ h1p!
95
+ memory_profiler (~> 1.0.0)
76
96
  minitest (~> 5.11.3)
77
- minitest-reporters (~> 1.4.2)
78
- rake (~> 12.3.3)
79
- rake-compiler (= 1.1.1)
97
+ polyphony!
98
+ qeweney!
99
+ rake (~> 13.0.6)
80
100
  simplecov (~> 0.17.1)
81
101
  tipi!
82
102
 
83
103
  BUNDLED WITH
84
- 2.1.4
104
+ 2.3.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'
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ HTTP_REQUEST = "GET /foo HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"
6
+
7
+ def measure_time_and_allocs
8
+ 4.times { GC.start }
9
+ GC.disable
10
+
11
+ t0 = Time.now
12
+ a0 = object_count
13
+ yield
14
+ t1 = Time.now
15
+ a1 = object_count
16
+ [t1 - t0, a1 - a0]
17
+ ensure
18
+ GC.enable
19
+ end
20
+
21
+ def object_count
22
+ count = ObjectSpace.count_objects
23
+ count[:TOTAL] - count[:FREE]
24
+ end
25
+
26
+ def benchmark_other_http1_parser(iterations)
27
+ STDOUT << "http_parser.rb: "
28
+ require 'http_parser.rb'
29
+
30
+ i, o = IO.pipe
31
+ parser = Http::Parser.new
32
+ done = false
33
+ headers = nil
34
+ parser.on_headers_complete = proc do |h|
35
+ headers = h
36
+ headers[':method'] = parser.http_method
37
+ headers[':path'] = parser.request_url
38
+ end
39
+ parser.on_message_complete = proc { done = true }
40
+
41
+ elapsed, allocated = measure_time_and_allocs do
42
+ iterations.times do
43
+ o << HTTP_REQUEST
44
+ done = false
45
+ while !done
46
+ msg = i.readpartial(4096)
47
+ parser << msg
48
+ end
49
+ end
50
+ end
51
+ puts(format('elapsed: %f, allocated: %d (%f/req), rate: %f ips', elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
52
+ end
53
+
54
+ def benchmark_tipi_http1_parser(iterations)
55
+ STDOUT << "tipi parser: "
56
+ require_relative '../lib/tipi_ext'
57
+ i, o = IO.pipe
58
+ reader = proc { |len| i.readpartial(len) }
59
+ parser = Tipi::HTTP1Parser.new(reader)
60
+
61
+ elapsed, allocated = measure_time_and_allocs do
62
+ iterations.times do
63
+ o << HTTP_REQUEST
64
+ headers = parser.parse_headers
65
+ end
66
+ end
67
+ puts(format('elapsed: %f, allocated: %d (%f/req), rate: %f ips', elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
68
+ end
69
+
70
+ def fork_benchmark(method, iterations)
71
+ pid = fork do
72
+ send(method, iterations)
73
+ rescue Exception => e
74
+ p e
75
+ p e.backtrace
76
+ exit!
77
+ end
78
+ Process.wait(pid)
79
+ end
80
+
81
+ x = 500000
82
+ # fork_benchmark(:benchmark_other_http1_parser, x)
83
+ # fork_benchmark(:benchmark_tipi_http1_parser, x)
84
+
85
+ benchmark_tipi_http1_parser(x)
data/bin/benchmark ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ def parse_latency(latency)
7
+ m = latency.match(/^([\d\.]+)(us|ms|s)$/)
8
+ return nil unless m
9
+
10
+ value = m[1].to_f
11
+ case m[2]
12
+ when 's' then value
13
+ when 'ms' then value / 1000
14
+ when 'us' then value / 1000000
15
+ end
16
+ end
17
+
18
+ def parse_wrk_results(results)
19
+ lines = results.lines
20
+ latencies = lines[3].strip.split(/\s+/)
21
+ throughput = lines[6].strip.split(/\s+/)
22
+
23
+ {
24
+ latency_avg: parse_latency(latencies[1]),
25
+ latency_max: parse_latency(latencies[3]),
26
+ rate: throughput[1].to_f
27
+ }
28
+ end
29
+
30
+ def run_wrk(duration: 10, threads: 2, connections: 10, url: )
31
+ `wrk -d#{duration} -t#{threads} -c#{connections} #{url}`
32
+ end
33
+
34
+ [8, 64, 256, 512].each do |c|
35
+ puts "connections: #{c}"
36
+ p parse_wrk_results(run_wrk(duration: 10, threads: 4, connections: c, url: "http://localhost:10080/"))
37
+ end
data/bin/h1pd ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+ rake compile
5
+ ruby test/test_http1_parser.rb
6
+ ruby benchmarks/bm_http1_parser.rb
data/bin/tipi CHANGED
@@ -1,26 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
5
- require File.expand_path('../lib/tipi/configuration', __dir__)
4
+ require 'tipi/cli'
6
5
 
7
- config = {}
8
- #config[:forked] = 4
6
+ trap('SIGINT') { exit }
9
7
 
10
- puts DATA.read
11
- puts
12
-
13
- configuration_manager = spin { Tipi::Configuration.supervise_config }
14
-
15
- configuration_manager << config
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
-
8
+ Tipi::CLI.start
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.rb CHANGED
@@ -8,6 +8,8 @@ listeners = [
8
8
  listen_unix
9
9
  ]
10
10
 
11
+ spin_loop(interval: 60) { GC.compact } if GC.respond_to?(:compact)
12
+
11
13
  begin
12
14
  log('Starting DF server')
13
15
  Fiber.await(*listeners)
@@ -17,7 +19,7 @@ rescue Interrupt
17
19
  rescue SystemExit
18
20
  # ignore
19
21
  rescue Exception => e
20
- log("Uncaught exception", error: e, backtrace: e.backtrace)
22
+ log("Uncaught exception", error: e, source: e.source_fiber, raising: e.raising_fiber, backtrace: e.backtrace)
21
23
  ensure
22
24
  log('DF server stopped')
23
25
  end