tipi 0.39 → 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: 02c5ce4798e1961ca59798b75bafe158570e777661f6fc8667169553df3cd3f4
4
- data.tar.gz: 405bfa3526efaad394d34840764dc131230a366574cfc52b63344a6d5cdfe6dd
3
+ metadata.gz: 3e78bf3b20c7ec6704c26869694cf69e389ae1c0c2f2e44c82d0104d2ecaded9
4
+ data.tar.gz: dac8b0014cbc3a6ee5b146f3645bd38f3d55b21d5c17d969eecaa62c1e8b9306
5
5
  SHA512:
6
- metadata.gz: 754bd35136e4e004e6bd782eb2060d933b2439c437a9c2a966529e0465c42096b97fa08b1dae04b7a3c8aa0c77c367fd5395d215cca73fd7cf1aa80c297b3178
7
- data.tar.gz: 24425d798c076ad43b4fa45b641a90a5e801dfe35acd6217e8a8fc97ea546f18457fc9a8653e47f7bd5f696da885ef86db8db36385dcaa79ccbcf9d42d64081c
6
+ metadata.gz: fa53cedcd271ba9dbc37821315d27bf14b1eac17451c912eb3dcaa3ceae3b0b738f5d10678cf390f0534edd9d352008e71dec95c46566cad513e3b2d783ea000
7
+ data.tar.gz: 3cf95c22b05c8eaebdf646963d930977ddb010c3ca9174f081cee674e315f5ac672059b31f833ec337c78e3ec43770f72f6bba145ad00e2bd8c1ed8d0acb2174
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
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
+
1
8
  ## 0.39 2021-06-20
2
9
 
3
10
  - More work on DF server
data/Gemfile.lock CHANGED
@@ -1,22 +1,38 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tipi (0.39)
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.55.0)
9
- qeweney (~> 0.9.1)
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.55.0)
32
- qeweney (0.9.1)
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/df/server.rb CHANGED
@@ -13,7 +13,10 @@ FileUtils.cd(__dir__)
13
13
  service = DigitalFabric::Service.new(token: 'foobar')
14
14
  executive = DigitalFabric::Executive.new(service, { host: 'executive.realiteq.net' })
15
15
 
16
- 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 }
17
20
 
18
21
  class Polyphony::BaseException
19
22
  attr_reader :caller_backtrace
@@ -51,7 +54,12 @@ https_listener = spin do
51
54
  cert = certificates.shift
52
55
  puts "Certificate expires: #{cert.not_after.inspect}"
53
56
  ctx.add_certificate(cert, private_key, certificates)
