tipi 0.31 → 0.36
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/CHANGELOG.md +29 -0
- data/Gemfile.lock +10 -4
- data/LICENSE +1 -1
- data/TODO.md +13 -47
- data/bin/tipi +13 -0
- data/df/agent.rb +63 -0
- data/df/etc_benchmark.rb +15 -0
- data/df/multi_agent_supervisor.rb +87 -0
- data/df/multi_client.rb +84 -0
- data/df/routing_benchmark.rb +60 -0
- data/df/sample_agent.rb +89 -0
- data/df/server.rb +54 -0
- data/df/sse_page.html +29 -0
- data/df/stress.rb +24 -0
- data/df/ws_page.html +38 -0
- data/examples/http_request_ws_server.rb +34 -0
- data/examples/http_server.rb +6 -6
- data/examples/http_server_forked.rb +4 -5
- data/examples/http_server_form.rb +23 -0
- data/examples/http_server_throttled_accept.rb +23 -0
- data/examples/http_unix_socket_server.rb +17 -0
- data/examples/http_ws_server.rb +10 -12
- data/examples/routing_server.rb +34 -0
- data/examples/websocket_client.rb +1 -2
- data/examples/websocket_demo.rb +4 -2
- data/examples/ws_page.html +1 -2
- data/lib/tipi.rb +7 -5
- data/lib/tipi/config_dsl.rb +153 -0
- data/lib/tipi/configuration.rb +1 -1
- data/lib/tipi/digital_fabric.rb +7 -0
- data/lib/tipi/digital_fabric/agent.rb +225 -0
- data/lib/tipi/digital_fabric/agent_proxy.rb +265 -0
- data/lib/tipi/digital_fabric/executive.rb +100 -0
- data/lib/tipi/digital_fabric/executive/index.html +69 -0
- data/lib/tipi/digital_fabric/protocol.rb +90 -0
- data/lib/tipi/digital_fabric/request_adapter.rb +48 -0
- data/lib/tipi/digital_fabric/service.rb +230 -0
- data/lib/tipi/http1_adapter.rb +59 -37
- data/lib/tipi/http2_adapter.rb +5 -3
- data/lib/tipi/http2_stream.rb +19 -7
- data/lib/tipi/rack_adapter.rb +11 -3
- data/lib/tipi/version.rb +1 -1
- data/lib/tipi/websocket.rb +33 -13
- data/test/helper.rb +1 -2
- data/test/test_http_server.rb +3 -2
- data/test/test_request.rb +108 -0
- data/tipi.gemspec +7 -3
- metadata +59 -7
- data/lib/tipi/request.rb +0 -118
@@ -0,0 +1,265 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './protocol'
|
4
|
+
require 'msgpack'
|
5
|
+
require 'tipi/websocket'
|
6
|
+
|
7
|
+
module DigitalFabric
|
8
|
+
class AgentProxy
|
9
|
+
def initialize(service, req)
|
10
|
+
@service = service
|
11
|
+
@req = req
|
12
|
+
@conn = req.adapter.conn
|
13
|
+
@msgpack_reader = MessagePack::Unpacker.new
|
14
|
+
@requests = {}
|
15
|
+
@current_request_count = 0
|
16
|
+
@last_request_id = 0
|
17
|
+
@last_recv = @last_send = Time.now
|
18
|
+
run
|
19
|
+
end
|
20
|
+
|
21
|
+
def current_request_count
|
22
|
+
@current_request_count
|
23
|
+
end
|
24
|
+
|
25
|
+
class TimeoutError < RuntimeError
|
26
|
+
end
|
27
|
+
|
28
|
+
class GracefulShutdown < RuntimeError
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
@fiber = Fiber.current
|
33
|
+
@service.mount(route, self)
|
34
|
+
keep_alive_timer = spin_loop(interval: 5) { keep_alive }
|
35
|
+
process_incoming_messages(false)
|
36
|
+
rescue GracefulShutdown
|
37
|
+
puts "Proxy got graceful shutdown, left: #{@requests.size} requests" if @requests.size > 0
|
38
|
+
process_incoming_messages(true)
|
39
|
+
ensure
|
40
|
+
keep_alive_timer&.stop
|
41
|
+
@service.unmount(self)
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_incoming_messages(shutdown = false)
|
45
|
+
return if shutdown && @requests.empty?
|
46
|
+
|
47
|
+
@conn.feed_loop(@msgpack_reader, :feed_each) do |msg|
|
48
|
+
recv_df_message(msg)
|
49
|
+
return if shutdown && @requests.empty?
|
50
|
+
end
|
51
|
+
rescue TimeoutError, IOError
|
52
|
+
end
|
53
|
+
|
54
|
+
def shutdown
|
55
|
+
send_df_message(Protocol.shutdown)
|
56
|
+
@fiber.raise GracefulShutdown.new
|
57
|
+
end
|
58
|
+
|
59
|
+
def keep_alive
|
60
|
+
now = Time.now
|
61
|
+
if now - @last_send >= Protocol::SEND_TIMEOUT
|
62
|
+
send_df_message(Protocol.ping)
|
63
|
+
end
|
64
|
+
# if now - @last_recv >= Protocol::RECV_TIMEOUT
|
65
|
+
# raise TimeoutError
|
66
|
+
# end
|
67
|
+
rescue TimeoutError, IOError
|
68
|
+
end
|
69
|
+
|
70
|
+
def route
|
71
|
+
case @req.headers['df-mount']
|
72
|
+
when /^\s*host\s*=\s*([^\s]+)/
|
73
|
+
{ host: Regexp.last_match(1) }
|
74
|
+
when /^\s*path\s*=\s*([^\s]+)/
|
75
|
+
{ path: Regexp.last_match(1) }
|
76
|
+
when /catch_all/
|
77
|
+
{ catch_all: true }
|
78
|
+
else
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def recv_df_message(message)
|
84
|
+
@last_recv = Time.now
|
85
|
+
return if message['kind'] == Protocol::PING
|
86
|
+
|
87
|
+
handler = @requests[message['id']]
|
88
|
+
if !handler
|
89
|
+
# puts "Unknown request id in #{message}"
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
handler << message
|
94
|
+
end
|
95
|
+
|
96
|
+
def send_df_message(message)
|
97
|
+
@last_send = Time.now
|
98
|
+
@conn << message.to_msgpack
|
99
|
+
end
|
100
|
+
|
101
|
+
# HTTP / WebSocket
|
102
|
+
|
103
|
+
def register_request_fiber
|
104
|
+
id = (@last_request_id += 1)
|
105
|
+
@requests[id] = Fiber.current
|
106
|
+
id
|
107
|
+
end
|
108
|
+
|
109
|
+
def unregister_request_fiber(id)
|
110
|
+
@requests.delete(id)
|
111
|
+
end
|
112
|
+
|
113
|
+
def with_request
|
114
|
+
@current_request_count += 1
|
115
|
+
id = (@last_request_id += 1)
|
116
|
+
@requests[id] = Fiber.current
|
117
|
+
yield id
|
118
|
+
ensure
|
119
|
+
@current_request_count -= 1
|
120
|
+
@requests.delete(id)
|
121
|
+
end
|
122
|
+
|
123
|
+
def http_request(req)
|
124
|
+
t0 = Time.now
|
125
|
+
t1 = nil
|
126
|
+
with_request do |id|
|
127
|
+
send_df_message(Protocol.http_request(id, req))
|
128
|
+
while (message = receive)
|
129
|
+
unless t1
|
130
|
+
t1 = Time.now
|
131
|
+
@service.record_latency_measurement(t1 - t0)
|
132
|
+
end
|
133
|
+
return if http_request_message(id, req, message)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
rescue => e
|
137
|
+
req.respond("Error: #{e.inspect}", ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
|
138
|
+
end
|
139
|
+
|
140
|
+
# @return [Boolean] true if response is complete
|
141
|
+
def http_request_message(id, req, message)
|
142
|
+
case message['kind']
|
143
|
+
when Protocol::HTTP_UPGRADE
|
144
|
+
http_custom_upgrade(id, req, message)
|
145
|
+
true
|
146
|
+
when Protocol::HTTP_GET_REQUEST_BODY
|
147
|
+
http_get_request_body(id, req, message)
|
148
|
+
false
|
149
|
+
when Protocol::HTTP_RESPONSE
|
150
|
+
headers = message['headers']
|
151
|
+
body = message['body']
|
152
|
+
done = message['complete']
|
153
|
+
req.send_headers(headers) if headers && !req.headers_sent?
|
154
|
+
req.send_chunk(body, done: done) if body or done
|
155
|
+
done
|
156
|
+
else
|
157
|
+
# invalid message
|
158
|
+
true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
HTTP_RESPONSE_UPGRADE_HEADERS = { ':status' => Qeweney::Status::SWITCHING_PROTOCOLS }
|
163
|
+
|
164
|
+
def http_custom_upgrade(id, req, message)
|
165
|
+
# send upgrade response
|
166
|
+
upgrade_headers = message['headers'] ?
|
167
|
+
message['headers'].merge(HTTP_RESPONSE_UPGRADE_HEADERS) :
|
168
|
+
HTTP_RESPONSE_UPGRADE_HEADERS
|
169
|
+
req.send_headers(upgrade_headers, true)
|
170
|
+
|
171
|
+
conn = req.adapter.conn
|
172
|
+
reader = spin do
|
173
|
+
conn.recv_loop do |data|
|
174
|
+
send_df_message(Protocol.conn_data(id, data))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
while (message = receive)
|
178
|
+
return if http_custom_upgrade_message(conn, message)
|
179
|
+
end
|
180
|
+
ensure
|
181
|
+
reader.stop
|
182
|
+
end
|
183
|
+
|
184
|
+
def http_custom_upgrade_message(conn, message)
|
185
|
+
case message['kind']
|
186
|
+
when Protocol::CONN_DATA
|
187
|
+
conn << message['data']
|
188
|
+
false
|
189
|
+
when Protocol::CONN_CLOSE
|
190
|
+
true
|
191
|
+
else
|
192
|
+
# invalid message
|
193
|
+
true
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def http_get_request_body(id, req, message)
|
198
|
+
case (limit = message['limit'])
|
199
|
+
when nil
|
200
|
+
body = req.read
|
201
|
+
else
|
202
|
+
limit = limit.to_i
|
203
|
+
body = nil
|
204
|
+
req.each_chunk do |chunk|
|
205
|
+
(body ||= +'') << chunk
|
206
|
+
break if body.bytesize >= limit
|
207
|
+
end
|
208
|
+
end
|
209
|
+
send_df_message(Protocol.http_request_body(id, body, req.complete?))
|
210
|
+
end
|
211
|
+
|
212
|
+
def http_upgrade(req, protocol)
|
213
|
+
if protocol == 'websocket'
|
214
|
+
handle_websocket_upgrade(req)
|
215
|
+
else
|
216
|
+
# other protocol upgrades should be handled by the agent, so we just run
|
217
|
+
# the request normally. The agent is expected to upgrade the connection
|
218
|
+
# using a http_upgrade message. From that moment on, two-way
|
219
|
+
# communication is handled using conn_data and conn_close messages.
|
220
|
+
http_request(req)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def handle_websocket_upgrade(req)
|
225
|
+
with_request do |id|
|
226
|
+
send_df_message(Protocol.ws_request(id, req.headers))
|
227
|
+
response = receive
|
228
|
+
case response['kind']
|
229
|
+
when Protocol::WS_RESPONSE
|
230
|
+
headers = response['headers'] || {}
|
231
|
+
status = headers[':status'] || Qeweney::Status::SWITCHING_PROTOCOLS
|
232
|
+
if status != Qeweney::Status::SWITCHING_PROTOCOLS
|
233
|
+
req.respond(nil, headers)
|
234
|
+
return
|
235
|
+
end
|
236
|
+
ws = Tipi::Websocket.new(req.adapter.conn, req.headers)
|
237
|
+
run_websocket_connection(id, ws)
|
238
|
+
else
|
239
|
+
req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def run_websocket_connection(id, websocket)
|
245
|
+
reader = spin do
|
246
|
+
websocket.recv_loop do |data|
|
247
|
+
send_df_message(Protocol.ws_data(id, data))
|
248
|
+
end
|
249
|
+
end
|
250
|
+
while (message = receive)
|
251
|
+
case message['kind']
|
252
|
+
when Protocol::WS_DATA
|
253
|
+
websocket << message['data']
|
254
|
+
when Protocol::WS_CLOSE
|
255
|
+
return
|
256
|
+
else
|
257
|
+
raise "Unexpected websocket message #{message.inspect}"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
ensure
|
261
|
+
reader.stop
|
262
|
+
websocket.close
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tipi/digital_fabric'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module DigitalFabric
|
7
|
+
# agent for managing DF service
|
8
|
+
class Executive
|
9
|
+
INDEX_HTML = IO.read(File.join(__dir__, 'executive/index.html'))
|
10
|
+
|
11
|
+
attr_reader :last_service_stats
|
12
|
+
|
13
|
+
def initialize(service, route = { path: '/executive' })
|
14
|
+
@service = service
|
15
|
+
route[:executive] = true
|
16
|
+
@service.mount(route, self)
|
17
|
+
@current_request_count = 0
|
18
|
+
@updater = spin_loop(interval: 10) { update_service_stats }
|
19
|
+
update_service_stats
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_request_count
|
23
|
+
@current_request_count
|
24
|
+
end
|
25
|
+
|
26
|
+
def http_request(req)
|
27
|
+
@current_request_count += 1
|
28
|
+
case req.path
|
29
|
+
when '/'
|
30
|
+
req.respond(INDEX_HTML, 'Content-Type' => 'text/html')
|
31
|
+
when '/stats'
|
32
|
+
message = last_service_stats
|
33
|
+
req.respond(message.to_json, { 'Content-Type' => 'text.json' })
|
34
|
+
when '/stream/stats'
|
35
|
+
stream_stats(req)
|
36
|
+
else
|
37
|
+
req.respond('Invalid path', { ':status' => Qeweney::Status::NOT_FOUND })
|
38
|
+
end
|
39
|
+
ensure
|
40
|
+
@current_request_count -= 1
|
41
|
+
end
|
42
|
+
|
43
|
+
def stream_stats(req)
|
44
|
+
req.send_headers({ 'Content-Type' => 'text/event-stream' })
|
45
|
+
|
46
|
+
@service.timer.every(10) do
|
47
|
+
message = last_service_stats
|
48
|
+
req.send_chunk(format_sse_event(message.to_json))
|
49
|
+
end
|
50
|
+
rescue IOError, SystemCallError
|
51
|
+
# ignore
|
52
|
+
ensure
|
53
|
+
req.send_chunk("retry: 0\n\n", true) rescue nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def format_sse_event(data)
|
57
|
+
"data: #{data}\n\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
def update_service_stats
|
61
|
+
@last_service_stats = {
|
62
|
+
service: @service.stats,
|
63
|
+
machine: machine_stats
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
TOP_CPU_REGEXP = /%Cpu(.+)/.freeze
|
68
|
+
TOP_CPU_IDLE_REGEXP = /([\d\.]+) id/.freeze
|
69
|
+
TOP_MEM_REGEXP = /MiB Mem(.+)/.freeze
|
70
|
+
TOP_MEM_FREE_REGEXP = /([\d\.]+) free/.freeze
|
71
|
+
LOADAVG_REGEXP = /^([\d\.]+)/.freeze
|
72
|
+
|
73
|
+
def machine_stats
|
74
|
+
top = `top -bn1 | head -n4`
|
75
|
+
unless top =~ TOP_CPU_REGEXP && Regexp.last_match(1) =~ TOP_CPU_IDLE_REGEXP
|
76
|
+
p top =~ TOP_CPU_REGEXP
|
77
|
+
p Regexp.last_match(1)
|
78
|
+
p Regexp.last_match(1) =~ TOP_CPU_IDLE_REGEXP
|
79
|
+
raise 'Invalid output from top (cpu)'
|
80
|
+
end
|
81
|
+
cpu_utilization = 100 - Regexp.last_match(1).to_i
|
82
|
+
|
83
|
+
unless top =~ TOP_MEM_REGEXP && Regexp.last_match(1) =~ TOP_MEM_FREE_REGEXP
|
84
|
+
raise 'Invalid output from top (mem)'
|
85
|
+
end
|
86
|
+
|
87
|
+
mem_free = Regexp.last_match(1).to_f
|
88
|
+
|
89
|
+
stats = `cat /proc/loadavg`
|
90
|
+
raise 'Invalid output from /proc/loadavg' unless stats =~ LOADAVG_REGEXP
|
91
|
+
load_avg = Regexp.last_match(1).to_f
|
92
|
+
|
93
|
+
{
|
94
|
+
mem_free: mem_free,
|
95
|
+
cpu_utilization: cpu_utilization,
|
96
|
+
load_avg: load_avg
|
97
|
+
}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<title>Digital Fabric Executive</title>
|
5
|
+
<style>
|
6
|
+
.hidden { display: none }
|
7
|
+
</style>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<h1>Digital Fabric Executive</h1>
|
11
|
+
<script>
|
12
|
+
function updateStats(update) {
|
13
|
+
for (let k in update.service) {
|
14
|
+
let v = update.service[k];
|
15
|
+
let e = document.querySelector('#' + k);
|
16
|
+
if (e) e.innerText = v;
|
17
|
+
}
|
18
|
+
for (let k in update.machine) {
|
19
|
+
let v = update.machine[k];
|
20
|
+
let e = document.querySelector('#' + k);
|
21
|
+
if (e) e.innerText = v;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
function connect() {
|
26
|
+
console.log("connecting...");
|
27
|
+
window.eventSource = new EventSource("/stream/stats");
|
28
|
+
|
29
|
+
window.eventSource.onopen = function(e) {
|
30
|
+
console.log("connected");
|
31
|
+
document.querySelector('#status').innerText = 'connected';
|
32
|
+
document.querySelector('#stats').className = '';
|
33
|
+
return false;
|
34
|
+
}
|
35
|
+
|
36
|
+
window.eventSource.onmessage = function(e) {
|
37
|
+
console.log("message", e.data);
|
38
|
+
updateStats(JSON.parse(e.data));
|
39
|
+
}
|
40
|
+
|
41
|
+
window.eventSource.onerror = function(e) {
|
42
|
+
console.log("error", e);
|
43
|
+
document.querySelector('#status').innerText = 'disconnected';
|
44
|
+
document.querySelector('#stats').className = 'hidden';
|
45
|
+
window.eventSource.close();
|
46
|
+
window.eventSource = null;
|
47
|
+
setTimeout(connect, 5000);
|
48
|
+
}
|
49
|
+
};
|
50
|
+
|
51
|
+
window.onload = connect;
|
52
|
+
</script>
|
53
|
+
<h2 id="status"></h2>
|
54
|
+
<div id="stats" class="hidden">
|
55
|
+
<h2>Service</h2>
|
56
|
+
<p>Request rate: <span id="http_request_rate"></span></p>
|
57
|
+
<p>Error rate: <span id="error_rate"></span></p>
|
58
|
+
<p>Average Latency: <span id="average_latency"></span>s</p>
|
59
|
+
<p>Connected agents: <span id="agent_count"></span></p>
|
60
|
+
<p>Connected clients: <span id="connection_count"></span></p>
|
61
|
+
<p>Concurrent requests: <span id="concurrent_requests"></span></p>
|
62
|
+
|
63
|
+
<h2>Machine</h2>
|
64
|
+
<p>CPU utilization: <span id="cpu_utilization"></span>%</p>
|
65
|
+
<p>Free memory: <span id="mem_free"></span>MB</p>
|
66
|
+
<p>Load average: <span id="load_avg"></span></p>
|
67
|
+
</div>
|
68
|
+
</body>
|
69
|
+
</html>
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DigitalFabric
|
4
|
+
module Protocol
|
5
|
+
PING = 'ping'
|
6
|
+
SHUTDOWN = 'shutdown'
|
7
|
+
|
8
|
+
HTTP_REQUEST = 'http_request'
|
9
|
+
HTTP_RESPONSE = 'http_response'
|
10
|
+
HTTP_UPGRADE = 'http_upgrade'
|
11
|
+
HTTP_GET_REQUEST_BODY = 'http_get_request_body'
|
12
|
+
HTTP_REQUEST_BODY = 'http_request_body'
|
13
|
+
|
14
|
+
CONN_DATA = 'conn_data'
|
15
|
+
CONN_CLOSE = 'conn_close'
|
16
|
+
|
17
|
+
WS_REQUEST = 'ws_request'
|
18
|
+
WS_RESPONSE = 'ws_response'
|
19
|
+
WS_DATA = 'ws_data'
|
20
|
+
WS_CLOSE = 'ws_close'
|
21
|
+
|
22
|
+
SEND_TIMEOUT = 15
|
23
|
+
RECV_TIMEOUT = SEND_TIMEOUT + 5
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def ping
|
27
|
+
{ kind: PING }
|
28
|
+
end
|
29
|
+
|
30
|
+
def shutdown
|
31
|
+
{ kind: SHUTDOWN }
|
32
|
+
end
|
33
|
+
|
34
|
+
DF_UPGRADE_RESPONSE = <<~HTTP.gsub("\n", "\r\n")
|
35
|
+
HTTP/1.1 101 Switching Protocols
|
36
|
+
Upgrade: df
|
37
|
+
Connection: Upgrade
|
38
|
+
|
39
|
+
HTTP
|
40
|
+
|
41
|
+
def df_upgrade_response
|
42
|
+
DF_UPGRADE_RESPONSE
|
43
|
+
end
|
44
|
+
|
45
|
+
def http_request(id, req)
|
46
|
+
{ kind: HTTP_REQUEST, id: id, headers: req.headers, body: req.next_chunk, complete: req.complete? }
|
47
|
+
end
|
48
|
+
|
49
|
+
def http_response(id, body, headers, complete)
|
50
|
+
{ kind: HTTP_RESPONSE, id: id, body: body, headers: headers, complete: complete }
|
51
|
+
end
|
52
|
+
|
53
|
+
def http_upgrade(id, headers)
|
54
|
+
{ kind: HTTP_UPGRADE, id: id }
|
55
|
+
end
|
56
|
+
|
57
|
+
def http_get_request_body(id, limit = nil)
|
58
|
+
{ kind: HTTP_GET_REQUEST_BODY, id: id, limit: limit }
|
59
|
+
end
|
60
|
+
|
61
|
+
def http_request_body(id, body, complete)
|
62
|
+
{ kind: HTTP_REQUEST_BODY, id: id, body: body, complete: complete }
|
63
|
+
end
|
64
|
+
|
65
|
+
def connection_data(id, data)
|
66
|
+
{ kind: CONN_DATA, id: id, data: data }
|
67
|
+
end
|
68
|
+
|
69
|
+
def connection_close(id)
|
70
|
+
{ kind: CONN_CLOSE, id: id }
|
71
|
+
end
|
72
|
+
|
73
|
+
def ws_request(id, headers)
|
74
|
+
{ kind: WS_REQUEST, id: id, headers: headers }
|
75
|
+
end
|
76
|
+
|
77
|
+
def ws_response(id, headers)
|
78
|
+
{ kind: WS_RESPONSE, id: id, headers: headers }
|
79
|
+
end
|
80
|
+
|
81
|
+
def ws_data(id, data)
|
82
|
+
{ id: id, kind: WS_DATA, data: data }
|
83
|
+
end
|
84
|
+
|
85
|
+
def ws_close(id)
|
86
|
+
{ id: id, kind: WS_CLOSE }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|