tipi 0.37 → 0.40

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4129b10a7f6b5fb92e4e4784bf8cfd12ea2659840b616b7627bf28d1dc423e87
4
- data.tar.gz: 7f5f71c4a870e33c7de84fd292568b3aca582dcc1adc9496d99e636328b3df4b
3
+ metadata.gz: 3e78bf3b20c7ec6704c26869694cf69e389ae1c0c2f2e44c82d0104d2ecaded9
4
+ data.tar.gz: dac8b0014cbc3a6ee5b146f3645bd38f3d55b21d5c17d969eecaa62c1e8b9306
5
5
  SHA512:
6
- metadata.gz: c1bfcbf1cdf4fa2868554e690a71543d89a37bf2d8cfa27cf604e453fb411d5b1dd2e4bc10678cbe79db6a94238ffef1a09e53771f7e8d8e71f2512b87f7265a
7
- data.tar.gz: 4f476a65800ffb6cbcdfde6e10ed9e1dd7b9fcc4ddd69ad2babed1a747cba4f94e05659cbd235c1f7e71e1595851d77896262d45d1036d216d866b7bdc3f914a
6
+ metadata.gz: fa53cedcd271ba9dbc37821315d27bf14b1eac17451c912eb3dcaa3ceae3b0b738f5d10678cf390f0534edd9d352008e71dec95c46566cad513e3b2d783ea000
7
+ data.tar.gz: 3cf95c22b05c8eaebdf646963d930977ddb010c3ca9174f081cee674e315f5ac672059b31f833ec337c78e3ec43770f72f6bba145ad00e2bd8c1ed8d0acb2174
@@ -22,6 +22,6 @@ jobs:
22
22
  - name: Install dependencies
23
23
  run: |
24
24
  gem install bundler
25
- bundle install
25
+ POLYPHONY_USE_LIBEV=1 bundle install
26
26
  - name: Run tests
27
27
  run: bundle exec rake test
data/.gitignore CHANGED
@@ -54,3 +54,4 @@ build-iPhoneSimulator/
54
54
 
55
55
  # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
56
  # .rubocop-https?--*
57
+ log
data/CHANGELOG.md CHANGED
@@ -1,72 +1,96 @@
1
+ ## 0.40 2021-06-24
2
+
3
+ - Implement serving static files using splice_chunks (nice performance boost for
4
+ files bigger than 1M)
5
+ - Call shutdown before closing socket
6
+ - Fix examples (thanks @timhatch!)
7
+
8
+ ## 0.39 2021-06-20
9
+
10
+ - More work on DF server
11
+ - Fix HTTP2StreamHandler#send_headers
12
+ - Various fixes to HTTP/2 adapter
13
+ - Fix host detection for HTTP/2 connections
14
+ - Fix HTTP/1 adapter #respond with nil body
15
+ - Fix HTTP1Adapter#send_headers
16
+
17
+ ## 0.38 2021-03-09
18
+
19
+ - Don't use chunked transfer encoding for non-streaming responses
20
+
21
+ ## 0.37.2 2021-03-08
22
+
23
+ - Fix header formatting when header value is an array
24
+
1
25
  ## 0.37 2021-02-15
2
26
 
3
- * Update upgrade mechanism to work with updated Qeweney API
27
+ - Update upgrade mechanism to work with updated Qeweney API
4
28
 
5
29
  ## 0.36 2021-02-12
6
30
 
7
- * Use `Qeweney::Status` constants
31
+ - Use `Qeweney::Status` constants
8
32
 
9
33
  ## 0.35 2021-02-10
10
34
 
