tipi 0.40 → 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 (63) 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 +5 -1
  5. data/CHANGELOG.md +35 -0
  6. data/Gemfile +7 -1
  7. data/Gemfile.lock +55 -29
  8. data/README.md +184 -8
  9. data/Rakefile +1 -3
  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 +16 -102
  18. data/df/server_utils.rb +175 -0
  19. data/examples/full_service.rb +13 -0
  20. data/examples/hello.rb +5 -0
  21. data/examples/http1_parser.rb +55 -0
  22. data/examples/http_server.js +1 -1
  23. data/examples/http_server.rb +15 -3
  24. data/examples/http_server_graceful.rb +1 -1
  25. data/examples/http_server_static.rb +6 -18
  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 +315 -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/{e → lib/tipi/controller/bare_polyphony.rb} +0 -0
  36. data/lib/tipi/controller/bare_stock.rb +10 -0
  37. data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
  38. data/lib/tipi/controller/web_polyphony.rb +351 -0
  39. data/lib/tipi/controller/web_stock.rb +631 -0
  40. data/lib/tipi/controller.rb +12 -0
  41. data/lib/tipi/digital_fabric/agent.rb +10 -8
  42. data/lib/tipi/digital_fabric/agent_proxy.rb +26 -12
  43. data/lib/tipi/digital_fabric/executive.rb +7 -3
  44. data/lib/tipi/digital_fabric/protocol.rb +19 -4
  45. data/lib/tipi/digital_fabric/request_adapter.rb +0 -4
  46. data/lib/tipi/digital_fabric/service.rb +84 -56
  47. data/lib/tipi/handler.rb +2 -2
  48. data/lib/tipi/http1_adapter.rb +86 -125
  49. data/lib/tipi/http2_adapter.rb +29 -16
  50. data/lib/tipi/http2_stream.rb +52 -56
  51. data/lib/tipi/rack_adapter.rb +2 -53
  52. data/lib/tipi/response_extensions.rb +2 -2
  53. data/lib/tipi/supervisor.rb +75 -0
  54. data/lib/tipi/version.rb +1 -1
  55. data/lib/tipi/websocket.rb +3 -3
  56. data/lib/tipi.rb +8 -5
  57. data/test/coverage.rb +2 -2
  58. data/test/helper.rb +60 -12
  59. data/test/test_http_server.rb +14 -41
  60. data/test/test_request.rb +2 -29
  61. data/tipi.gemspec +12 -8
  62. metadata +88 -28
  63. data/examples/automatic_certificate.rb +0 -193
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tipi
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.40'
4
+ version: '0.45'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-24 00:00:00.000000000 Z
11
+ date: 2021-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: polyphony
@@ -16,56 +16,84 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.57.0
19
+ version: '0.71'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.57.0
26
+ version: '0.71'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ever
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: qeweney
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: 0.10.0
47
+ version: '0.14'
34
48
  type: :runtime
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: 0.10.0
54
+ version: '0.14'
41
55
  - !ruby/object:Gem::Dependency
42
- name: http_parser.rb
56
+ name: extralite
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: 0.6.0
61
+ version: '1.2'
48
62
  type: :runtime
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: 0.6.0
68
+ version: '1.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: h1p
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.2'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: http-2
57
85
  requirement: !ruby/object:Gem::Requirement
58
86
  requirements:
59
87
  - - "~>"
60
88
  - !ruby/object:Gem::Version
61
- version: 0.10.0
89
+ version: '0.11'
62
90
  type: :runtime
63
91
  prerelease: false
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
- version: 0.10.0
96
+ version: '0.11'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: rack
71
99
  requirement: !ruby/object:Gem::Requirement
@@ -114,6 +142,20 @@ dependencies:
114
142
  - - "~>"
115
143
  - !ruby/object:Gem::Version
116
144
  version: 2.0.8
145
+ - !ruby/object:Gem::Dependency
146
+ name: localhost
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: 1.1.4
152
+ type: :runtime
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: 1.1.4
117
159
  - !ruby/object:Gem::Dependency
118
160
  name: msgpack
119
161
  requirement: !ruby/object:Gem::Requirement
@@ -134,70 +176,70 @@ dependencies:
134
176
  requirements:
135
177
  - - "~>"
136
178
  - !ruby/object:Gem::Version
137
- version: 12.3.3
179
+ version: 13.0.6
138
180
  type: :development
