tipi 0.42 → 0.47

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) 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 +27 -0
  5. data/Gemfile +3 -1
  6. data/Gemfile.lock +35 -29
  7. data/README.md +184 -8
  8. data/Rakefile +1 -7
  9. data/benchmarks/bm_http1_parser.rb +45 -21
  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.rb +2 -0
  16. data/df/server_utils.rb +12 -15
  17. data/examples/hello.rb +5 -0
  18. data/examples/hello.ru +3 -3
  19. data/examples/http_server.js +1 -1
  20. data/examples/http_server_graceful.rb +1 -1
  21. data/examples/https_server.rb +41 -18
  22. data/examples/rack_server_forked.rb +26 -0
  23. data/examples/rack_server_https_forked.rb +1 -1
  24. data/examples/websocket_demo.rb +1 -1
  25. data/lib/tipi/acme.rb +51 -39
  26. data/lib/tipi/cli.rb +79 -16
  27. data/lib/tipi/config_dsl.rb +13 -13
  28. data/lib/tipi/configuration.rb +2 -2
  29. data/lib/tipi/controller/bare_polyphony.rb +0 -0
  30. data/lib/tipi/controller/bare_stock.rb +10 -0
  31. data/lib/tipi/controller/extensions.rb +37 -0
  32. data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
  33. data/lib/tipi/controller/web_polyphony.rb +353 -0
  34. data/lib/tipi/controller/web_stock.rb +635 -0
  35. data/lib/tipi/controller.rb +12 -0
  36. data/lib/tipi/digital_fabric/agent.rb +3 -3
  37. data/lib/tipi/digital_fabric/agent_proxy.rb +11 -5
  38. data/lib/tipi/digital_fabric/executive.rb +1 -1
  39. data/lib/tipi/digital_fabric/protocol.rb +1 -1
  40. data/lib/tipi/digital_fabric/service.rb +12 -8
  41. data/lib/tipi/handler.rb +2 -2
  42. data/lib/tipi/http1_adapter.rb +36 -30
  43. data/lib/tipi/http2_adapter.rb +10 -10
  44. data/lib/tipi/http2_stream.rb +14 -15
  45. data/lib/tipi/rack_adapter.rb +2 -2
  46. data/lib/tipi/response_extensions.rb +1 -1
  47. data/lib/tipi/supervisor.rb +75 -0
  48. data/lib/tipi/version.rb +1 -1
  49. data/lib/tipi/websocket.rb +3 -3
  50. data/lib/tipi.rb +4 -83
  51. data/test/coverage.rb +2 -2
  52. data/test/helper.rb +0 -1
  53. data/test/test_http_server.rb +14 -14
  54. data/test/test_request.rb +1 -1
  55. data/tipi.gemspec +6 -7
  56. metadata +58 -53
  57. data/ext/tipi/extconf.rb +0 -13
  58. data/ext/tipi/http1_parser.c +0 -823
  59. data/ext/tipi/http1_parser.h +0 -18
  60. data/ext/tipi/tipi_ext.c +0 -5
  61. data/security/http1.rb +0 -12
  62. data/test/test_http1_parser.rb +0 -586
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82155f4b86223d3fb32dc8c314abfeb75fda951747e63832ed5c15bc3b1f43cc
4
- data.tar.gz: 721b98fb8d330d1bc38f8f236f82ddd909235d31f5875c1b4f89895e4e3926da
3
+ metadata.gz: f49829928be9cc9f944b9749c4092da37f99f42c34b9b07e2e16bceccf0f6e02
4
+ data.tar.gz: fcdc73b07ec3ad3ad6bb06832b135ca8179c3d1d2939d8065d5b10217edab55c
5
5
  SHA512:
