tipi 0.38 → 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 +9 -0
- data/Gemfile.lock +5 -5
- data/TODO.md +77 -1
- data/df/sample_agent.rb +1 -1
- data/df/server.rb +46 -4
- data/examples/http_server.rb +1 -1
- data/examples/http_server_forked.rb +2 -0
- data/examples/http_server_throttled.rb +3 -2
- data/examples/https_server.rb +3 -4
- 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 -32
- 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 +9 -7
- data/lib/tipi/http1_adapter.rb +35 -26
- data/lib/tipi/http2_adapter.rb +35 -4
- data/lib/tipi/http2_stream.rb +63 -20
- data/lib/tipi/version.rb +1 -1
- data/tipi.gemspec +2 -2
- metadata +7 -7
@@ -31,6 +31,7 @@ module DigitalFabric
|
|
31
31
|
def run
|
32
32
|
@fiber = Fiber.current
|
33
33
|
@service.mount(route, self)
|
34
|
+
@mounted = true
|
34
35
|
keep_alive_timer = spin_loop(interval: 5) { keep_alive }
|
35
36
|
process_incoming_messages(false)
|
36
37
|
rescue GracefulShutdown
|
@@ -38,7 +39,7 @@ module DigitalFabric
|
|
38
39
|
process_incoming_messages(true)
|
39
40
|
ensure
|
40
41
|
keep_alive_timer&.stop
|
41
|
-
|
42
|
+
unmount
|
42
43
|
end
|
43
44
|
|
44
45
|
def process_incoming_messages(shutdown = false)
|
@@ -48,7 +49,15 @@ module DigitalFabric
|
|
48
49
|
recv_df_message(msg)
|
49
50
|
return if shutdown && @requests.empty?
|
50
51
|
end
|
51
|
-
rescue TimeoutError, IOError
|
52
|
+
rescue TimeoutError, IOError, SystemCallError
|
53
|
+
# ignore and just return in order to terminate the proxy
|
54
|
+
end
|
55
|
+
|
56
|
+
def unmount
|
57
|
+
return unless @mounted
|
58
|
+
|
59
|
+
@service.unmount(self)
|
60
|
+
@mounted = nil
|
52
61
|
end
|
53
62
|
|
54
63
|
def shutdown
|
@@ -82,9 +91,16 @@ module DigitalFabric
|
|
82
91
|
|
83
92
|
def recv_df_message(message)
|
84
93
|
@last_recv = Time.now
|
85
|
-
|
94
|
+
# puts "<<< #{message.inspect}"
|
86
95
|
|
87
|
-
|
96
|
+
case message[Protocol::Attribute::KIND]
|
97
|
+
when Protocol::PING
|
98
|
+
return
|
99
|
+
when Protocol::UNMOUNT
|
100
|
+
return unmount
|
101
|
+
end
|
102
|
+
|
103
|
+
handler = @requests[message[Protocol::Attribute::ID]]
|
88
104
|
if !handler
|
89
105
|
# puts "Unknown request id in #{message}"
|
90
106
|
return
|
@@ -94,6 +110,8 @@ module DigitalFabric
|
|
94
110
|
end
|
95
111
|
|
96
112
|
def send_df_message(message)
|
113
|
+
# puts ">>> #{message.inspect}" unless message[Protocol::Attribute::KIND] == Protocol::PING
|
114
|
+
|
97
115
|
@last_send = Time.now
|
98
116
|
@conn << message.to_msgpack
|
99
117
|
end
|
@@ -130,46 +148,51 @@ module DigitalFabric
|
|
130
148
|
t1 = Time.now
|
131
149
|
@service.record_latency_measurement(t1 - t0)
|
132
150
|
end
|
133
|
-
|
151
|
+
kind = message[Protocol::Attribute::KIND]
|
152
|
+
attributes = message[Protocol::Attribute::HttpRequest::HEADERS..-1]
|
153
|
+
return if http_request_message(id, req, kind, attributes)
|
134
154
|
end
|
135
155
|
end
|
136
156
|
rescue => e
|
137
|
-
|
157
|
+
p "Internal server error: #{e.inspect}"
|
158
|
+
puts e.backtrace.join("\n")
|
159
|
+
http_request_send_error_response(e)
|
160
|
+
end
|
161
|
+
|
162
|
+
def http_request_send_error_response(error)
|
163
|
+
response = format("Error: %s\n%s", error.inspect, error.backtrace.join("\n"))
|
164
|
+
req.respond(response, ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
|
165
|
+
rescue IOError, SystemCallError
|
166
|
+
# ignore
|
138
167
|
end
|
139
168
|
|
140
169
|
# @return [Boolean] true if response is complete
|
141
|
-
def http_request_message(id, req, message)
|
142
|
-
case
|
170
|
+
def http_request_message(id, req, kind, message)
|
171
|
+
case kind
|
143
172
|
when Protocol::HTTP_UPGRADE
|
144
|
-
http_custom_upgrade(id, req, message)
|
173
|
+
http_custom_upgrade(id, req, *message)
|
145
174
|
true
|
146
175
|
when Protocol::HTTP_GET_REQUEST_BODY
|
147
|
-
http_get_request_body(id, req, message)
|
176
|
+
http_get_request_body(id, req, *message)
|
148
177
|
false
|
149
178
|
when Protocol::HTTP_RESPONSE
|
150
|
-
|
151
|
-
body = message['body']
|
152
|
-
done = message['complete']
|
153
|
-
if !req.headers_sent? && done
|
154
|
-
req.respond(body, headers|| {})
|
155
|
-
true
|
156
|
-
else
|
157
|
-
req.send_headers(headers) if headers && !req.headers_sent?
|
158
|
-
req.send_chunk(body, done: done) if body or done
|
159
|
-
done
|
160
|
-
end
|
179
|
+
http_response(id, req, *message)
|
161
180
|
else
|
162
181
|
# invalid message
|
163
182
|
true
|
164
183
|
end
|
165
184
|
end
|
166
185
|
|
186
|
+
def send_transfer_count(key, rx, tx)
|
187
|
+
send_df_message(Protocol.transfer_count(key, rx, tx))
|
188
|
+
end
|
189
|
+
|
167
190
|
HTTP_RESPONSE_UPGRADE_HEADERS = { ':status' => Qeweney::Status::SWITCHING_PROTOCOLS }
|
168
191
|
|
169
|
-
def http_custom_upgrade(id, req,
|
192
|
+
def http_custom_upgrade(id, req, headers)
|
170
193
|
# send upgrade response
|
171
|
-
upgrade_headers =
|
172
|
-
|
194
|
+
upgrade_headers = headers ?
|
195
|
+
headers.merge(HTTP_RESPONSE_UPGRADE_HEADERS) :
|
173
196
|
HTTP_RESPONSE_UPGRADE_HEADERS
|
174
197
|
req.send_headers(upgrade_headers, true)
|
175
198
|
|
@@ -187,9 +210,9 @@ module DigitalFabric
|
|
187
210
|
end
|
188
211
|
|
189
212
|
def http_custom_upgrade_message(conn, message)
|
190
|
-
case message[
|
213
|
+
case message[Protocol::Attribute::KIND]
|
191
214
|
when Protocol::CONN_DATA
|
192
|
-
conn << message[
|
215
|
+
conn << message[:Protocol::Attribute::ConnData::DATA]
|
193
216
|
false
|
194
217
|
when Protocol::CONN_CLOSE
|
195
218
|
true
|
@@ -199,8 +222,30 @@ module DigitalFabric
|
|
199
222
|
end
|
200
223
|
end
|
201
224
|
|
202
|
-
def
|
203
|
-
|
225
|
+
def http_response(id, req, body, headers, complete, transfer_count_key)
|
226
|
+
if !req.headers_sent? && complete
|
227
|
+
req.respond(body, headers|| {})
|
228
|
+
if transfer_count_key
|
229
|
+
rx, tx = req.transfer_counts
|
230
|
+
send_transfer_count(transfer_count_key, rx, tx)
|
231
|
+
end
|
232
|
+
true
|
233
|
+
else
|
234
|
+
req.send_headers(headers) if headers && !req.headers_sent?
|
235
|
+
req.send_chunk(body, done: complete) if body or complete
|
236
|
+
|
237
|
+
if complete && transfer_count_key
|
238
|
+
rx, tx = req.transfer_counts
|
239
|
+
send_transfer_count(transfer_count_key, rx, tx)
|
240
|
+
end
|
241
|
+
complete
|
242
|
+
end
|
243
|
+
rescue IOError, SystemCallError
|
244
|
+
# ignore error
|
245
|
+
end
|
246
|
+
|
247
|
+
def http_get_request_body(id, req, limit)
|
248
|
+
case limit
|
204
249
|
when nil
|
205
250
|
body = req.read
|
206
251
|
else
|
@@ -230,9 +275,9 @@ module DigitalFabric
|
|
230
275
|
with_request do |id|
|
231
276
|
send_df_message(Protocol.ws_request(id, req.headers))
|
232
277
|
response = receive
|
233
|
-
case response[
|
278
|
+
case response[0]
|
234
279
|
when Protocol::WS_RESPONSE
|
235
|
-
headers = response[
|
280
|
+
headers = response[2] || {}
|
236
281
|
status = headers[':status'] || Qeweney::Status::SWITCHING_PROTOCOLS
|
237
282
|
if status != Qeweney::Status::SWITCHING_PROTOCOLS
|
238
283
|
req.respond(nil, headers)
|
@@ -244,6 +289,8 @@ module DigitalFabric
|
|
244
289
|
req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
|
245
290
|
end
|
246
291
|
end
|
292
|
+
rescue IOError, SystemCallError
|
293
|
+
# ignore
|
247
294
|
end
|
248
295
|
|
249
296
|
def run_websocket_connection(id, websocket)
|
@@ -253,9 +300,9 @@ module DigitalFabric
|
|
253
300
|
end
|
254
301
|
end
|
255
302
|
while (message = receive)
|
256
|
-
case message[
|
303
|
+
case message[Protocol::Attribute::KIND]
|
257
304
|
when Protocol::WS_DATA
|
258
|
-
websocket << message[
|
305
|
+
websocket << message[Protocol::Attribute::WS::DATA]
|
259
306
|
when Protocol::WS_CLOSE
|
260
307
|
return
|
261
308
|
else
|
@@ -4,6 +4,7 @@ module DigitalFabric
|
|
4
4
|
module Protocol
|
5
5
|
PING = 'ping'
|
6
6
|
SHUTDOWN = 'shutdown'
|
7
|
+
UNMOUNT = 'unmount'
|
7
8
|
|
8
9
|
HTTP_REQUEST = 'http_request'
|
9
10
|
HTTP_RESPONSE = 'http_response'
|
@@ -19,16 +20,68 @@ module DigitalFabric
|
|
19
20
|
WS_DATA = 'ws_data'
|
20
21
|
WS_CLOSE = 'ws_close'
|
21
22
|
|
23
|
+
TRANSFER_COUNT = 'transfer_count'
|
24
|
+
|
22
25
|
SEND_TIMEOUT = 15
|
23
26
|
RECV_TIMEOUT = SEND_TIMEOUT + 5
|
24
27
|
|
28
|
+
module Attribute
|
29
|
+
KIND = 0
|
30
|
+
ID = 1
|
31
|
+
|
32
|
+
module HttpRequest
|
33
|
+
HEADERS = 2
|
34
|
+
BODY_CHUNK = 3
|
35
|
+
COMPLETE = 4
|
36
|
+
end
|
37
|
+
|
38
|
+
module HttpResponse
|
39
|
+
BODY = 2
|
40
|
+
HEADERS = 3
|
41
|
+
COMPLETE = 4
|
42
|
+
TRANSFER_COUNT_KEY = 5
|
43
|
+
end
|
44
|
+
|
45
|
+
module HttpUpgrade
|
46
|
+
HEADERS = 2
|
47
|
+
end
|
48
|
+
|
49
|
+
module HttpGetRequestBody
|
50
|
+
LIMIT = 2
|
51
|
+
end
|
52
|
+
|
53
|
+
module HttpRequestBody
|
54
|
+
BODY = 2
|
55
|
+
COMPLETE = 3
|
56
|
+
end
|
57
|
+
|
58
|
+
module ConnectionData
|
59
|
+
DATA = 2
|
60
|
+
end
|
61
|
+
|
62
|
+
module WS
|
63
|
+
HEADERS = 2
|
64
|
+
DATA = 2
|
65
|
+
end
|
66
|
+
|
67
|
+
module TransferCount
|
68
|
+
KEY = 1
|
69
|
+
RX = 2
|
70
|
+
TX = 3
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
25
74
|
class << self
|
26
75
|
def ping
|
27
|
-
|
76
|
+
[ PING ]
|
28
77
|
end
|
29
78
|
|
30
79
|
def shutdown
|
31
|
-
|
80
|
+
[ SHUTDOWN ]
|
81
|
+
end
|
82
|
+
|
83
|
+
def unmount
|
84
|
+
[ UNMOUNT ]
|
32
85
|
end
|
33
86
|
|
34
87
|
DF_UPGRADE_RESPONSE = <<~HTTP.gsub("\n", "\r\n")
|
@@ -43,47 +96,51 @@ module DigitalFabric
|
|
43
96
|
end
|
44
97
|
|
45
98
|
def http_request(id, req)
|
46
|
-
|
99
|
+
[ HTTP_REQUEST, id, req.headers, req.next_chunk, req.complete? ]
|
47
100
|
end
|
48
101
|
|
49
|
-
def http_response(id, body, headers, complete)
|
50
|
-
|
102
|
+
def http_response(id, body, headers, complete, transfer_count_key = nil)
|
103
|
+
[ HTTP_RESPONSE, id, body, headers, complete, transfer_count_key ]
|
51
104
|
end
|
52
105
|
|
53
106
|
def http_upgrade(id, headers)
|
54
|
-
|
107
|
+
[ HTTP_UPGRADE, id, headers ]
|
55
108
|
end
|
56
109
|
|
57
110
|
def http_get_request_body(id, limit = nil)
|
58
|
-
|
111
|
+
[ HTTP_GET_REQUEST_BODY, id, limit ]
|
59
112
|
end
|
60
113
|
|
61
114
|
def http_request_body(id, body, complete)
|
62
|
-
|
115
|
+
[ HTTP_REQUEST_BODY, id, body, complete ]
|
63
116
|
end
|
64
117
|
|
65
118
|
def connection_data(id, data)
|
66
|
-
|
119
|
+
[ CONN_DATA, id, data ]
|
67
120
|
end
|
68
121
|
|
69
122
|
def connection_close(id)
|
70
|
-
|
123
|
+
[ CONN_CLOSE, id ]
|
71
124
|
end
|
72
125
|
|
73
126
|
def ws_request(id, headers)
|
74
|
-
|
127
|
+
[ WS_REQUEST, id, headers ]
|
75
128
|
end
|
76
129
|
|
77
130
|
def ws_response(id, headers)
|
78
|
-
|
131
|
+
[ WS_RESPONSE, id, headers ]
|
79
132
|
end
|
80
133
|
|
81
134
|
def ws_data(id, data)
|
82
|
-
|
135
|
+
[ WS_DATA, id, data ]
|
83
136
|
end
|
84
137
|
|
85
138
|
def ws_close(id)
|
86
|
-
|
139
|
+
[WS_CLOSE, id ]
|
140
|
+
end
|
141
|
+
|
142
|
+
def transfer_count(key, rx, tx)
|
143
|
+
[ TRANSFER_COUNT, key, rx, tx ]
|
87
144
|
end
|
88
145
|
end
|
89
146
|
end
|
@@ -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)
|
@@ -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
|
@@ -185,8 +189,8 @@ module Tipi
|
|
185
189
|
)
|
186
190
|
end
|
187
191
|
|
188
|
-
def websocket_connection(
|
189
|
-
Tipi::Websocket.new(@conn,
|
192
|
+
def websocket_connection(request)
|
193
|
+
Tipi::Websocket.new(@conn, request.headers)
|
190
194
|
end
|
191
195
|
|
192
196
|
def scheme_from_connection
|
@@ -200,52 +204,55 @@ module Tipi
|
|
200
204
|
|
201
205
|
# Sends response including headers and body. Waits for the request to complete
|
202
206
|
# if not yet completed. The body is sent using chunked transfer encoding.
|
207
|
+
# @param request [Qeweney::Request] HTTP request
|
203
208
|
# @param body [String] response body
|
204
209
|
# @param headers
|
205
|
-
def respond(body, headers)
|
206
|
-
consume_request if @parsing
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
# data << "#{body.bytesize.to_s(16)}\r\n#{body}\r\n0\r\n\r\n"
|
215
|
-
# end
|
216
|
-
# end
|
217
|
-
# Polyphony.backend_sendv(@conn, data, 0)
|
218
|
-
@conn.write(*data)
|
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))
|
214
|
+
if body
|
215
|
+
@conn.write(formatted_headers, body)
|
216
|
+
else
|
217
|
+
@conn.write(formatted_headers)
|
218
|
+
end
|
219
219
|
end
|
220
220
|
|
221
221
|
# Sends response headers. If empty_response is truthy, the response status
|
222
222
|
# code will default to 204, otherwise to 200.
|
223
|
+
# @param request [Qeweney::Request] HTTP request
|
223
224
|
# @param headers [Hash] response headers
|
224
225
|
# @param empty_response [boolean] whether a response body will be sent
|
225
226
|
# @param chunked [boolean] whether to use chunked transfer encoding
|
226
227
|
# @return [void]
|
227
|
-
def send_headers(headers, empty_response: false, chunked: true)
|
228
|
-
|
229
|
-
|
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)
|
230
232
|
end
|
231
233
|
|
232
234
|
# Sends a response body chunk. If no headers were sent, default headers are
|
233
235
|
# sent using #send_headers. if the done option is true(thy), an empty chunk
|
234
236
|
# will be sent to signal response completion to the client.
|
237
|
+
# @param request [Qeweney::Request] HTTP request
|
235
238
|
# @param chunk [String] response body chunk
|
236
239
|
# @param done [boolean] whether the response is completed
|
237
240
|
# @return [void]
|
238
|
-
def send_chunk(chunk, done: false)
|
239
|
-
data =
|
241
|
+
def send_chunk(request, chunk, done: false)
|
242
|
+
data = +''
|
240
243
|
data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" if chunk
|
241
244
|
data << "0\r\n\r\n" if done
|
242
|
-
|
245
|
+
return if data.empty?
|
246
|
+
|
247
|
+
request.tx_incr(data.bytesize)
|
248
|
+
@conn.write(data)
|
243
249
|
end
|
244
250
|
|
245
251
|
# Finishes the response to the current request. If no headers were sent,
|
246
252
|
# default headers are sent using #send_headers.
|
247
253
|
# @return [void]
|
248
|
-
def finish
|
254
|
+
def finish(request)
|
255
|
+
request.tx_incr(5)
|
249
256
|
@conn << "0\r\n\r\n"
|
250
257
|
end
|
251
258
|
|
@@ -255,6 +262,8 @@ module Tipi
|
|
255
262
|
|
256
263
|
private
|
257
264
|
|
265
|
+
INTERNAL_HEADER_REGEXP = /^:/.freeze
|
266
|
+
|
258
267
|
# Formats response headers into an array. If empty_response is true(thy),
|
259
268
|
# the response status code will default to 204, otherwise to 200.
|
260
269
|
# @param headers [Hash] response headers
|
@@ -266,7 +275,7 @@ module Tipi
|
|
266
275
|
status ||= (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
|
267
276
|
lines = format_status_line(body, status, chunked)
|
268
277
|
headers.each do |k, v|
|
269
|
-
next if k =~
|
278
|
+
next if k =~ INTERNAL_HEADER_REGEXP
|
270
279
|
|
271
280
|
collect_header_lines(lines, k, v)
|
272
281
|
end
|