139
181
  prerelease: false
140
182
  version_requirements: !ruby/object:Gem::Requirement
141
183
  requirements:
142
184
  - - "~>"
143
185
  - !ruby/object:Gem::Version
144
- version: 12.3.3
186
+ version: 13.0.6
145
187
  - !ruby/object:Gem::Dependency
146
- name: localhost
188
+ name: minitest
147
189
  requirement: !ruby/object:Gem::Requirement
148
190
  requirements:
149
191
  - - "~>"
150
192
  - !ruby/object:Gem::Version
151
- version: 1.1.4
193
+ version: 5.11.3
152
194
  type: :development
153
195
  prerelease: false
154
196
  version_requirements: !ruby/object:Gem::Requirement
155
197
  requirements:
156
198
  - - "~>"
157
199
  - !ruby/object:Gem::Version
158
- version: 1.1.4
200
+ version: 5.11.3
159
201
  - !ruby/object:Gem::Dependency
160
- name: minitest
202
+ name: simplecov
161
203
  requirement: !ruby/object:Gem::Requirement
162
204
  requirements:
163
205
  - - "~>"
164
206
  - !ruby/object:Gem::Version
165
- version: 5.11.3
207
+ version: 0.17.1
166
208
  type: :development
167
209
  prerelease: false
168
210
  version_requirements: !ruby/object:Gem::Requirement
169
211
  requirements:
170
212
  - - "~>"
171
213
  - !ruby/object:Gem::Version
172
- version: 5.11.3
214
+ version: 0.17.1
173
215
  - !ruby/object:Gem::Dependency
174
- name: minitest-reporters
216
+ name: memory_profiler
175
217
  requirement: !ruby/object:Gem::Requirement
176
218
  requirements:
177
219
  - - "~>"
178
220
  - !ruby/object:Gem::Version
179
- version: 1.4.2
221
+ version: 1.0.0
180
222
  type: :development
181
223
  prerelease: false
182
224
  version_requirements: !ruby/object:Gem::Requirement
183
225
  requirements:
184
226
  - - "~>"
185
227
  - !ruby/object:Gem::Version
186
- version: 1.4.2
228
+ version: 1.0.0
187
229
  - !ruby/object:Gem::Dependency
188
- name: simplecov
230
+ name: cuba
189
231
  requirement: !ruby/object:Gem::Requirement
190
232
  requirements:
191
233
  - - "~>"
192
234
  - !ruby/object:Gem::Version
193
- version: 0.17.1
235
+ version: 3.9.3
194
236
  type: :development
195
237
  prerelease: false
196
238
  version_requirements: !ruby/object:Gem::Requirement
197
239
  requirements:
198
240
  - - "~>"
199
241
  - !ruby/object:Gem::Version
200
- version: 0.17.1
242
+ version: 3.9.3
201
243
  description:
202
244
  email: sharon@noteflakes.com
203
245
  executables:
@@ -206,6 +248,7 @@ extensions: []
206
248
  extra_rdoc_files:
207
249
  - README.md
208
250
  files:
251
+ - ".github/FUNDING.yml"
209
252
  - ".github/workflows/test.yml"
210
253
  - ".gitignore"
211
254
  - CHANGELOG.md
@@ -215,7 +258,11 @@ files:
215
258
  - README.md
216
259
  - Rakefile
217
260
  - TODO.md
261
+ - benchmarks/bm_http1_parser.rb
262
+ - bin/benchmark
263
+ - bin/h1pd
218
264
  - bin/tipi
265
+ - bm.png
219
266
  - df/agent.rb
220
267
  - df/etc_benchmark.rb
221
268
  - df/multi_agent_supervisor.rb
@@ -223,16 +270,18 @@ files:
223
270
  - df/routing_benchmark.rb
224
271
  - df/sample_agent.rb
225
272
  - df/server.rb
273
+ - df/server_utils.rb
226
274
  - df/sse_page.html
227
275
  - df/stress.rb
228
276
  - df/ws_page.html
229
277
  - docs/README.md
230
278
  - docs/tipi-logo.png
231
- - e
232
- - examples/automatic_certificate.rb
233
279
  - examples/cuba.ru
280
+ - examples/full_service.rb
234
281
  - examples/hanami-api.ru
282
+ - examples/hello.rb
235
283
  - examples/hello.ru
284
+ - examples/http1_parser.rb
236
285
  - examples/http_request_ws_server.rb