6
- metadata.gz: 10389779975234d90c968626b4f9d6b168491f9a32ecf1be71683c37cf11f58f1bac52a36478691e72328236a16d309a8462104bda556772e25cebe52b2b0d53
7
- data.tar.gz: 99db2c15278cdfc286ffad05f266f5d62eec6a3bb1363f8dfa4c302549a308a691496ee60ec085ede59e3b594533e8e196bbca144878e5a6dccf2ecfc962151c
6
+ metadata.gz: cfba22e88c1bff8f23c828e86274148f5ef895262f2258afb1c30743c67b4e7ff0493b40334dddccc20c418d4dbb3462e68e01a397030681adb4bd1c130d3ab2
7
+ data.tar.gz: 0765f9c44a88f00d5e45f0d168ade5ae7649682bf61134142ce531603857cbb9c1180dde0f101b69a07d4e57688a47522ab86cb87178074fbcf7d84d949ee40f
@@ -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,30 @@
1
+ ## 0.47 2202-02-03
2
+
3
+ - Update H1P dependency
4
+
5
+ ## 0.46 2022-02-01
6
+
7
+ - Allow setting valid hosts
8
+ - Change interface of Qeweney apps to use #run (#15)
9
+ - Close server listener before terminating connections
10
+
11
+ ## 0.45 2021-10-25
12
+
13
+ - Remove `http_parser.rb` dependency (#14) - thanks @SwagDevOps
14
+ - Use `argv` argument in `Tipi.opts_from_argv` (#13) - thanks @SwagDevOps
15
+ - Ignore `ArgumentError` in `#parse_headers`
16
+
17
+ ## 0.44 2021-09-29
18
+
19
+ - Implement compatibility mode for HTTP/1 (WIP)
20
+ - Add option parsing for CLI tool
21
+ - Implement supervisor-controller-worker model in CLI tool
22
+
23
+ ## 0.43 2021-08-20
24
+
25
+ - Extract HTTP/1 parser into a separate gem:
26
+ [H1P](https://github.com/digital-fabric/h1p)
27
+
1
28
  ## 0.42 2021-08-16
2
29
 
3
30
  - HTTP/1 parser: disable UTF-8 parsing for all but header values
data/Gemfile CHANGED
@@ -1,7 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
- %w{polyphony qeweney}.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,24 +1,36 @@
1
+ PATH
2
+ remote: ../ever
3
+ specs:
4
+ ever (0.1)
5
+
6
+ PATH
7
+ remote: ../h1p
8
+ specs:
9
+ h1p (0.3)
10
+
1
11
  PATH
2
12
  remote: ../polyphony
3
13
  specs:
4
- polyphony (0.69)
14
+ polyphony (0.74)
5
15
 
6
16
  PATH
7
17
  remote: ../qeweney
8
18
  specs:
9
- qeweney (0.14)
19
+ qeweney (0.16)
10
20
  escape_utils (~> 1.2.1)
11
21
 
12
22
  PATH
13
23
  remote: .
14
24
  specs:
15
- tipi (0.42)
25
+ tipi (0.47)
16
26
  acme-client (~> 2.0.8)
27
+ ever (~> 0.1)
17
28
  extralite (~> 1.2)
29
+ h1p (~> 0.3)
18
30
  http-2 (~> 0.11)
19
31
  localhost (~> 1.1.4)
20
32
  msgpack (~> 1.4.2)
21
- polyphony (~> 0.69)
33
+ polyphony (~> 0.71)
22
34
  qeweney (~> 0.14)
23
35
  rack (>= 2.0.8, < 2.3.0)
24
36
  websocket (~> 1.2.8)
@@ -26,51 +38,45 @@ PATH
26
38
  GEM
27
39
  remote: https://rubygems.org/
28
40
  specs:
29
- acme-client (2.0.8)
41
+ acme-client (2.0.9)
30
42
  faraday (>= 0.17, < 2.0.0)
31
- ansi (1.5.0)
32
- builder (3.2.4)
33
43
  cuba (3.9.3)
34
44
  rack (>= 1.6.0)
35
45
  docile (1.4.0)
36
46
  escape_utils (1.2.1)
37
- extralite (1.2)
38
- faraday (1.7.0)
47
+ extralite (1.11)
48
+ faraday (1.9.3)
39
49
  faraday-em_http (~> 1.0)
40
50
  faraday-em_synchrony (~> 1.0)
41
51
  faraday-excon (~> 1.1)
42
- faraday-httpclient (~> 1.0.1)
52
+ faraday-httpclient (~> 1.0)
53
+ faraday-multipart (~> 1.0)
43
54
  faraday-net_http (~> 1.0)
44
- faraday-net_http_persistent (~> 1.1)
55
+ faraday-net_http_persistent (~> 1.0)
45
56
  faraday-patron (~> 1.0)
46
57
  faraday-rack (~> 1.0)
47
- multipart-post (>= 1.2, < 3)
58
+ faraday-retry (~> 1.0)
48
59
  ruby2_keywords (>= 0.0.4)
49
60
  faraday-em_http (1.0.0)
50
61
  faraday-em_synchrony (1.0.0)
51
62
  faraday-excon (1.1.0)
52
63
  faraday-httpclient (1.0.1)
64
+ faraday-multipart (1.0.3)
65
+ multipart-post (>= 1.2, < 3)
53
66
  faraday-net_http (1.0.1)
54
67
  faraday-net_http_persistent (1.2.0)
55
68
  faraday-patron (1.0.0)
56
69
  faraday-rack (1.0.0)
70
+ faraday-retry (1.0.3)
57
71
  http-2 (0.11.0)
58
- http_parser.rb (0.7.0)
59
72
  json (2.5.1)
60
- localhost (1.1.8)
73
+ localhost (1.1.9)
74
+ memory_profiler (1.0.0)
61
75
  minitest (5.11.3)
62
- minitest-reporters (1.4.3)
63
- ansi
64
- builder
65
- minitest (>= 5.0)
66
- ruby-progressbar
67
- msgpack (1.4.2)
76
+ msgpack (1.4.4)
68
77
  multipart-post (2.1.1)
69
78
  rack (2.2.3)
70
- rake (12.3.3)
71
- rake-compiler (1.1.1)
72
- rake
73
- ruby-progressbar (1.11.0)
79
+ rake (13.0.6)
74
80
  ruby2_keywords (0.0.5)
75
81
  simplecov (0.17.1)
76
82
  docile (~> 1.1)
@@ -84,15 +90,15 @@ PLATFORMS
84
90
 
85
91
  DEPENDENCIES
86
92
  cuba (~> 3.9.3)
87
- http_parser.rb (= 0.7.0)
93
+ ever!
94
+ h1p!
95
+ memory_profiler (~> 1.0.0)
88
96
  minitest (~> 5.11.3)
89
- minitest-reporters (~> 1.4.2)
90
97
  polyphony!
91
98
  qeweney!
92
- rake (~> 12.3.3)
93
- rake-compiler (= 1.1.1)
99
+ rake (~> 13.0.6)
94
100
  simplecov (~> 0.17.1)
95
101
  tipi!
96
102
 
97
103
  BUNDLED WITH
98
- 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'
@@ -4,10 +4,29 @@ require 'bundler/setup'
4
4
 
5
5
  HTTP_REQUEST = "GET /foo HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"
6
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
+
7
26
  def benchmark_other_http1_parser(iterations)
8
27
  STDOUT << "http_parser.rb: "
9
28
  require 'http_parser.rb'
10
-
29
+
11
30
  i, o = IO.pipe
12
31
  parser = Http::Parser.new
13
32
  done = false
@@ -16,21 +35,20 @@ def benchmark_other_http1_parser(iterations)
16
35
  headers = h
17
36
  headers[':method'] = parser.http_method
18
37
  headers[':path'] = parser.request_url
19
- headers[':protocol'] = parser.http_version
20
38
  end
21
39
  parser.on_message_complete = proc { done = true }
22
40
 
23
- t0 = Time.now
24
- iterations.times do
25
- o << HTTP_REQUEST
26
- done = false
27
- while !done
28
- msg = i.readpartial(4096)
29
- parser << msg
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
30
49
  end
31
50
  end
32
- t1 = Time.now
33
- puts "#{iterations / (t1 - t0)} ips"
51
+ puts(format('elapsed: %f, allocated: %d (%f/req), rate: %f ips', elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
34
52
  end
35
53
 
36
54
  def benchmark_tipi_http1_parser(iterations)
@@ -40,22 +58,28 @@ def benchmark_tipi_http1_parser(iterations)
40
58
  reader = proc { |len| i.readpartial(len) }
41
59
  parser = Tipi::HTTP1Parser.new(reader)
42
60
 
43
- t0 = Time.now
44
- iterations.times do
45
- o << HTTP_REQUEST
46
- headers = parser.parse_headers
61
+ elapsed, allocated = measure_time_and_allocs do
62
+ iterations.times do
63
+ o << HTTP_REQUEST
64
+ headers = parser.parse_headers
65
+ end
47
66
  end
48
- t1 = Time.now
49
- puts "#{iterations / (t1 - t0)} ips"
67
+ puts(format('elapsed: %f, allocated: %d (%f/req), rate: %f ips', elapsed, allocated, allocated.to_f / iterations, iterations / elapsed))
50
68
  end
51
69
 
52
70
  def fork_benchmark(method, iterations)
53
- pid = fork { send(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
54
78
  Process.wait(pid)
55
79
  end
56
80
 
57
81
  x = 500000
58
- fork_benchmark(:benchmark_other_http1_parser, x)
59
- fork_benchmark(:benchmark_tipi_http1_parser, x)
82
+ # fork_benchmark(:benchmark_other_http1_parser, x)
83
+ # fork_benchmark(:benchmark_tipi_http1_parser, x)
60
84
 
61
- # benchmark_tipi_http1_parser(x)
85
+ benchmark_tipi_http1_parser(x)
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.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)
data/df/server_utils.rb CHANGED
@@ -64,17 +64,14 @@ def listen_https
64
64
  c = IO.read('../../reality/ssl/cacert.pem')
65
65
  certificates = c.scan(CERTIFICATE_REGEXP).map { |p| OpenSSL::X509::Certificate.new(p.first) }
66
66
  ctx = OpenSSL::SSL::SSLContext.new
67
+ ctx.security_level = 0
67
68
  cert = certificates.shift
68
69
  log "SSL Certificate expires: #{cert.not_after.inspect}"
69
70
  ctx.add_certificate(cert, private_key, certificates)
70
- ctx.ciphers = 'ECDH+aRSA'
71
- ctx.send(
72
- :set_minmax_proto_version,
73
- OpenSSL::SSL::SSL3_VERSION,
74
- OpenSSL::SSL::TLS1_3_VERSION
75
- )
76
- # ctx.min_version = OpenSSL::SSL::SSL3_VERSION #OpenSSL::SSL::TLS1_VERSION
77
- # ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
71
+ # ctx.ciphers = 'ECDH+aRSA'
72
+ ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
73
+ ctx.min_version = OpenSSL::SSL::SSL3_VERSION
74
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
78
75
 
79
76
  # TODO: further limit ciphers
80
77
  # ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/environments/tls.rb
@@ -91,10 +88,10 @@ def listen_https
91
88
  server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
92
89
  id = 0
93
90
  loop do
94
- log('Before HTTPS server.accept')
95
- client = server.accept
96
- log('After HTTPS server.accept')
97
- log('Accept HTTPS client connection', client: client)
91
+ client = server.accept rescue nil
92
+ next unless client
93
+
94
+ # log('Accept HTTPS client connection', client: client)
98
95
  spin("https#{id += 1}") do
99
96
  @service.incr_connection_count
100
97
  Tipi.client_loop(client, opts) { |req| @service.http_request(req) }
@@ -104,12 +101,12 @@ def listen_https
104
101
  # log("Done with HTTP connection", client: client)
105
102
  @service.decr_connection_count
106
103
  end
107
- rescue OpenSSL::SSL::SSLError, SystemCallError, TypeError => e
108
- log('HTTPS accept error', error: e)
104
+ # rescue OpenSSL::SSL::SSLError, SystemCallError, TypeError => e
105
+ # log('HTTPS accept error', error: e)
109
106
  rescue Polyphony::BaseException
110
107
  raise
111
108
  rescue Exception => e
112
- log 'HTTPS accept (unknown) error', error: e, backtrace: e.backtrace
109
+ log 'HTTPS listener error: ', error: e, backtrace: e.backtrace
113
110
  end
114
111
  end
115
112
  end
data/examples/hello.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ run { |req|
4
+ req.respond('Hello, world!')
5
+ }
data/examples/hello.ru CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  run lambda { |env|
4
4
  [
5
- 200,
6
- {"Content-Type" => "text/plain"},
7
- ["Hello, world!"]
5
+ 200,
6
+ {"Content-Type" => "text/plain"},
7
+ ["Hello, world!"]
8
8
  ]
9
9
  }
@@ -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