tipi 0.39 → 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: 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