237
286
  - examples/http_server.js
238
287
  - examples/http_server.rb
@@ -251,9 +300,11 @@ files:
251
300
  - examples/https_server_forked.rb
252
301
  - examples/https_wss_server.rb
253
302
  - examples/rack_server.rb
303
+ - examples/rack_server_forked.rb
254
304
  - examples/rack_server_https.rb
255
305
  - examples/rack_server_https_forked.rb
256
306
  - examples/routing_server.rb
307
+ - examples/servername_cb.rb
257
308
  - examples/websocket_client.rb
258
309
  - examples/websocket_demo.rb
259
310
  - examples/websocket_secure_server.rb
@@ -261,8 +312,16 @@ files:
261
312
  - examples/ws_page.html
262
313
  - examples/wss_page.html
263
314
  - lib/tipi.rb
315
+ - lib/tipi/acme.rb
316
+ - lib/tipi/cli.rb
264
317
  - lib/tipi/config_dsl.rb
265
318
  - lib/tipi/configuration.rb
319
+ - lib/tipi/controller.rb
320
+ - lib/tipi/controller/bare_polyphony.rb
321
+ - lib/tipi/controller/bare_stock.rb
322
+ - lib/tipi/controller/stock_http1_adapter.rb
323
+ - lib/tipi/controller/web_polyphony.rb
324
+ - lib/tipi/controller/web_stock.rb
266
325
  - lib/tipi/digital_fabric.rb
267
326
  - lib/tipi/digital_fabric/agent.rb
268
327
  - lib/tipi/digital_fabric/agent_proxy.rb
@@ -277,6 +336,7 @@ files:
277
336
  - lib/tipi/http2_stream.rb
278
337
  - lib/tipi/rack_adapter.rb
279
338
  - lib/tipi/response_extensions.rb
339
+ - lib/tipi/supervisor.rb
280
340
  - lib/tipi/version.rb
281
341
  - lib/tipi/websocket.rb
282
342
  - test/coverage.rb
@@ -310,7 +370,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
310
370
  - !ruby/object:Gem::Version
311
371
  version: '0'
312
372
  requirements: []
313
- rubygems_version: 3.1.4
373
+ rubygems_version: 3.1.2
314
374
  signing_key:
315
375
  specification_version: 4
316
376
  summary: Tipi - the All-in-one Web Server for Ruby Apps