11
- * Extract Request class into separate [qeweney](https://github.com/digital-fabric/qeweney) gem
35
+ - Extract Request class into separate [qeweney](https://github.com/digital-fabric/qeweney) gem
12
36
 
13
37
  ## 0.34 2021-02-07
14
38
 
15
- * Implement digital fabric service and agents
16
- * Add multipart and urlencoded form data parsing
17
- * Improve request body reading behaviour
18
- * Add more `Request` information methods
19
- * Add access to connection for HTTP2 requests
20
- * Allow calling `Request#send_chunk` with empty chunk
21
- * Add support for handling protocol upgrades from within request handler
39
+ - Implement digital fabric service and agents
40
+ - Add multipart and urlencoded form data parsing
41
+ - Improve request body reading behaviour
42
+ - Add more `Request` information methods
43
+ - Add access to connection for HTTP2 requests
44
+ - Allow calling `Request#send_chunk` with empty chunk
45
+ - Add support for handling protocol upgrades from within request handler
22
46
 
23
47
  ## 0.33 2020-11-20
24
48
 
25
- * Update code for Polyphony 0.47.5
26
- * Add support for Rack::File body to Tipi::RackAdapter
49
+ - Update code for Polyphony 0.47.5
50
+ - Add support for Rack::File body to Tipi::RackAdapter
27
51
 
28
52
  ## 0.32 2020-08-14
29
53
 
30
- * Respond with array of strings instead of concatenating for HTTP 1
31
- * Use read_loop instead of readpartial
32
- * Fix http upgrade test
54
+ - Respond with array of strings instead of concatenating for HTTP 1
55
+ - Use read_loop instead of readpartial
56
+ - Fix http upgrade test
33
57
 
34
58
  ## 0.31 2020-07-28
35
59
 
36
- * Fix websocket server code
37
- * Implement configuration layer (WIP)
38
- * Improve performance of rack adapter
60
+ - Fix websocket server code
61
+ - Implement configuration layer (WIP)
62
+ - Improve performance of rack adapter
39
63
 
40
64
  ## 0.30 2020-07-15
41
65
 
42
- * Rename project to Tipi
43
- * Rearrange source code
44
- * Remove HTTP client code (to be developed eventually into a separate gem)
45
- * Fix header rendering in rack adapter (#2)
66
+ - Rename project to Tipi
67
+ - Rearrange source code
68
+ - Remove HTTP client code (to be developed eventually into a separate gem)
69
+ - Fix header rendering in rack adapter (#2)
46
70
 
47
71
  ## 0.29 2020-07-06
48
72
 
49
- * Use IO#read_loop
73
+ - Use IO#read_loop
50
74
 
51
75
  ## 0.28 2020-07-03
52
76
 
53
- * Update with API changes from Polyphony >= 0.41
77
+ - Update with API changes from Polyphony >= 0.41
54
78
 
55
79
  ## 0.27 2020-04-14
56
80
 
57
- * Remove modulation dependency
81
+ - Remove modulation dependency
58
82
 
59
83
  ## 0.26 2020-03-03
60
84
 
61
- * Fix `Server#listen`
85
+ - Fix `Server#listen`
62
86
 
63
87
  ## 0.25 2020-02-19
64
88
 
65
- * Ensure server socket is closed upon stopping loop
66
- * Fix `Request#format_header_lines`
89
+ - Ensure server socket is closed upon stopping loop
90
+ - Fix `Request#format_header_lines`
67
91
 
68
92
  ## 0.24 2020-01-08
69
93
 
70
- * Move HTTP to separate polyphony-http gem
94
+ - Move HTTP to separate polyphony-http gem
71
95
 
72
96
  For earlier changes look at the Polyphony changelog.
data/Gemfile.lock CHANGED
@@ -1,22 +1,38 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tipi (0.37)
4
+ tipi (0.40)
5
+ acme-client (~> 2.0.8)
5
6
  http-2 (~> 0.10.0)
6
7
  http_parser.rb (~> 0.6.0)
7
8
  msgpack (~> 1.4.2)
8
- polyphony (~> 0.51.0)
9
- qeweney (~> 0.5)
9
+ polyphony (~> 0.57.0)
10
+ qeweney (~> 0.10.0)
10
11
  rack (>= 2.0.8, < 2.3.0)
11
12
  websocket (~> 1.2.8)
12
13
 
13
14
  GEM
14
15
  remote: https://rubygems.org/
15
16
  specs:
17
+ acme-client (2.0.8)
18
+ faraday (>= 0.17, < 2.0.0)
16
19
  ansi (1.5.0)
17
20
  builder (3.2.4)
18
21
  docile (1.3.2)
19
22
  escape_utils (1.2.1)
23
+ faraday (1.4.3)
24
+ faraday-em_http (~> 1.0)
25
+ faraday-em_synchrony (~> 1.0)
26
+ faraday-excon (~> 1.1)
27
+ faraday-net_http (~> 1.0)
28
+ faraday-net_http_persistent (~> 1.1)
29
+ multipart-post (>= 1.2, < 3)
30
+ ruby2_keywords (>= 0.0.4)
31
+ faraday-em_http (1.0.0)
32
+ faraday-em_synchrony (1.0.0)
33
+ faraday-excon (1.1.0)
34
+ faraday-net_http (1.0.1)
35
+ faraday-net_http_persistent (1.1.0)
20
36
  http-2 (0.10.2)
21
37
  http_parser.rb (0.6.0)
22
38
  json (2.3.1)
@@ -28,12 +44,14 @@ GEM
28
44
  minitest (>= 5.0)
29
45
  ruby-progressbar
30
46
  msgpack (1.4.2)
31
- polyphony (0.51.0)
32
- qeweney (0.5)
47
+ multipart-post (2.1.1)
48
+ polyphony (0.57.0)
49
+ qeweney (0.10)
33
50
  escape_utils (~> 1.2.1)
34
51
  rack (2.2.3)
35
52
  rake (12.3.3)
36
53
  ruby-progressbar (1.10.1)
54
+ ruby2_keywords (0.0.4)
37
55
  simplecov (0.17.1)
38
56
  docile (~> 1.1)
39
57
  json (>= 1.8, < 3)
data/TODO.md CHANGED
@@ -1,8 +1,82 @@
1
- # Digital Fabric
1
+ ## Add an API for reading a request body chunk into an IO (pipe)
2
+
3
+ ```ruby
4
+ # currently
5
+ chunk = req.next_chunk
6
+ # or
7
+ req.each_chunk { |c| do_something(c) }
8
+
9
+ # what we'd like to do
10
+ r, w = IO.pipe
11
+ len = req.splice_chunk(w)
12
+ sock << "Here comes a chunk of #{len} bytes\n"
13
+ sock.splice(r, len)
14
+
15
+ # or:
16
+ r, w = IO.pipe
17
+ req.splice_each_chunk(w) do |len|
18
+ sock << "Here comes a chunk of #{len} bytes\n"
19
+ sock.splice(r, len)
20
+ end
21
+ ```
22
+
23
+ # HTTP/1.1 parser
24
+
25
+ - httparser.rb is not actively updated
26
+ - the httparser.rb C parser code comes originally from https://github.com/nodejs/llhttp
27
+ - there's a Ruby gem https://github.com/metabahn/llhttp, but its API is too low-level
28
+ (lots of callbacks, headers need to be retained across callbacks)
29
+ - the basic idea is to import the C-code, then build a parser object with the following
30
+ callbacks:
31
+
32
+ ```ruby
33
+ on_headers_complete(headers)
34
+ on_body_chunk(chunk)
35
+ on_message_complete
36
+ ```
37
+
38
+ - The llhttp gem's C-code is here: https://github.com/metabahn/llhttp/tree/main/mri
39
+
40
+ - Actually, if you do a C extension, instead of a callback-based API, we can
41
+ design a blocking API:
42
+
43
+ ```ruby
44
+ parser = Tipi::HTTP1::Parser.new
45
+ parser.each_request(socket) do |headers|
46
+ request = Request.new(normalize_headers(headers))
47
+ handle_request(request)
48
+ end
49
+ ```
50
+
51
+ # What about HTTP/2?
52
+
53
+ It would be a nice exercise in converting a callback-based API to a blocking
54
+ one:
55
+
56
+ ```ruby
57
+ parser = Tipi::HTTP2::Parser.new(socket)
58
+ parser.each_stream(socket) do |stream|
59
+ spin { handle_stream(stream) }
60
+ end
61
+ ```
62
+
63
+
64
+
65
+ # DF
66
+
67
+ - Add attack protection for IP-address HTTP host:
68
+
69
+ ```ruby
70
+ IPV4_REGEXP = /^\d+\.\d+\.\d+\.\d+$/.freeze
71
+
72
+ def is_attack_request?(req)
73
+ return true if req.host =~ IPV4_REGEXP && req.query[:q] != 'ping'
74
+ end
75
+ ```
76
+
77
+ - Add attack route to Qeweney routing API
2
78
 
3
- Problems to fix:
4
79
 
5
- - Memory leak (in server? multi agent? multi client?)
6
80
 
7
81
  # Roadmap
8
82
 
@@ -14,7 +88,7 @@ Problems to fix:
14
88
  - https://gitlab.com/honeyryderchuck/http-2-next
15
89
  - Open an issue there, ask what's the difference between the two gems?
16
90
 
17
- ## 0.35
91
+ ## 0.38
18
92
 
19
93
  - Add more poly CLI commands and options:
20
94
 
@@ -26,7 +100,7 @@ Problems to fix:
26
100
  - set port to bind to
27
101
  - set forking process count
28
102
 
29
- ## 0.36 Working Sinatra application
103
+ ## 0.39 Working Sinatra application
30
104
 
31
105
  - app with database access (postgresql)
32
106
  - benchmarks!
data/df/sample_agent.rb CHANGED
@@ -13,7 +13,7 @@ class SampleAgent < DigitalFabric::Agent
13
13
  HTML_SSE = IO.read(File.join(__dir__, 'sse_page.html'))
14
14
 
15
15
  def http_request(req)
16
- path = req['headers'][':path']
16
+ path = req.headers[':path']
17
17
  case path
18
18
  when '/agent'
19
19
  send_df_message(Protocol.http_response(
data/df/server.rb CHANGED
@@ -6,12 +6,17 @@ require 'tipi/digital_fabric'
6
6
  require 'tipi/digital_fabric/executive'
7
7
  require 'json'
8
8
  require 'fileutils'
9
+ require 'localhost/authority'
10
+
9
11
  FileUtils.cd(__dir__)
10
12
 
11
13
  service = DigitalFabric::Service.new(token: 'foobar')
12
14
  executive = DigitalFabric::Executive.new(service, { host: 'executive.realiteq.net' })
13
15
 
14
- spin_loop(interval: 60) { GC.start }
16
+ GC.disable
17
+ Thread.current.backend.idle_gc_period = 60
18
+
19
+ # spin_loop(interval: 60) { GC.start }
15
20
 
16
21
  class Polyphony::BaseException
17
22
  attr_reader :caller_backtrace
@@ -19,13 +24,13 @@ end
19
24
 
20
25
  puts "pid: #{Process.pid}"
21
26
 
22
- tcp_listener = spin do
27
+ http_listener = spin do
23
28
  opts = {
24
29
  reuse_addr: true,
25
30
  dont_linger: true,
26
31
  }
27
- puts 'Listening on localhost:4411'
28
- server = Polyphony::Net.tcp_listen('0.0.0.0', 4411, opts)
32
+ puts 'Listening for HTTP on localhost:10080'
33
+ server = Polyphony::Net.tcp_listen('0.0.0.0', 10080, opts)
29
34
  server.accept_loop do |client|
30
35
  spin do
31
36
  service.incr_connection_count
@@ -33,11 +38,59 @@ tcp_listener = spin do
33
38
  ensure
34
39
  service.decr_connection_count
35
40
  end
41
+ rescue Exception => e
42
+ puts "HTTP accept_loop error: #{e.inspect}"
43
+ puts e.backtrace.join("\n")
36
44
  end
37
45
  end
38
46
 
39
- UNIX_SOCKET_PATH = '/tmp/df.sock'
47
+ CERTIFICATE_REGEXP = /(-----BEGIN CERTIFICATE-----\n[^-]+-----END CERTIFICATE-----\n)/.freeze
48
+
49
+ https_listener = spin do
50
+ private_key = OpenSSL::PKey::RSA.new IO.read('../../reality/ssl/privkey.pem')
51
+ c = IO.read('../../reality/ssl/cacert.pem')
52
+ certificates = c.scan(CERTIFICATE_REGEXP).map { |p| OpenSSL::X509::Certificate.new(p.first) }
53
+ ctx = OpenSSL::SSL::SSLContext.new
54
+ cert = certificates.shift
55
+ puts "Certificate expires: #{cert.not_after.inspect}"
56
+ ctx.add_certificate(cert, private_key, certificates)
57
+ ctx.ciphers = 'ECDH+aRSA'
58
+
59
+ # TODO: further limit ciphers
60
+ # ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/environments/tls.rb
61
+ # ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/tls.rb
62
+
63
+ opts = {
64
+ reuse_addr: true,
65
+ dont_linger: true,
66
+ secure_context: ctx,
67
+ alpn_protocols: Tipi::ALPN_PROTOCOLS
68
+ }
69
+
70
+ puts 'Listening for HTTPS on localhost:10443'
71
+ server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
72
+ loop do
73
+ client = server.accept
74
+ spin do
75
+ service.incr_connection_count
76
+ Tipi.client_loop(client, opts) { |req| service.http_request(req) }
77
+ rescue Exception => e
78
+ puts "Exception: #{e.inspect}"
79
+ puts e.backtrace.join("\n")
80
+ ensure
81
+ service.decr_connection_count
82
+ end
83
+ rescue Polyphony::BaseException
84
+ raise
85
+ rescue OpenSSL::SSL::SSLError, SystemCallError => e
86
+ puts "HTTPS accept error: #{e.inspect}"
87
+ rescue Exception => e
88
+ puts "HTTPS accept error: #{e.inspect}"
89
+ puts e.backtrace.join("\n")
90
+ end
91
+ end
40
92
 
93
+ UNIX_SOCKET_PATH = '/tmp/df.sock'
41
94
  unix_listener = spin do
42
95
  puts "Listening on #{UNIX_SOCKET_PATH}"
43
96
  FileUtils.rm(UNIX_SOCKET_PATH) if File.exists?(UNIX_SOCKET_PATH)
@@ -46,9 +99,13 @@ unix_listener = spin do
46
99
  end
47
100
 
48
101
  begin
49
- Fiber.await(tcp_listener, unix_listener)
102
+ Fiber.await(http_listener, https_listener, unix_listener)
50
103
  rescue Interrupt
51
104
  puts "Got SIGINT, shutting down gracefully"
52
105
  service.graceful_shutdown
53
106
  puts "post graceful shutdown"
107
+ rescue Exception => e
108
+ puts '*' * 40
109
+ p e
110
+ puts e.backtrace.join("\n")
54
111
  end
@@ -0,0 +1,193 @@
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