54
- # ctx = Localhost::Authority.fetch.server_context
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
+
55
63
  opts = {
56
64
  reuse_addr: true,
57
65
  dont_linger: true,
@@ -61,21 +69,28 @@ https_listener = spin do
61
69
 
62
70
  puts 'Listening for HTTPS on localhost:10443'
63
71
  server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
64
- server.accept_loop do |client|
72
+ loop do
73
+ client = server.accept
65
74
  spin do
66
75
  service.incr_connection_count
67
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")
68
80
  ensure
69
81
  service.decr_connection_count
70
82
  end
83
+ rescue Polyphony::BaseException
84
+ raise
85
+ rescue OpenSSL::SSL::SSLError, SystemCallError => e
86
+ puts "HTTPS accept error: #{e.inspect}"
71
87
  rescue Exception => e
72
- puts "HTTPS accept_loop error: #{e.inspect}"
88
+ puts "HTTPS accept error: #{e.inspect}"
73
89
  puts e.backtrace.join("\n")
74
90
  end
75
91
  end
76
92
 
77
93
  UNIX_SOCKET_PATH = '/tmp/df.sock'
78
-
79
94
  unix_listener = spin do
80
95
  puts "Listening on #{UNIX_SOCKET_PATH}"
81
96
  FileUtils.rm(UNIX_SOCKET_PATH) if File.exists?(UNIX_SOCKET_PATH)
@@ -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
@@ -11,11 +11,13 @@ opts = {
11
11
  dont_linger: true
12
12
  }
13
13
 
14
+ server = Tipi.listen('0.0.0.0', 1234, opts)
15
+
14
16
  child_pids = []
15
17
  8.times do
16
18
  pid = Polyphony.fork do
17
19
  puts "forked pid: #{Process.pid}"
18
- Tipi.serve('0.0.0.0', 1234, opts) do |req|
20
+ server.each do |req|
19
21
  req.respond("Hello world! from pid: #{Process.pid}\n")
20
22
  end
21
23
  rescue Interrupt
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'tipi'
5
+
6
+ opts = {
7
+ reuse_addr: true,
8
+ dont_linger: true
9
+ }
10
+
11
+ puts "pid: #{Process.pid}"
12
+ puts 'Listening on port 4411...'
13
+
14
+ app = Tipi.route do |req|
15
+ req.on 'stream' do
16
+ req.send_headers('Foo' => 'Bar')
17
+ sleep 1
18
+ req.send_chunk("foo\n")
19
+ sleep 1
20
+ req.send_chunk("bar\n")
21
+ req.finish
22
+ end
23
+ req.default do
24
+ req.respond("Hello world!\n")
25
+ end
26
+ end
27
+
28
+ trap('INT') { exit! }
29
+ Tipi.serve('0.0.0.0', 4411, opts, &app)
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'tipi'
5
+ require 'fileutils'
6
+
7
+ opts = {
8
+ reuse_addr: true,
9
+ dont_linger: true
10
+ }
11
+
12
+ puts "pid: #{Process.pid}"
13
+ puts 'Listening on port 4411...'
14
+
15
+ root_path = FileUtils.pwd
16
+
17
+ trap('INT') { exit! }
18
+
19
+ app = Tipi.route do |req|
20
+ req.on('normal') do
21
+ path = File.join(root_path, req.route_relative_path)
22
+ if File.file?(path)
23
+ req.serve_file(path)
24
+ else
25
+ req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
26
+ end
27
+ end
28
+ req.on('spliced') do
29
+ path = File.join(root_path, req.route_relative_path)
30
+ if File.file?(path)
31
+ req.serve_file(path, respond_from_io: true)
32
+ else
33
+ req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
34
+ end
35
+ end
36
+ end
37
+
38
+ Tipi.serve('0.0.0.0', 4411, opts, &app)
@@ -1,13 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/inline'
4
-
5
- gemfile do
6
- source 'https://rubygems.org'
7
- gem 'polyphony', '~> 0.44'
8
- gem 'tipi', '~> 0.31'
9
- end
10
-
3
+ require 'bundler/setup'
4
+ require 'tipi'
11
5
  require 'tipi/websocket'
12
6
 
13
7
  class WebsocketClient
@@ -6,7 +6,7 @@
6
6
  <body>
7
7
  <script>
8
8
  var connect = function () {
9
- var exampleSocket = new WebSocket("wss://dev.realiteq.net/");
9
+ var exampleSocket = new WebSocket("ws://localhost:4411/");
10
10
 
11
11
  exampleSocket.onopen = function (event) {
12
12
  document.querySelector('#status').innerText = 'connected';
@@ -30,4 +30,4 @@
30
30
  <h1 id="status">disconnected</h1>
31
31
  <h1 id="msg"></h1>
32
32
  </body>
33
- </html>
33
+ </html>
data/lib/tipi.rb CHANGED
@@ -4,6 +4,12 @@ require 'polyphony'
4
4
  require_relative './tipi/http1_adapter'
5
5
  require_relative './tipi/http2_adapter'
6
6
  require_relative './tipi/configuration'
7
+ require_relative './tipi/response_extensions'
8
+ require 'qeweney/request'
9
+
10
+ class Qeweney::Request
11
+ include Tipi::ResponseExtensions
12
+ end
7
13
 
8
14
  module Tipi
9
15
  ALPN_PROTOCOLS = %w[h2 http/1.1].freeze
@@ -50,6 +50,8 @@ module Tipi
50
50
  # release references to various objects
51
51
  @requests_head = @requests_tail = nil
52
52
  @parser = nil
53
+ @splicing_pipe = nil
54
+ @conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
53
55
  @conn.close
54
56
  end
55
57
 
@@ -217,7 +219,25 @@ module Tipi
217
219
  @conn.write(formatted_headers)
218
220
  end
219
221
  end
222
+
223
+ def respond_from_io(request, io, headers, chunk_size = 2**14)
224
+ consume_request(request) if @parsing
225
+
226
+ formatted_headers = format_headers(headers, true, true)
227
+ request.tx_incr(formatted_headers.bytesize)
220
228
 
229
+ # assume chunked encoding
230
+ Thread.current.backend.splice_chunks(
231
+ io,
232
+ @conn,
233
+ formatted_headers,
234
+ "0\r\n\r\n",
235
+ ->(len) { "#{len.to_s(16)}\r\n" },
236
+ "\r\n",
237
+ 16384
238
+ )
239
+ end
240
+
221
241
  # Sends response headers. If empty_response is truthy, the response status
222
242
  # code will default to 204, otherwise to 200.
223
243
  # @param request [Qeweney::Request] HTTP request
@@ -248,6 +268,20 @@ module Tipi
248
268
  @conn.write(data)
249
269
  end
250
270
 
271
+ def send_chunk_from_io(request, io, r, w, chunk_size)
272
+ len = w.splice(io, chunk_size)
273
+ if len > 0
274
+ Thread.current.backend.chain(
275
+ [:write, @conn, "#{len.to_s(16)}\r\n"],
276
+ [:splice, r, @conn, len],
277
+ [:write, @conn, "\r\n"]
278
+ )
279
+ else
280
+ @conn.write("0\r\n\r\n")
281
+ end
282
+ len
283
+ end
284
+
251
285
  # Finishes the response to the current request. If no headers were sent,
252
286
  # default headers are sent using #send_headers.
253
287
  # @return [void]
@@ -257,6 +291,7 @@ module Tipi
257
291
  end
258
292
 
259
293
  def close
294
+ @conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
260
295
  @conn.close
261
296
  end
262
297
 
@@ -303,7 +338,7 @@ module Tipi
303
338
  if chunked
304
339
  +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
305
340
  else
306
- +"HTTP/1.1 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
341
+ +"HTTP/1.1 #{status}\r\nContent-Length: #{body.is_a?(String) ? body.bytesize : body.to_i}\r\n"
307
342
  end
308
343
  end
309
344
 
@@ -87,10 +87,12 @@ module Tipi
87
87
  def finalize_client_loop
88
88
  @interface = nil
89
89
  @streams.each_key(&:stop)
90
+ @conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
90
91
  @conn.close
91
92
  end
92
93
 
93
94
  def close
95
+ @conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
94
96
  @conn.close
95
97
  end
96
98
 
@@ -123,9 +123,23 @@ module Tipi
123
123
  headers[':status'] = headers[':status'].to_s
124
124
  with_transfer_count(request) do
125
125
  @stream.headers(transform_headers(headers))
126
+ @headers_sent = true
126
127
  @stream.data(chunk || '')
127
128
  end
128
- @headers_sent = true
129
+ rescue HTTP2::Error::StreamClosed
130
+ # ignore
131
+ end
132
+
133
+ def respond_from_io(request, io, headers, chunk_size = 2**16)
134
+ headers[':status'] ||= Qeweney::Status::OK
135
+ headers[':status'] = headers[':status'].to_s
136
+ with_transfer_count(request) do
137
+ @stream.headers(transform_headers(headers))
138
+ @headers_sent = true
139
+ while (chunk = io.read(chunk_size))
140
+ @stream.data(chunk)
141
+ end
142
+ end
129
143
  rescue HTTP2::Error::StreamClosed
130
144
  # ignore
131
145
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'qeweney/request'
4
+
5
+ module Tipi
6
+ module ResponseExtensions
7
+ SPLICE_CHUNKS_SIZE_THRESHOLD = 2**20
8
+
9
+ def serve_io(io, opts)
10
+ if !opts[:stat] || opts[:stat].size >= SPLICE_CHUNKS_SIZE_THRESHOLD
11
+ @adapter.respond_from_io(self, io, opts[:headers])
12
+ else
13
+ respond(io.read, opts[:headers] || {})
14
+ end
15
+ end
16
+ end
17
+ end
data/lib/tipi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tipi
4
- VERSION = '0.39'
4
+ VERSION = '0.40'
5
5
  end
data/tipi.gemspec CHANGED
@@ -19,13 +19,14 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.executables = ['tipi']
21
21
 
22
- s.add_runtime_dependency 'polyphony', '~>0.55.0'
23
- s.add_runtime_dependency 'qeweney', '~>0.9.1'
22
+ s.add_runtime_dependency 'polyphony', '~>0.57.0'
23
+ s.add_runtime_dependency 'qeweney', '~>0.10.0'
24
24
 
25
25
  s.add_runtime_dependency 'http_parser.rb', '~>0.6.0'
26
26
  s.add_runtime_dependency 'http-2', '~>0.10.0'
27
27
  s.add_runtime_dependency 'rack', '>=2.0.8', '<2.3.0'
28
28
  s.add_runtime_dependency 'websocket', '~>1.2.8'
29
+ s.add_runtime_dependency 'acme-client', '~>2.0.8'
29
30
 
30
31
  # for digital fabric
31
32
  s.add_runtime_dependency 'msgpack', '~>1.4.2'
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.39'
4
+ version: '0.40'
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-20 00:00:00.000000000 Z
11
+ date: 2021-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: polyphony
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.55.0
19
+ version: 0.57.0
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.55.0
26
+ version: 0.57.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: qeweney
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.9.1
33
+ version: 0.10.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.9.1
40
+ version: 0.10.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: http_parser.rb
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -100,6 +100,20 @@ dependencies:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
102
  version: 1.2.8
103
+ - !ruby/object:Gem::Dependency
104
+ name: acme-client
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 2.0.8
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: 2.0.8
103
117
  - !ruby/object:Gem::Dependency
104
118
  name: msgpack
105
119
  requirement: !ruby/object:Gem::Requirement
@@ -215,6 +229,7 @@ files:
215
229
  - docs/README.md
216
230
  - docs/tipi-logo.png
217
231
  - e
232
+ - examples/automatic_certificate.rb
218
233
  - examples/cuba.ru
219
234
  - examples/hanami-api.ru
220
235
  - examples/hello.ru
@@ -224,7 +239,9 @@ files:
224
239
  - examples/http_server_forked.rb
225
240
  - examples/http_server_form.rb
226
241
  - examples/http_server_graceful.rb
242
+ - examples/http_server_routes.rb
227
243
  - examples/http_server_simple.rb
244
+ - examples/http_server_static.rb
228
245
  - examples/http_server_throttled.rb
229
246
  - examples/http_server_throttled_accept.rb
230
247
  - examples/http_server_timeout.rb
@@ -259,6 +276,7 @@ files:
259
276
  - lib/tipi/http2_adapter.rb
260
277
  - lib/tipi/http2_stream.rb
261
278
  - lib/tipi/rack_adapter.rb
279
+ - lib/tipi/response_extensions.rb
262
280
  - lib/tipi/version.rb
263
281
  - lib/tipi/websocket.rb
264
282
  - test/coverage.rb