@@ -1,193 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/setup'
4
- require 'tipi'
5
- require 'openssl'
6
- require 'acme-client'
7
-
8
- # ::Exception.__disable_sanitized_backtrace__ = true
9
-
10
- class CertificateManager
11
- def initialize(store:, challenge_handler:)
12
- @store = store
13
- @challenge_handler = challenge_handler
14
- @workers = {}
15
- @contexts = {}
16
- end
17
-
18
- def [](name)
19
- worker = worker_for_name(name)
20
- p worker: worker
21
-
22
- worker << Fiber.current
23
- # cancel_after(30) { receive }
24
- receive.tap { |ctx| p got_ctx: ctx }
25
- rescue Exception => e
26
- p e
27
- puts e.backtrace.join("\n")
28
- nil
29
- end
30
-
31
- def worker_for_name(name)
32
- @workers[name] ||= spin { worker_loop(name) }
33
- end
34
-
35
- def worker_loop(name)
36
- while (client = receive)
37
- puts "get request for #{name} from #{client.inspect}"
38
- ctx = get_context(name)
39
- client << ctx rescue nil
40
- end
41
- end
42
-
43
- def get_context(name)
44
- @contexts[name] ||= setup_context(name)
45
- end
46
-
47
- CERTIFICATE_REGEXP = /(-----BEGIN CERTIFICATE-----\n[^-]+-----END CERTIFICATE-----\n)/.freeze
48
-
49
- def setup_context(name)
50
- certificate = get_certificate(name)
51
- ctx = OpenSSL::SSL::SSLContext.new
52
- chain = certificate.scan(CERTIFICATE_REGEXP).map { |p| OpenSSL::X509::Certificate.new(p.first) }
53
- cert = chain.shift
54
- puts "Certificate expires: #{cert.not_after.inspect}"
55
- ctx.add_certificate(cert, private_key, chain)
56
- Polyphony::Net.setup_alpn(ctx, Tipi::ALPN_PROTOCOLS)
57
- ctx
58
- end
59
-
60
- def get_certificate(name)
61
- @store[name] ||= provision_certificate(name)
62
- end
63
-
64
- def private_key
65
- @private_key ||= OpenSSL::PKey::RSA.new(4096)
66
- end
67
-
68
- ACME_DIRECTORY = 'https://acme-staging-v02.api.letsencrypt.org/directory'
69
-
70
- def acme_client
71
- @acme_client ||= setup_acme_client
72
- end
73
-
74
- def setup_acme_client
75
- client = Acme::Client.new(
76
- private_key: private_key,
77
- directory: ACME_DIRECTORY
78
- )
79
- p client: client
80
- account = client.new_account(
81
- contact: 'mailto:info@noteflakes.com',
82
- terms_of_service_agreed: true
83
- )
84
- p account: account.kid
85
- client
86
- end
87
-
88
- def provision_certificate(name)
89
- order = acme_client.new_order(identifiers: [name])
90
- p order: true
91
- authorization = order.authorizations.first
92
- p authorization: authorization
93
- challenge = authorization.http
94
- p challenge: challenge
95
-
96
- @challenge_handler.add(challenge)
97
- challenge.request_validation
98
- p challenge_status: challenge.status
99
- while challenge.status == 'pending'
100
- sleep(1)
101
- challenge.reload
102
- p challenge_status: challenge.status
103
- end
104
-
105
- csr = Acme::Client::CertificateRequest.new(private_key: @private_key, subject: { common_name: name })
106
- p csr: csr
107
- order.finalize(csr: csr)
108
- p order_status: order.status
109
- while order.status == 'processing'
110
- sleep(1)
111
- order.reload
112
- p order_status: order.status
113
- end
114
- order.certificate.tap { |c| p certificate: c } # => PEM-formatted certificate
115
- end
116
- end
117
-
118
- class AcmeHTTPChallengeHandler
119
- def initialize
120
- @challenges = {}
121
- end
122
-
123
- def add(challenge)
124
- path = "/.well-known/acme-challenge/#{challenge.token}"
125
- @challenges[path] = challenge
126
- end
127
-
128
- def call(req)
129
- # handle incoming request
130
- challenge = @challenges[req.path]
131
- return req.respond(nil, ':status' => 400) unless challenge
132
-
133
- req.respond(challenge.file_content, 'content-type' => challenge.content_type)
134
- @challenges.delete(req.path)
135
- end
136
- end
137
-
138
- challenge_handler = AcmeHTTPChallengeHandler.new
139
- certificate_manager = CertificateManager.new(
140
- store: {},
141
- challenge_handler: challenge_handler
142
- )
143
-
144
- http_handler = Tipi.route do |r|
145
- r.on('/.well-known/acme-challenge') { challenge_handler.call(r) }
146
- r.default { r.redirect "https://#{r.host}#{r.path}" }
147
- end
148
-
149
- https_handler = ->(r) { r.respond('Hello, world!') }
150
-
151
- http_listener = spin do
152
- opts = {
153
- reuse_addr: true,
154
- dont_linger: true,
155
- }
156
- puts 'Listening for HTTP on localhost:10080'
157
- Tipi.serve('0.0.0.0', 10080, opts, &http_handler)
158
- end
159
-
160
- https_listener = spin do
161
- ctx = OpenSSL::SSL::SSLContext.new
162
- ctx.servername_cb = proc { |_, name| p name: name; certificate_manager[name] }
163
- opts = {
164
- reuse_addr: true,
165
- dont_linger: true,
166
- secure_context: ctx,
167
- alpn_protocols: Tipi::ALPN_PROTOCOLS
168
- }
169
-
170
- puts 'Listening for HTTPS on localhost:10443'
171
- server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
172
- server.accept_loop do |client|
173
- spin do
174
- service.incr_connection_count
175
- Tipi.client_loop(client, opts) { |req| service.http_request(req) }
176
- ensure
177
- service.decr_connection_count
178
- end
179
- rescue Exception => e
180
- puts "HTTPS accept_loop error: #{e.inspect}"
181
- puts e.backtrace.join("\n")
182
- end
183
- end
184
-
185
- begin
186
- Fiber.await(http_listener, https_listener)
187
- rescue Interrupt
188
- puts "Got SIGINT, terminating"
189
- rescue Exception => e
190
- puts '*' * 40
191
- p e
192
- puts e.backtrace.join("\n")
193
- end