tipi 0.36 → 0.39
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/workflows/test.yml +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +49 -28
- data/Gemfile.lock +5 -5
- data/TODO.md +79 -5
- data/df/sample_agent.rb +1 -1
- data/df/server.rb +46 -4
- data/e +0 -0
- data/examples/http_request_ws_server.rb +2 -1
- data/examples/http_server.rb +11 -3
- data/examples/http_server_forked.rb +2 -0
- data/examples/http_server_throttled.rb +3 -2
- data/examples/https_server.rb +10 -1
- data/examples/https_wss_server.rb +2 -1
- data/examples/rack_server.rb +5 -0
- data/examples/rack_server_https.rb +1 -1
- data/examples/rack_server_https_forked.rb +4 -3
- data/examples/routing_server.rb +5 -4
- data/lib/tipi/digital_fabric/agent.rb +16 -13
- data/lib/tipi/digital_fabric/agent_proxy.rb +79 -27
- data/lib/tipi/digital_fabric/protocol.rb +71 -14
- data/lib/tipi/digital_fabric/request_adapter.rb +7 -7
- data/lib/tipi/digital_fabric/service.rb +10 -8
- data/lib/tipi/http1_adapter.rb +63 -35
- data/lib/tipi/http2_adapter.rb +35 -4
- data/lib/tipi/http2_stream.rb +64 -21
- data/lib/tipi/version.rb +1 -1
- data/lib/tipi/websocket.rb +9 -22
- data/test/test_http_server.rb +22 -37
- data/test/test_request.rb +4 -4
- data/tipi.gemspec +2 -2
- metadata +7 -6
|
@@ -6,40 +6,40 @@ module DigitalFabric
|
|
|
6
6
|
class RequestAdapter
|
|
7
7
|
def initialize(agent, msg)
|
|
8
8
|
@agent = agent
|
|
9
|
-
@id = msg[
|
|
9
|
+
@id = msg[Protocol::Attribute::ID]
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def protocol
|
|
13
13
|
'df'
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def get_body_chunk
|
|
16
|
+
def get_body_chunk(request)
|
|
17
17
|
@agent.get_http_request_body(@id, 1)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def consume_request
|
|
20
|
+
def consume_request(request)
|
|
21
21
|
@agent.get_http_request_body(@id, nil)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def respond(body, headers)
|
|
24
|
+
def respond(request, body, headers)
|
|
25
25
|
@agent.send_df_message(
|
|
26
26
|
Protocol.http_response(@id, body, headers, true)
|
|
27
27
|
)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def send_headers(headers, opts = {})
|
|
30
|
+
def send_headers(request, headers, opts = {})
|
|
31
31
|
@agent.send_df_message(
|
|
32
32
|
Protocol.http_response(@id, nil, headers, false)
|
|
33
33
|
)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def send_chunk(body, done: )
|
|
36
|
+
def send_chunk(request, body, done: )
|
|
37
37
|
@agent.send_df_message(
|
|
38
38
|
Protocol.http_response(@id, body, nil, done)
|
|
39
39
|
)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def finish
|
|
42
|
+
def finish(request)
|
|
43
43
|
@agent.send_df_message(
|
|
44
44
|
Protocol.http_response(@id, nil, nil, true)
|
|
45
45
|
)
|
|
@@ -99,10 +99,12 @@ module DigitalFabric
|
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
agent.http_request(req)
|
|
102
|
-
rescue IOError, SystemCallError
|
|
102
|
+
rescue IOError, SystemCallError, HTTP2::Error::StreamClosed
|
|
103
103
|
@counters[:errors] += 1
|
|
104
104
|
rescue => e
|
|
105
105
|
@counters[:errors] += 1
|
|
106
|
+
puts '*' * 40
|
|
107
|
+
p req
|
|
106
108
|
p e
|
|
107
109
|
puts e.backtrace.join("\n")
|
|
108
110
|
req.respond(e.inspect, ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
|
|
@@ -115,7 +117,7 @@ module DigitalFabric
|
|
|
115
117
|
req.headers['x-request-id'] = SecureRandom.uuid
|
|
116
118
|
conn = req.adapter.conn
|
|
117
119
|
req.headers['x-forwarded-for'] = conn.peeraddr(false)[2]
|
|
118
|
-
req.headers['x-forwarded-proto']
|
|
120
|
+
req.headers['x-forwarded-proto'] ||= conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
|
|
119
121
|
end
|
|
120
122
|
|
|
121
123
|
def upgrade_request(req)
|
|
@@ -172,7 +174,7 @@ module DigitalFabric
|
|
|
172
174
|
def find_agent(req)
|
|
173
175
|
compile_agent_routes if @routing_changed
|
|
174
176
|
|
|
175
|
-
host = req.headers['host'] || INVALID_HOST
|
|
177
|
+
host = req.headers[':authority'] || req.headers['host'] || INVALID_HOST
|
|
176
178
|
path = req.headers[':path']
|
|
177
179
|
|
|
178
180
|
route = @route_keys.find do |route|
|
|
@@ -180,11 +182,11 @@ module DigitalFabric
|
|
|
180
182
|
end
|
|
181
183
|
return @routes[route] if route
|
|
182
184
|
|
|
183
|
-
# search for a known route for an agent that recently unmounted
|
|
184
|
-
route, wait_list = @waiting_lists.find do |route, _|
|
|
185
|
-
|
|
186
|
-
end
|
|
187
|
-
return wait_for_agent(wait_list) if route
|
|
185
|
+
# # search for a known route for an agent that recently unmounted
|
|
186
|
+
# route, wait_list = @waiting_lists.find do |route, _|
|
|
187
|
+
# (host == route[:host]) || (path =~ route[:path_regexp])
|
|
188
|
+
# end
|
|
189
|
+
# return wait_for_agent(wait_list) if route
|
|
188
190
|
|
|
189
191
|
nil
|
|
190
192
|
end
|
data/lib/tipi/http1_adapter.rb
CHANGED
|
@@ -29,8 +29,10 @@ module Tipi
|
|
|
29
29
|
|
|
30
30
|
# return [Boolean] true if client loop should stop
|
|
31
31
|
def handle_incoming_data(data, &block)
|
|
32
|
+
rx = data.bytesize
|
|
32
33
|
@parser << data
|
|
33
34
|
while (request = @requests_head)
|
|
35
|
+
request.headers[':rx'] = rx
|
|
34
36
|
if @first
|
|
35
37
|
request.headers[':first'] = true
|
|
36
38
|
@first = nil
|
|
@@ -53,10 +55,11 @@ module Tipi
|
|
|
53
55
|
|
|
54
56
|
# Reads a body chunk for the current request. Transfers control to the parse
|
|
55
57
|
# loop, and resumes once the parse_loop has fired the on_body callback
|
|
56
|
-
def get_body_chunk
|
|
58
|
+
def get_body_chunk(request)
|
|
57
59
|
@waiting_for_body_chunk = true
|
|
58
60
|
@next_chunk = nil
|
|
59
61
|
while !@requests_tail.complete? && (data = @conn.readpartial(8192))
|
|
62
|
+
request.rx_incr(data.bytesize)
|
|
60
63
|
@parser << data
|
|
61
64
|
return @next_chunk if @next_chunk
|
|
62
65
|
|
|
@@ -70,9 +73,10 @@ module Tipi
|
|
|
70
73
|
# Waits for the current request to complete. Transfers control to the parse
|
|
71
74
|
# loop, and resumes once the parse_loop has fired the on_message_complete
|
|
72
75
|
# callback
|
|
73
|
-
def consume_request
|
|
76
|
+
def consume_request(request)
|
|
74
77
|
request = @requests_head
|
|
75
78
|
@conn.recv_loop do |data|
|
|
79
|
+
request.rx_incr(data.bytesize)
|
|
76
80
|
@parser << data
|
|
77
81
|
return if request.complete?
|
|
78
82
|
end
|
|
@@ -87,6 +91,9 @@ module Tipi
|
|
|
87
91
|
headers = normalize_headers(headers)
|
|
88
92
|
headers[':path'] = @parser.request_url
|
|
89
93
|
headers[':method'] = @parser.http_method.downcase
|
|
94
|
+
scheme = (proto = headers['x-forwarded-proto']) ?
|
|
95
|
+
proto.downcase : scheme_from_connection
|
|
96
|
+
headers[':scheme'] = scheme
|
|
90
97
|
queue_request(Qeweney::Request.new(headers, self))
|
|
91
98
|
end
|
|
92
99
|
|
|
@@ -131,6 +138,14 @@ module Tipi
|
|
|
131
138
|
# protocols, notably WebSocket, can be specified by passing a hash to the
|
|
132
139
|
# :upgrade option when starting a server:
|
|
133
140
|
#
|
|
141
|
+
# def ws_handler(conn)
|
|
142
|
+
# conn << 'hi'
|
|
143
|
+
# msg = conn.recv
|
|
144
|
+
# conn << "You said #{msg}"
|
|
145
|
+
# conn << 'bye'
|
|
146
|
+
# conn.close
|
|
147
|
+
# end
|
|
148
|
+
#
|
|
134
149
|
# opts = {
|
|
135
150
|
# upgrade: {
|
|
136
151
|
# websocket: Tipi::Websocket.handler(&method(:ws_handler))
|
|
@@ -154,7 +169,7 @@ module Tipi
|
|
|
154
169
|
|
|
155
170
|
def upgrade_with_handler(handler, headers)
|
|
156
171
|
@parser = @requests_head = @requests_tail = nil
|
|
157
|
-
handler.(
|
|
172
|
+
handler.(self, headers)
|
|
158
173
|
true
|
|
159
174
|
end
|
|
160
175
|
|
|
@@ -173,6 +188,14 @@ module Tipi
|
|
|
173
188
|
':authority' => headers['host']
|
|
174
189
|
)
|
|
175
190
|
end
|
|
191
|
+
|
|
192
|
+
def websocket_connection(request)
|
|
193
|
+
Tipi::Websocket.new(@conn, request.headers)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def scheme_from_connection
|
|
197
|
+
@conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
|
|
198
|
+
end
|
|
176
199
|
|
|
177
200
|
# response API
|
|
178
201
|
|
|
@@ -181,53 +204,55 @@ module Tipi
|
|
|
181
204
|
|
|
182
205
|
# Sends response including headers and body. Waits for the request to complete
|
|
183
206
|
# if not yet completed. The body is sent using chunked transfer encoding.
|
|
207
|
+
# @param request [Qeweney::Request] HTTP request
|
|
184
208
|
# @param body [String] response body
|
|
185
209
|
# @param headers
|
|
186
|
-
def respond(body, headers)
|
|
187
|
-
consume_request if @parsing
|
|
188
|
-
|
|
210
|
+
def respond(request, body, headers)
|
|
211
|
+
consume_request(request) if @parsing
|
|
212
|
+
formatted_headers = format_headers(headers, body, false)
|
|
213
|
+
request.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
|
|
189
214
|
if body
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
data << body.bytesize.to_s(16) << CRLF << body << CRLF_ZERO_CRLF_CRLF
|
|
194
|
-
end
|
|
215
|
+
@conn.write(formatted_headers, body)
|
|
216
|
+
else
|
|
217
|
+
@conn.write(formatted_headers)
|
|
195
218
|
end
|
|
196
|
-
@conn.write(data.join)
|
|
197
219
|
end
|
|
198
220
|
|
|
199
|
-
DEFAULT_HEADERS_OPTS = {
|
|
200
|
-
empty_response: false,
|
|
201
|
-
consume_request: true
|
|
202
|
-
}.freeze
|
|
203
|
-
|
|
204
221
|
# Sends response headers. If empty_response is truthy, the response status
|
|
205
222
|
# code will default to 204, otherwise to 200.
|
|
223
|
+
# @param request [Qeweney::Request] HTTP request
|
|
206
224
|
# @param headers [Hash] response headers
|
|
207
225
|
# @param empty_response [boolean] whether a response body will be sent
|
|
226
|
+
# @param chunked [boolean] whether to use chunked transfer encoding
|
|
208
227
|
# @return [void]
|
|
209
|
-
def send_headers(headers,
|
|
210
|
-
|
|
211
|
-
|
|
228
|
+
def send_headers(request, headers, empty_response: false, chunked: true)
|
|
229
|
+
formatted_headers = format_headers(headers, !empty_response, @parser.http_minor == 1 && chunked)
|
|
230
|
+
request.tx_incr(formatted_headers.bytesize)
|
|
231
|
+
@conn.write(formatted_headers)
|
|
212
232
|
end
|
|
213
233
|
|
|
214
234
|
# Sends a response body chunk. If no headers were sent, default headers are
|
|
215
235
|
# sent using #send_headers. if the done option is true(thy), an empty chunk
|
|
216
236
|
# will be sent to signal response completion to the client.
|
|
237
|
+
# @param request [Qeweney::Request] HTTP request
|
|
217
238
|
# @param chunk [String] response body chunk
|
|
218
239
|
# @param done [boolean] whether the response is completed
|
|
219
240
|
# @return [void]
|
|
220
|
-
def send_chunk(chunk, done: false)
|
|
221
|
-
data =
|
|
241
|
+
def send_chunk(request, chunk, done: false)
|
|
242
|
+
data = +''
|
|
222
243
|
data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" if chunk
|
|
223
244
|
data << "0\r\n\r\n" if done
|
|
224
|
-
|
|
245
|
+
return if data.empty?
|
|
246
|
+
|
|
247
|
+
request.tx_incr(data.bytesize)
|
|
248
|
+
@conn.write(data)
|
|
225
249
|
end
|
|
226
250
|
|
|
227
251
|
# Finishes the response to the current request. If no headers were sent,
|
|
228
252
|
# default headers are sent using #send_headers.
|
|
229
253
|
# @return [void]
|
|
230
|
-
def finish
|
|
254
|
+
def finish(request)
|
|
255
|
+
request.tx_incr(5)
|
|
231
256
|
@conn << "0\r\n\r\n"
|
|
232
257
|
end
|
|
233
258
|
|
|
@@ -237,17 +262,20 @@ module Tipi
|
|
|
237
262
|
|
|
238
263
|
private
|
|
239
264
|
|
|
265
|
+
INTERNAL_HEADER_REGEXP = /^:/.freeze
|
|
266
|
+
|
|
240
267
|
# Formats response headers into an array. If empty_response is true(thy),
|
|
241
268
|
# the response status code will default to 204, otherwise to 200.
|
|
242
269
|
# @param headers [Hash] response headers
|
|
243
|
-
# @param
|
|
270
|
+
# @param body [boolean] whether a response body will be sent
|
|
271
|
+
# @param chunked [boolean] whether to use chunked transfer encoding
|
|
244
272
|
# @return [String] formatted response headers
|
|
245
|
-
def format_headers(headers, body)
|
|
273
|
+
def format_headers(headers, body, chunked)
|
|
246
274
|
status = headers[':status']
|
|
247
275
|
status ||= (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
|
|
248
|
-
lines =
|
|
276
|
+
lines = format_status_line(body, status, chunked)
|
|
249
277
|
headers.each do |k, v|
|
|
250
|
-
next if k =~
|
|
278
|
+
next if k =~ INTERNAL_HEADER_REGEXP
|
|
251
279
|
|
|
252
280
|
collect_header_lines(lines, k, v)
|
|
253
281
|
end
|
|
@@ -255,11 +283,11 @@ module Tipi
|
|
|
255
283
|
lines
|
|
256
284
|
end
|
|
257
285
|
|
|
258
|
-
def format_status_line(body, status)
|
|
286
|
+
def format_status_line(body, status, chunked)
|
|
259
287
|
if !body
|
|
260
288
|
empty_status_line(status)
|
|
261
289
|
else
|
|
262
|
-
with_body_status_line(status, body)
|
|
290
|
+
with_body_status_line(status, body, chunked)
|
|
263
291
|
end
|
|
264
292
|
end
|
|
265
293
|
|
|
@@ -271,17 +299,17 @@ module Tipi
|
|
|
271
299
|
end
|
|
272
300
|
end
|
|
273
301
|
|
|
274
|
-
def with_body_status_line(status, body)
|
|
275
|
-
if
|
|
276
|
-
+"HTTP/1.0 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
|
|
277
|
-
else
|
|
302
|
+
def with_body_status_line(status, body, chunked)
|
|
303
|
+
if chunked
|
|
278
304
|
+"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
|
|
305
|
+
else
|
|
306
|
+
+"HTTP/1.1 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
|
|
279
307
|
end
|
|
280
308
|
end
|
|
281
309
|
|
|
282
310
|
def collect_header_lines(lines, key, value)
|
|
283
311
|
if value.is_a?(Array)
|
|
284
|
-
value.inject(lines) { |
|
|
312
|
+
value.inject(lines) { |_, item| lines << "#{key}: #{item}\r\n" }
|
|
285
313
|
else
|
|
286
314
|
lines << "#{key}: #{value}\r\n"
|
|
287
315
|
end
|
data/lib/tipi/http2_adapter.rb
CHANGED
|
@@ -16,7 +16,9 @@ module Tipi
|
|
|
16
16
|
@opts = opts
|
|
17
17
|
@upgrade_headers = upgrade_headers
|
|
18
18
|
@first = true
|
|
19
|
-
|
|
19
|
+
@rx = (upgrade_headers && upgrade_headers[':rx']) || 0
|
|
20
|
+
@tx = (upgrade_headers && upgrade_headers[':tx']) || 0
|
|
21
|
+
|
|
20
22
|
@interface = ::HTTP2::Server.new
|
|
21
23
|
@connection_fiber = Fiber.current
|
|
22
24
|
@interface.on(:frame, &method(:send_frame))
|
|
@@ -24,6 +26,9 @@ module Tipi
|
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def send_frame(data)
|
|
29
|
+
if @transfer_count_request
|
|
30
|
+
@transfer_count_request.tx_incr(data.bytesize)
|
|
31
|
+
end
|
|
27
32
|
@conn << data
|
|
28
33
|
rescue Exception => e
|
|
29
34
|
@connection_fiber.transfer e
|
|
@@ -38,6 +43,7 @@ module Tipi
|
|
|
38
43
|
|
|
39
44
|
def upgrade
|
|
40
45
|
@conn << UPGRADE_MESSAGE
|
|
46
|
+
@tx += UPGRADE_MESSAGE.bytesize
|
|
41
47
|
settings = @upgrade_headers['http2-settings']
|
|
42
48
|
Fiber.current.schedule(nil)
|
|
43
49
|
@interface.upgrade(settings, @upgrade_headers, '')
|
|
@@ -49,16 +55,31 @@ module Tipi
|
|
|
49
55
|
def each(&block)
|
|
50
56
|
@interface.on(:stream) { |stream| start_stream(stream, &block) }
|
|
51
57
|
upgrade if @upgrade_headers
|
|
52
|
-
|
|
53
|
-
@conn.recv_loop
|
|
58
|
+
|
|
59
|
+
@conn.recv_loop do |data|
|
|
60
|
+
@rx += data.bytesize
|
|
61
|
+
@interface << data
|
|
62
|
+
end
|
|
54
63
|
rescue SystemCallError, IOError
|
|
55
64
|
# ignore
|
|
56
65
|
ensure
|
|
57
66
|
finalize_client_loop
|
|
58
67
|
end
|
|
68
|
+
|
|
69
|
+
def get_rx_count
|
|
70
|
+
count = @rx
|
|
71
|
+
@rx = 0
|
|
72
|
+
count
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def get_tx_count
|
|
76
|
+
count = @tx
|
|
77
|
+
@tx = 0
|
|
78
|
+
count
|
|
79
|
+
end
|
|
59
80
|
|
|
60
81
|
def start_stream(stream, &block)
|
|
61
|
-
stream = HTTP2StreamHandler.new(stream, @conn, @first, &block)
|
|
82
|
+
stream = HTTP2StreamHandler.new(self, stream, @conn, @first, &block)
|
|
62
83
|
@first = nil if @first
|
|
63
84
|
@streams[stream] = true
|
|
64
85
|
end
|
|
@@ -72,5 +93,15 @@ module Tipi
|
|
|
72
93
|
def close
|
|
73
94
|
@conn.close
|
|
74
95
|
end
|
|
96
|
+
|
|
97
|
+
def set_request_for_transfer_count(request)
|
|
98
|
+
@transfer_count_request = request
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def unset_request_for_transfer_count(request)
|
|
102
|
+
return unless @transfer_count_request == request
|
|
103
|
+
|
|
104
|
+
@transfer_count_request = nil
|
|
105
|
+
end
|
|
75
106
|
end
|
|
76
107
|
end
|
data/lib/tipi/http2_stream.rb
CHANGED
|
@@ -9,13 +9,15 @@ module Tipi
|
|
|
9
9
|
attr_accessor :__next__
|
|
10
10
|
attr_reader :conn
|
|
11
11
|
|
|
12
|
-
def initialize(stream, conn, first, &block)
|
|
12
|
+
def initialize(adapter, stream, conn, first, &block)
|
|
13
|
+
@adapter = adapter
|
|
13
14
|
@stream = stream
|
|
14
15
|
@conn = conn
|
|
15
16
|
@first = first
|
|
16
17
|
@connection_fiber = Fiber.current
|
|
17
18
|
@stream_fiber = spin { |req| handle_request(req, &block) }
|
|
18
|
-
|
|
19
|
+
Thread.current.fiber_unschedule(@stream_fiber)
|
|
20
|
+
|
|
19
21
|
# Stream callbacks occur on the connection fiber (see HTTP2Adapter#each).
|
|
20
22
|
# The request handler is run on a separate fiber for each stream, allowing
|
|
21
23
|
# concurrent handling of incoming requests on the same HTTP/2 connection.
|
|
@@ -47,14 +49,17 @@ module Tipi
|
|
|
47
49
|
|
|
48
50
|
def on_headers(headers)
|
|
49
51
|
@request = Qeweney::Request.new(headers.to_h, self)
|
|
52
|
+
@request.rx_incr(@adapter.get_rx_count)
|
|
53
|
+
@request.tx_incr(@adapter.get_tx_count)
|
|
50
54
|
if @first
|
|
51
55
|
@request.headers[':first'] = true
|
|
52
56
|
@first = false
|
|
53
57
|
end
|
|
54
58
|
@stream_fiber.schedule @request
|
|
55
59
|
end
|
|
56
|
-
|
|
60
|
+
|
|
57
61
|
def on_data(data)
|
|
62
|
+
data = data.to_s # chunks might be wrapped in a HTTP2::Buffer
|
|
58
63
|
if @waiting_for_body_chunk
|
|
59
64
|
@waiting_for_body_chunk = nil
|
|
60
65
|
@stream_fiber.schedule data
|
|
@@ -62,7 +67,7 @@ module Tipi
|
|
|
62
67
|
@request.buffer_body_chunk(data)
|
|
63
68
|
end
|
|
64
69
|
end
|
|
65
|
-
|
|
70
|
+
|
|
66
71
|
def on_half_close
|
|
67
72
|
if @waiting_for_body_chunk
|
|
68
73
|
@waiting_for_body_chunk = nil
|
|
@@ -78,62 +83,100 @@ module Tipi
|
|
|
78
83
|
def protocol
|
|
79
84
|
'h2'
|
|
80
85
|
end
|
|
86
|
+
|
|
87
|
+
def with_transfer_count(request)
|
|
88
|
+
@adapter.set_request_for_transfer_count(request)
|
|
89
|
+
yield
|
|
90
|
+
ensure
|
|
91
|
+
@adapter.unset_request_for_transfer_count(request)
|
|
92
|
+
end
|
|
81
93
|
|
|
82
|
-
def get_body_chunk
|
|
94
|
+
def get_body_chunk(request)
|
|
83
95
|
# called in the context of the stream fiber
|
|
84
96
|
return nil if @request.complete?
|
|
85
97
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
with_transfer_count(request) do
|
|
99
|
+
@waiting_for_body_chunk = true
|
|
100
|
+
# the chunk (or an exception) will be returned once the stream fiber is
|
|
101
|
+
# resumed
|
|
102
|
+
suspend
|
|
103
|
+
end
|
|
90
104
|
ensure
|
|
91
105
|
@waiting_for_body_chunk = nil
|
|
92
106
|
end
|
|
93
107
|
|
|
94
108
|
# Wait for request to finish
|
|
95
|
-
def consume_request
|
|
109
|
+
def consume_request(request)
|
|
96
110
|
return if @request.complete?
|
|
97
111
|
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
with_transfer_count(request) do
|
|
113
|
+
@waiting_for_half_close = true
|
|
114
|
+
suspend
|
|
115
|
+
end
|
|
100
116
|
ensure
|
|
101
117
|
@waiting_for_half_close = nil
|
|
102
118
|
end
|
|
103
119
|
|
|
104
120
|
# response API
|
|
105
|
-
def respond(chunk, headers)
|
|
121
|
+
def respond(request, chunk, headers)
|
|
106
122
|
headers[':status'] ||= Qeweney::Status::OK
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
headers[':status'] = headers[':status'].to_s
|
|
124
|
+
with_transfer_count(request) do
|
|
125
|
+
@stream.headers(transform_headers(headers))
|
|
126
|
+
@stream.data(chunk || '')
|
|
127
|
+
end
|
|
109
128
|
@headers_sent = true
|
|
129
|
+
rescue HTTP2::Error::StreamClosed
|
|
130
|
+
# ignore
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def transform_headers(headers)
|
|
134
|
+
headers.each_with_object([]) do |(k, v), a|
|
|
135
|
+
if v.is_a?(Array)
|
|
136
|
+
v.each { |vv| a << [k, vv.to_s] }
|
|
137
|
+
else
|
|
138
|
+
a << [k, v.to_s]
|
|
139
|
+
end
|
|
140
|
+
end
|
|
110
141
|
end
|
|
111
142
|
|
|
112
|
-
def send_headers(headers, empty_response
|
|
143
|
+
def send_headers(request, headers, empty_response: false)
|
|
113
144
|
return if @headers_sent
|
|
114
145
|
|
|
115
146
|
headers[':status'] ||= (empty_response ? Qeweney::Status::NO_CONTENT : Qeweney::Status::OK).to_s
|
|
116
|
-
|
|
147
|
+
with_transfer_count(request) do
|
|
148
|
+
@stream.headers(transform_headers(headers), end_stream: false)
|
|
149
|
+
end
|
|
117
150
|
@headers_sent = true
|
|
151
|
+
rescue HTTP2::Error::StreamClosed
|
|
152
|
+
# ignore
|
|
118
153
|
end
|
|
119
154
|
|
|
120
|
-
def send_chunk(chunk, done: false)
|
|
155
|
+
def send_chunk(request, chunk, done: false)
|
|
121
156
|
send_headers({}, false) unless @headers_sent
|
|
122
157
|
|
|
123
158
|
if chunk
|
|
124
|
-
|
|
159
|
+
with_transfer_count(request) do
|
|
160
|
+
@stream.data(chunk, end_stream: done)
|
|
161
|
+
end
|
|
125
162
|
elsif done
|
|
126
163
|
@stream.close
|
|
127
164
|
end
|
|
165
|
+
rescue HTTP2::Error::StreamClosed
|
|
166
|
+
# ignore
|
|
128
167
|
end
|
|
129
168
|
|
|
130
|
-
def finish
|
|
169
|
+
def finish(request)
|
|
131
170
|
if @headers_sent
|
|
132
171
|
@stream.close
|
|
133
172
|
else
|
|
134
173
|
headers[':status'] ||= Qeweney::Status::NO_CONTENT
|
|
135
|
-
|
|
174
|
+
with_transfer_count(request) do
|
|
175
|
+
@stream.headers(transform_headers(headers), end_stream: true)
|
|
176
|
+
end
|
|
136
177
|
end
|
|
178
|
+
rescue HTTP2::Error::StreamClosed
|
|
179
|
+
# ignore
|
|
137
180
|
end
|
|
138
181
|
|
|
139
182
|
def stop
|