tipi 0.37 → 0.40

Sign up to get free protection for your applications and to get access to all the features.
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