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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/test.yml +3 -1
- data/.gitignore +5 -1
- data/CHANGELOG.md +35 -0
- data/Gemfile +7 -1
- data/Gemfile.lock +55 -29
- data/README.md +184 -8
- data/Rakefile +1 -3
- data/benchmarks/bm_http1_parser.rb +85 -0
- data/bin/benchmark +37 -0
- data/bin/h1pd +6 -0
- data/bin/tipi +3 -21
- data/bm.png +0 -0
- data/df/agent.rb +1 -1
- data/df/sample_agent.rb +2 -2
- data/df/server.rb +16 -102
- data/df/server_utils.rb +175 -0
- data/examples/full_service.rb +13 -0
- data/examples/hello.rb +5 -0
- data/examples/http1_parser.rb +55 -0
- data/examples/http_server.js +1 -1
- data/examples/http_server.rb +15 -3
- data/examples/http_server_graceful.rb +1 -1
- data/examples/http_server_static.rb +6 -18
- data/examples/https_server.rb +41 -15
- data/examples/rack_server_forked.rb +26 -0
- data/examples/rack_server_https_forked.rb +1 -1
- data/examples/servername_cb.rb +37 -0
- data/examples/websocket_demo.rb +1 -1
- data/lib/tipi/acme.rb +315 -0
- data/lib/tipi/cli.rb +93 -0
- data/lib/tipi/config_dsl.rb +13 -13
- data/lib/tipi/configuration.rb +2 -2
- data/{e → lib/tipi/controller/bare_polyphony.rb} +0 -0
- data/lib/tipi/controller/bare_stock.rb +10 -0
- data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
- data/lib/tipi/controller/web_polyphony.rb +351 -0
- data/lib/tipi/controller/web_stock.rb +631 -0
- data/lib/tipi/controller.rb +12 -0
- data/lib/tipi/digital_fabric/agent.rb +10 -8
- data/lib/tipi/digital_fabric/agent_proxy.rb +26 -12
- data/lib/tipi/digital_fabric/executive.rb +7 -3
- data/lib/tipi/digital_fabric/protocol.rb +19 -4
- data/lib/tipi/digital_fabric/request_adapter.rb +0 -4
- data/lib/tipi/digital_fabric/service.rb +84 -56
- data/lib/tipi/handler.rb +2 -2
- data/lib/tipi/http1_adapter.rb +86 -125
- data/lib/tipi/http2_adapter.rb +29 -16
- data/lib/tipi/http2_stream.rb +52 -56
- data/lib/tipi/rack_adapter.rb +2 -53
- data/lib/tipi/response_extensions.rb +2 -2
- data/lib/tipi/supervisor.rb +75 -0
- data/lib/tipi/version.rb +1 -1
- data/lib/tipi/websocket.rb +3 -3
- data/lib/tipi.rb +8 -5
- data/test/coverage.rb +2 -2
- data/test/helper.rb +60 -12
- data/test/test_http_server.rb +14 -41
- data/test/test_request.rb +2 -29
- data/tipi.gemspec +12 -8
- metadata +88 -28
- data/examples/automatic_certificate.rb +0 -193
data/lib/tipi/http1_adapter.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require_relative './http2_adapter'
|
3
|
+
require 'h1p'
|
5
4
|
require 'qeweney/request'
|
6
5
|
|
6
|
+
require_relative './http2_adapter'
|
7
|
+
|
7
8
|
module Tipi
|
8
9
|
# HTTP1 protocol implementation
|
9
10
|
class HTTP1Adapter
|
@@ -14,127 +15,83 @@ module Tipi
|
|
14
15
|
@conn = conn
|
15
16
|
@opts = opts
|
16
17
|
@first = true
|
17
|
-
@parser = ::
|
18
|
+
@parser = H1P::Parser.new(@conn)
|
18
19
|
end
|
19
|
-
|
20
|
+
|
20
21
|
def each(&block)
|
21
|
-
|
22
|
-
|
22
|
+
while true
|
23
|
+
headers = @parser.parse_headers
|
24
|
+
break unless headers
|
25
|
+
|
26
|
+
# handle_request returns true if connection is not persistent or was
|
27
|
+
# upgraded
|
28
|
+
break if handle_request(headers, &block)
|
23
29
|
end
|
30
|
+
rescue H1P::Error, ArgumentError
|
31
|
+
# an ArgumentError might be raised in the parser if an invalid input
|
32
|
+
# string is given as the HTTP method (String#upcase will raise on invalid HTTP string)
|
33
|
+
#
|
34
|
+
# ignore
|
24
35
|
rescue SystemCallError, IOError
|
25
36
|
# ignore
|
26
37
|
ensure
|
27
38
|
finalize_client_loop
|
28
39
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@first = nil
|
39
|
-
end
|
40
|
-
return true if upgrade_connection(request.headers, &block)
|
41
|
-
|
42
|
-
@requests_head = request.__next__
|
43
|
-
block.call(request)
|
44
|
-
return true unless request.keep_alive?
|
40
|
+
|
41
|
+
def handle_request(headers, &block)
|
42
|
+
scheme = (proto = headers['x-forwarded-proto']) ?
|
43
|
+
proto.downcase : scheme_from_connection
|
44
|
+
headers[':scheme'] = scheme
|
45
|
+
@protocol = headers[':protocol']
|
46
|
+
if @first
|
47
|
+
headers[':first'] = true
|
48
|
+
@first = nil
|
45
49
|
end
|
46
|
-
|
50
|
+
|
51
|
+
return true if upgrade_connection(headers, &block)
|
52
|
+
|
53
|
+
request = Qeweney::Request.new(headers, self)
|
54
|
+
if !@parser.complete?
|
55
|
+
request.buffer_body_chunk(@parser.read_body_chunk(true))
|
56
|
+
end
|
57
|
+
block.call(request)
|
58
|
+
return !persistent_connection?(headers)
|
47
59
|
end
|
48
|
-
|
60
|
+
|
61
|
+
def persistent_connection?(headers)
|
62
|
+
if headers[':protocol'] == 'http/1.1'
|
63
|
+
return headers['connection'] != 'close'
|
64
|
+
else
|
65
|
+
connection = headers['connection']
|
66
|
+
return connection && connection != 'close'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
49
70
|
def finalize_client_loop
|
50
|
-
# release references to various objects
|
51
|
-
@requests_head = @requests_tail = nil
|
52
71
|
@parser = nil
|
53
72
|
@splicing_pipe = nil
|
54
73
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
55
74
|
@conn.close
|
56
75
|
end
|
57
|
-
|
76
|
+
|
58
77
|
# Reads a body chunk for the current request. Transfers control to the parse
|
59
78
|
# loop, and resumes once the parse_loop has fired the on_body callback
|
60
|
-
def get_body_chunk(request)
|
61
|
-
@
|
62
|
-
@next_chunk = nil
|
63
|
-
while !@requests_tail.complete? && (data = @conn.readpartial(8192))
|
64
|
-
request.rx_incr(data.bytesize)
|
65
|
-
@parser << data
|
66
|
-
return @next_chunk if @next_chunk
|
67
|
-
|
68
|
-
snooze
|
69
|
-
end
|
70
|
-
nil
|
71
|
-
ensure
|
72
|
-
@waiting_for_body_chunk = nil
|
73
|
-
end
|
74
|
-
|
75
|
-
# Waits for the current request to complete. Transfers control to the parse
|
76
|
-
# loop, and resumes once the parse_loop has fired the on_message_complete
|
77
|
-
# callback
|
78
|
-
def consume_request(request)
|
79
|
-
request = @requests_head
|
80
|
-
@conn.recv_loop do |data|
|
81
|
-
request.rx_incr(data.bytesize)
|
82
|
-
@parser << data
|
83
|
-
return if request.complete?
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def protocol
|
88
|
-
version = @parser.http_version
|
89
|
-
"HTTP #{version.join('.')}"
|
90
|
-
end
|
91
|
-
|
92
|
-
def on_headers_complete(headers)
|
93
|
-
headers = normalize_headers(headers)
|
94
|
-
headers[':path'] = @parser.request_url
|
95
|
-
headers[':method'] = @parser.http_method.downcase
|
96
|
-
scheme = (proto = headers['x-forwarded-proto']) ?
|
97
|
-
proto.downcase : scheme_from_connection
|
98
|
-
headers[':scheme'] = scheme
|
99
|
-
queue_request(Qeweney::Request.new(headers, self))
|
79
|
+
def get_body_chunk(request, buffered_only = false)
|
80
|
+
@parser.read_body_chunk(buffered_only)
|
100
81
|
end
|
101
82
|
|
102
|
-
def
|
103
|
-
|
104
|
-
k = k.downcase
|
105
|
-
hk = h[k]
|
106
|
-
if hk
|
107
|
-
hk = h[k] = [hk] unless hk.is_a?(Array)
|
108
|
-
v.is_a?(Array) ? hk.concat(v) : hk << v
|
109
|
-
else
|
110
|
-
h[k] = v
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def queue_request(request)
|
116
|
-
if @requests_head
|
117
|
-
@requests_tail.__next__ = request
|
118
|
-
@requests_tail = request
|
119
|
-
else
|
120
|
-
@requests_head = @requests_tail = request
|
121
|
-
end
|
83
|
+
def get_body(request)
|
84
|
+
@parser.read_body
|
122
85
|
end
|
123
|
-
|
124
|
-
def
|
125
|
-
|
126
|
-
@next_chunk = chunk
|
127
|
-
@waiting_for_body_chunk = nil
|
128
|
-
else
|
129
|
-
@requests_tail.buffer_body_chunk(chunk)
|
130
|
-
end
|
86
|
+
|
87
|
+
def complete?(request)
|
88
|
+
@parser.complete?
|
131
89
|
end
|
132
|
-
|
133
|
-
def
|
134
|
-
@
|
135
|
-
@requests_tail.complete!(@parser.keep_alive?)
|
90
|
+
|
91
|
+
def protocol
|
92
|
+
@protocol
|
136
93
|
end
|
137
|
-
|
94
|
+
|
138
95
|
# Upgrades the connection to a different protocol, if the 'Upgrade' header is
|
139
96
|
# given. By default the only supported upgrade protocol is HTTP2. Additional
|
140
97
|
# protocols, notably WebSocket, can be specified by passing a hash to the
|
@@ -160,27 +117,28 @@ module Tipi
|
|
160
117
|
def upgrade_connection(headers, &block)
|
161
118
|
upgrade_protocol = headers['upgrade']
|
162
119
|
return nil unless upgrade_protocol
|
163
|
-
|
120
|
+
|
164
121
|
upgrade_protocol = upgrade_protocol.downcase.to_sym
|
165
122
|
upgrade_handler = @opts[:upgrade] && @opts[:upgrade][upgrade_protocol]
|
166
123
|
return upgrade_with_handler(upgrade_handler, headers) if upgrade_handler
|
167
124
|
return upgrade_to_http2(headers, &block) if upgrade_protocol == :h2c
|
168
|
-
|
125
|
+
|
169
126
|
nil
|
170
127
|
end
|
171
|
-
|
128
|
+
|
172
129
|
def upgrade_with_handler(handler, headers)
|
173
|
-
@parser =
|
130
|
+
@parser = nil
|
174
131
|
handler.(self, headers)
|
175
132
|
true
|
176
133
|
end
|
177
|
-
|
134
|
+
|
178
135
|
def upgrade_to_http2(headers, &block)
|
179
|
-
|
180
|
-
|
136
|
+
headers = http2_upgraded_headers(headers)
|
137
|
+
body = @parser.read_body
|
138
|
+
HTTP2Adapter.upgrade_each(@conn, @opts, headers, body, &block)
|
181
139
|
true
|
182
140
|
end
|
183
|
-
|
141
|
+
|
184
142
|
# Returns headers for HTTP2 upgrade
|
185
143
|
# @param headers [Hash] request headers
|
186
144
|
# @return [Hash] headers for HTTP2 upgrade
|
@@ -198,10 +156,10 @@ module Tipi
|
|
198
156
|
def scheme_from_connection
|
199
157
|
@conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
|
200
158
|
end
|
201
|
-
|
159
|
+
|
202
160
|
# response API
|
203
161
|
|
204
|
-
CRLF = "\r\n"
|
162
|
+
CRLF = "\r\n"
|
205
163
|
CRLF_ZERO_CRLF_CRLF = "\r\n0\r\n\r\n"
|
206
164
|
|
207
165
|
# Sends response including headers and body. Waits for the request to complete
|
@@ -210,7 +168,6 @@ module Tipi
|
|
210
168
|
# @param body [String] response body
|
211
169
|
# @param headers
|
212
170
|
def respond(request, body, headers)
|
213
|
-
consume_request(request) if @parsing
|
214
171
|
formatted_headers = format_headers(headers, body, false)
|
215
172
|
request.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
|
216
173
|
if body
|
@@ -220,21 +177,21 @@ module Tipi
|
|
220
177
|
end
|
221
178
|
end
|
222
179
|
|
223
|
-
|
224
|
-
consume_request(request) if @parsing
|
180
|
+
CHUNK_LENGTH_PROC = ->(len) { "#{len.to_s(16)}\r\n" }
|
225
181
|
|
182
|
+
def respond_from_io(request, io, headers, chunk_size = 2**14)
|
226
183
|
formatted_headers = format_headers(headers, true, true)
|
227
184
|
request.tx_incr(formatted_headers.bytesize)
|
228
|
-
|
185
|
+
|
229
186
|
# assume chunked encoding
|
230
187
|
Thread.current.backend.splice_chunks(
|
231
188
|
io,
|
232
189
|
@conn,
|
233
190
|
formatted_headers,
|
234
191
|
"0\r\n\r\n",
|
235
|
-
|
192
|
+
CHUNK_LENGTH_PROC,
|
236
193
|
"\r\n",
|
237
|
-
|
194
|
+
chunk_size
|
238
195
|
)
|
239
196
|
end
|
240
197
|
|
@@ -246,11 +203,15 @@ module Tipi
|
|
246
203
|
# @param chunked [boolean] whether to use chunked transfer encoding
|
247
204
|
# @return [void]
|
248
205
|
def send_headers(request, headers, empty_response: false, chunked: true)
|
249
|
-
formatted_headers = format_headers(headers, !empty_response,
|
206
|
+
formatted_headers = format_headers(headers, !empty_response, http1_1?(request) && chunked)
|
250
207
|
request.tx_incr(formatted_headers.bytesize)
|
251
208
|
@conn.write(formatted_headers)
|
252
209
|
end
|
253
|
-
|
210
|
+
|
211
|
+
def http1_1?(request)
|
212
|
+
request.headers[':protocol'] == 'http/1.1'
|
213
|
+
end
|
214
|
+
|
254
215
|
# Sends a response body chunk. If no headers were sent, default headers are
|
255
216
|
# sent using #send_headers. if the done option is true(thy), an empty chunk
|
256
217
|
# will be sent to signal response completion to the client.
|
@@ -267,7 +228,7 @@ module Tipi
|
|
267
228
|
request.tx_incr(data.bytesize)
|
268
229
|
@conn.write(data)
|
269
230
|
end
|
270
|
-
|
231
|
+
|
271
232
|
def send_chunk_from_io(request, io, r, w, chunk_size)
|
272
233
|
len = w.splice(io, chunk_size)
|
273
234
|
if len > 0
|
@@ -289,12 +250,12 @@ module Tipi
|
|
289
250
|
request.tx_incr(5)
|
290
251
|
@conn << "0\r\n\r\n"
|
291
252
|
end
|
292
|
-
|
253
|
+
|
293
254
|
def close
|
294
255
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
295
256
|
@conn.close
|
296
257
|
end
|
297
|
-
|
258
|
+
|
298
259
|
private
|
299
260
|
|
300
261
|
INTERNAL_HEADER_REGEXP = /^:/.freeze
|
@@ -311,13 +272,13 @@ module Tipi
|
|
311
272
|
lines = format_status_line(body, status, chunked)
|
312
273
|
headers.each do |k, v|
|
313
274
|
next if k =~ INTERNAL_HEADER_REGEXP
|
314
|
-
|
275
|
+
|
315
276
|
collect_header_lines(lines, k, v)
|
316
277
|
end
|
317
278
|
lines << CRLF
|
318
279
|
lines
|
319
280
|
end
|
320
|
-
|
281
|
+
|
321
282
|
def format_status_line(body, status, chunked)
|
322
283
|
if !body
|
323
284
|
empty_status_line(status)
|
@@ -325,7 +286,7 @@ module Tipi
|
|
325
286
|
with_body_status_line(status, body, chunked)
|
326
287
|
end
|
327
288
|
end
|
328
|
-
|
289
|
+
|
329
290
|
def empty_status_line(status)
|
330
291
|
if status == 204
|
331
292
|
+"HTTP/1.1 #{status}\r\n"
|
@@ -333,7 +294,7 @@ module Tipi
|
|
333
294
|
+"HTTP/1.1 #{status}\r\nContent-Length: 0\r\n"
|
334
295
|
end
|
335
296
|
end
|
336
|
-
|
297
|
+
|
337
298
|
def with_body_status_line(status, body, chunked)
|
338
299
|
if chunked
|
339
300
|
+"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
|
data/lib/tipi/http2_adapter.rb
CHANGED
@@ -3,18 +3,30 @@
|
|
3
3
|
require 'http/2'
|
4
4
|
require_relative './http2_stream'
|
5
5
|
|
6
|
+
# patch to fix bug in HTTP2::Stream
|
7
|
+
class HTTP2::Stream
|
8
|
+
def end_stream?(frame)
|
9
|
+
case frame[:type]
|
10
|
+
when :data, :headers, :continuation
|
11
|
+
frame[:flags]&.include?(:end_stream)
|
12
|
+
else false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
6
17
|
module Tipi
|
7
18
|
# HTTP2 server adapter
|
8
19
|
class HTTP2Adapter
|
9
|
-
def self.upgrade_each(socket, opts, headers, &block)
|
10
|
-
adapter = new(socket, opts, headers)
|
20
|
+
def self.upgrade_each(socket, opts, headers, body, &block)
|
21
|
+
adapter = new(socket, opts, headers, body)
|
11
22
|
adapter.each(&block)
|
12
23
|
end
|
13
|
-
|
14
|
-
def initialize(conn, opts, upgrade_headers = nil)
|
24
|
+
|
25
|
+
def initialize(conn, opts, upgrade_headers = nil, upgrade_body = nil)
|
15
26
|
@conn = conn
|
16
27
|
@opts = opts
|
17
28
|
@upgrade_headers = upgrade_headers
|
29
|
+
@upgrade_body = upgrade_body
|
18
30
|
@first = true
|
19
31
|
@rx = (upgrade_headers && upgrade_headers[':rx']) || 0
|
20
32
|
@tx = (upgrade_headers && upgrade_headers[':tx']) || 0
|
@@ -24,33 +36,34 @@ module Tipi
|
|
24
36
|
@interface.on(:frame, &method(:send_frame))
|
25
37
|
@streams = {}
|
26
38
|
end
|
27
|
-
|
39
|
+
|
28
40
|
def send_frame(data)
|
29
41
|
if @transfer_count_request
|
30
42
|
@transfer_count_request.tx_incr(data.bytesize)
|
31
43
|
end
|
32
44
|
@conn << data
|
45
|
+
rescue Polyphony::BaseException
|
46
|
+
raise
|
33
47
|
rescue Exception => e
|
34
48
|
@connection_fiber.transfer e
|
35
49
|
end
|
36
|
-
|
50
|
+
|
37
51
|
UPGRADE_MESSAGE = <<~HTTP.gsub("\n", "\r\n")
|
38
52
|
HTTP/1.1 101 Switching Protocols
|
39
53
|
Connection: Upgrade
|
40
54
|
Upgrade: h2c
|
41
|
-
|
55
|
+
|
42
56
|
HTTP
|
43
|
-
|
57
|
+
|
44
58
|
def upgrade
|
45
59
|
@conn << UPGRADE_MESSAGE
|
46
60
|
@tx += UPGRADE_MESSAGE.bytesize
|
47
61
|
settings = @upgrade_headers['http2-settings']
|
48
|
-
|
49
|
-
@interface.upgrade(settings, @upgrade_headers, '')
|
62
|
+
@interface.upgrade(settings, @upgrade_headers, @upgrade_body || '')
|
50
63
|
ensure
|
51
64
|
@upgrade_headers = nil
|
52
65
|
end
|
53
|
-
|
66
|
+
|
54
67
|
# Iterates over incoming requests
|
55
68
|
def each(&block)
|
56
69
|
@interface.on(:stream) { |stream| start_stream(stream, &block) }
|
@@ -60,7 +73,7 @@ module Tipi
|
|
60
73
|
@rx += data.bytesize
|
61
74
|
@interface << data
|
62
75
|
end
|
63
|
-
rescue SystemCallError, IOError
|
76
|
+
rescue SystemCallError, IOError, HTTP2::Error::Error
|
64
77
|
# ignore
|
65
78
|
ensure
|
66
79
|
finalize_client_loop
|
@@ -71,26 +84,26 @@ module Tipi
|
|
71
84
|
@rx = 0
|
72
85
|
count
|
73
86
|
end
|
74
|
-
|
87
|
+
|
75
88
|
def get_tx_count
|
76
89
|
count = @tx
|
77
90
|
@tx = 0
|
78
91
|
count
|
79
92
|
end
|
80
|
-
|
93
|
+
|
81
94
|
def start_stream(stream, &block)
|
82
95
|
stream = HTTP2StreamHandler.new(self, stream, @conn, @first, &block)
|
83
96
|
@first = nil if @first
|
84
97
|
@streams[stream] = true
|
85
98
|
end
|
86
|
-
|
99
|
+
|
87
100
|
def finalize_client_loop
|
88
101
|
@interface = nil
|
89
102
|
@streams.each_key(&:stop)
|
90
103
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
91
104
|
@conn.close
|
92
105
|
end
|
93
|
-
|
106
|
+
|
94
107
|
def close
|
95
108
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
96
109
|
@conn.close
|
data/lib/tipi/http2_stream.rb
CHANGED
@@ -8,15 +8,14 @@ module Tipi
|
|
8
8
|
class HTTP2StreamHandler
|
9
9
|
attr_accessor :__next__
|
10
10
|
attr_reader :conn
|
11
|
-
|
11
|
+
|
12
12
|
def initialize(adapter, stream, conn, first, &block)
|
13
13
|
@adapter = adapter
|
14
14
|
@stream = stream
|
15
15
|
@conn = conn
|
16
16
|
@first = first
|
17
17
|
@connection_fiber = Fiber.current
|
18
|
-
@stream_fiber = spin {
|
19
|
-
Thread.current.fiber_unschedule(@stream_fiber)
|
18
|
+
@stream_fiber = spin { run(&block) }
|
20
19
|
|
21
20
|
# Stream callbacks occur on the connection fiber (see HTTP2Adapter#each).
|
22
21
|
# The request handler is run on a separate fiber for each stream, allowing
|
@@ -33,20 +32,20 @@ module Tipi
|
|
33
32
|
stream.on(:data, &method(:on_data))
|
34
33
|
stream.on(:half_close, &method(:on_half_close))
|
35
34
|
end
|
36
|
-
|
37
|
-
def
|
35
|
+
|
36
|
+
def run(&block)
|
37
|
+
request = receive
|
38
38
|
error = nil
|
39
39
|
block.(request)
|
40
40
|
@connection_fiber.schedule
|
41
|
-
rescue Polyphony::
|
42
|
-
|
41
|
+
rescue Polyphony::BaseException
|
42
|
+
raise
|
43
43
|
rescue Exception => e
|
44
44
|
error = e
|
45
45
|
ensure
|
46
|
-
@done = true
|
47
46
|
@connection_fiber.schedule error
|
48
47
|
end
|
49
|
-
|
48
|
+
|
50
49
|
def on_headers(headers)
|
51
50
|
@request = Qeweney::Request.new(headers.to_h, self)
|
52
51
|
@request.rx_incr(@adapter.get_rx_count)
|
@@ -55,31 +54,21 @@ module Tipi
|
|
55
54
|
@request.headers[':first'] = true
|
56
55
|
@first = false
|
57
56
|
end
|
58
|
-
@stream_fiber
|
57
|
+
@stream_fiber << @request
|
59
58
|
end
|
60
59
|
|
61
60
|
def on_data(data)
|
62
61
|
data = data.to_s # chunks might be wrapped in a HTTP2::Buffer
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
else
|
67
|
-
@request.buffer_body_chunk(data)
|
68
|
-
end
|
62
|
+
|
63
|
+
(@buffered_chunks ||= []) << data
|
64
|
+
@get_body_chunk_fiber&.schedule
|
69
65
|
end
|
70
66
|
|
71
67
|
def on_half_close
|
72
|
-
|
73
|
-
|
74
|
-
@stream_fiber.schedule
|
75
|
-
elsif @waiting_for_half_close
|
76
|
-
@waiting_for_half_close = nil
|
77
|
-
@stream_fiber.schedule
|
78
|
-
else
|
79
|
-
@request.complete!
|
80
|
-
end
|
68
|
+
@get_body_chunk_fiber&.schedule
|
69
|
+
@complete = true
|
81
70
|
end
|
82
|
-
|
71
|
+
|
83
72
|
def protocol
|
84
73
|
'h2'
|
85
74
|
end
|
@@ -90,33 +79,40 @@ module Tipi
|
|
90
79
|
ensure
|
91
80
|
@adapter.unset_request_for_transfer_count(request)
|
92
81
|
end
|
93
|
-
|
94
|
-
def get_body_chunk(request)
|
95
|
-
|
96
|
-
return
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
# resumed
|
82
|
+
|
83
|
+
def get_body_chunk(request, buffered_only = false)
|
84
|
+
@buffered_chunks ||= []
|
85
|
+
return @buffered_chunks.shift unless @buffered_chunks.empty?
|
86
|
+
return nil if @complete
|
87
|
+
|
88
|
+
begin
|
89
|
+
@get_body_chunk_fiber = Fiber.current
|
102
90
|
suspend
|
91
|
+
ensure
|
92
|
+
@get_body_chunk_fiber = nil
|
103
93
|
end
|
104
|
-
|
105
|
-
@waiting_for_body_chunk = nil
|
94
|
+
@buffered_chunks.shift
|
106
95
|
end
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
return if @
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
96
|
+
|
97
|
+
def get_body(request)
|
98
|
+
@buffered_chunks ||= []
|
99
|
+
return @buffered_chunks.join if @complete
|
100
|
+
|
101
|
+
while !@complete
|
102
|
+
begin
|
103
|
+
@get_body_chunk_fiber = Fiber.current
|
104
|
+
suspend
|
105
|
+
ensure
|
106
|
+
@get_body_chunk_fiber = nil
|
107
|
+
end
|
115
108
|
end
|
116
|
-
|
117
|
-
|
109
|
+
@buffered_chunks.join
|
110
|
+
end
|
111
|
+
|
112
|
+
def complete?(request)
|
113
|
+
@complete
|
118
114
|
end
|
119
|
-
|
115
|
+
|
120
116
|
# response API
|
121
117
|
def respond(request, chunk, headers)
|
122
118
|
headers[':status'] ||= Qeweney::Status::OK
|
@@ -153,10 +149,10 @@ module Tipi
|
|
153
149
|
end
|
154
150
|
end
|
155
151
|
end
|
156
|
-
|
152
|
+
|
157
153
|
def send_headers(request, headers, empty_response: false)
|
158
154
|
return if @headers_sent
|
159
|
-
|
155
|
+
|
160
156
|
headers[':status'] ||= (empty_response ? Qeweney::Status::NO_CONTENT : Qeweney::Status::OK).to_s
|
161
157
|
with_transfer_count(request) do
|
162
158
|
@stream.headers(transform_headers(headers), end_stream: false)
|
@@ -165,10 +161,10 @@ module Tipi
|
|
165
161
|
rescue HTTP2::Error::StreamClosed
|
166
162
|
# ignore
|
167
163
|
end
|
168
|
-
|
164
|
+
|
169
165
|
def send_chunk(request, chunk, done: false)
|
170
166
|
send_headers({}, false) unless @headers_sent
|
171
|
-
|
167
|
+
|
172
168
|
if chunk
|
173
169
|
with_transfer_count(request) do
|
174
170
|
@stream.data(chunk, end_stream: done)
|
@@ -179,7 +175,7 @@ module Tipi
|
|
179
175
|
rescue HTTP2::Error::StreamClosed
|
180
176
|
# ignore
|
181
177
|
end
|
182
|
-
|
178
|
+
|
183
179
|
def finish(request)
|
184
180
|
if @headers_sent
|
185
181
|
@stream.close
|
@@ -192,10 +188,10 @@ module Tipi
|
|
192
188
|
rescue HTTP2::Error::StreamClosed
|
193
189
|
# ignore
|
194
190
|
end
|
195
|
-
|
191
|
+
|
196
192
|
def stop
|
197
|
-
return if @
|
198
|
-
|
193
|
+
return if @complete
|
194
|
+
|
199
195
|
@stream.close
|
200
196
|
@stream_fiber.schedule(Polyphony::MoveOn.new)
|
201
197
|
end
|