tipi 0.33 → 0.37.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +10 -4
- data/LICENSE +1 -1
- data/TODO.md +11 -47
- 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/e +0 -0
- data/examples/http_request_ws_server.rb +35 -0
- data/examples/http_server.rb +6 -6
- data/examples/http_server_form.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/ws_page.html +1 -2
- data/lib/tipi.rb +5 -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 +50 -14
- data/lib/tipi/http2_adapter.rb +4 -2
- data/lib/tipi/http2_stream.rb +20 -8
- data/lib/tipi/rack_adapter.rb +1 -1
- data/lib/tipi/version.rb +1 -1
- data/lib/tipi/websocket.rb +33 -29
- data/test/helper.rb +1 -2
- data/test/test_http_server.rb +10 -12
- data/test/test_request.rb +108 -0
- data/tipi.gemspec +7 -3
- metadata +57 -6
- data/lib/tipi/request.rb +0 -118
data/lib/tipi/http1_adapter.rb
CHANGED
@@ -1,16 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'http/parser'
|
4
|
-
require_relative './request'
|
5
4
|
require_relative './http2_adapter'
|
5
|
+
require 'qeweney/request'
|
6
6
|
|
7
7
|
module Tipi
|
8
8
|
# HTTP1 protocol implementation
|
9
9
|
class HTTP1Adapter
|
10
|
+
attr_reader :conn
|
11
|
+
|
10
12
|
# Initializes a protocol adapter instance
|
11
13
|
def initialize(conn, opts)
|
12
14
|
@conn = conn
|
13
15
|
@opts = opts
|
16
|
+
@first = true
|
14
17
|
@parser = ::HTTP::Parser.new(self)
|
15
18
|
end
|
16
19
|
|
@@ -28,6 +31,10 @@ module Tipi
|
|
28
31
|
def handle_incoming_data(data, &block)
|
29
32
|
@parser << data
|
30
33
|
while (request = @requests_head)
|
34
|
+
if @first
|
35
|
+
request.headers[':first'] = true
|
36
|
+
@first = nil
|
37
|
+
end
|
31
38
|
return true if upgrade_connection(request.headers, &block)
|
32
39
|
|
33
40
|
@requests_head = request.__next__
|
@@ -77,9 +84,23 @@ module Tipi
|
|
77
84
|
end
|
78
85
|
|
79
86
|
def on_headers_complete(headers)
|
87
|
+
headers = normalize_headers(headers)
|
80
88
|
headers[':path'] = @parser.request_url
|
81
|
-
headers[':method'] = @parser.http_method
|
82
|
-
queue_request(Request.new(headers, self))
|
89
|
+
headers[':method'] = @parser.http_method.downcase
|
90
|
+
queue_request(Qeweney::Request.new(headers, self))
|
91
|
+
end
|
92
|
+
|
93
|
+
def normalize_headers(headers)
|
94
|
+
headers.each_with_object({}) do |(k, v), h|
|
95
|
+
k = k.downcase
|
96
|
+
hk = h[k]
|
97
|
+
if hk
|
98
|
+
hk = h[k] = [hk] unless hk.is_a?(Array)
|
99
|
+
v.is_a?(Array) ? hk.concat(v) : hk << v
|
100
|
+
else
|
101
|
+
h[k] = v
|
102
|
+
end
|
103
|
+
end
|
83
104
|
end
|
84
105
|
|
85
106
|
def queue_request(request)
|
@@ -110,6 +131,14 @@ module Tipi
|
|
110
131
|
# protocols, notably WebSocket, can be specified by passing a hash to the
|
111
132
|
# :upgrade option when starting a server:
|
112
133
|
#
|
134
|
+
# def ws_handler(conn)
|
135
|
+
# conn << 'hi'
|
136
|
+
# msg = conn.recv
|
137
|
+
# conn << "You said #{msg}"
|
138
|
+
# conn << 'bye'
|
139
|
+
# conn.close
|
140
|
+
# end
|
141
|
+
#
|
113
142
|
# opts = {
|
114
143
|
# upgrade: {
|
115
144
|
# websocket: Tipi::Websocket.handler(&method(:ws_handler))
|
@@ -120,7 +149,7 @@ module Tipi
|
|
120
149
|
# @param headers [Hash] request headers
|
121
150
|
# @return [boolean] truthy if the connection has been upgraded
|
122
151
|
def upgrade_connection(headers, &block)
|
123
|
-
upgrade_protocol = headers['
|
152
|
+
upgrade_protocol = headers['upgrade']
|
124
153
|
return nil unless upgrade_protocol
|
125
154
|
|
126
155
|
upgrade_protocol = upgrade_protocol.downcase.to_sym
|
@@ -133,7 +162,7 @@ module Tipi
|
|
133
162
|
|
134
163
|
def upgrade_with_handler(handler, headers)
|
135
164
|
@parser = @requests_head = @requests_tail = nil
|
136
|
-
handler.(
|
165
|
+
handler.(self, headers)
|
137
166
|
true
|
138
167
|
end
|
139
168
|
|
@@ -149,9 +178,13 @@ module Tipi
|
|
149
178
|
def http2_upgraded_headers(headers)
|
150
179
|
headers.merge(
|
151
180
|
':scheme' => 'http',
|
152
|
-
':authority' => headers['
|
181
|
+
':authority' => headers['host']
|
153
182
|
)
|
154
183
|
end
|
184
|
+
|
185
|
+
def websocket_connection(req)
|
186
|
+
Tipi::Websocket.new(@conn, req.headers)
|
187
|
+
end
|
155
188
|
|
156
189
|
# response API
|
157
190
|
|
@@ -164,15 +197,17 @@ module Tipi
|
|
164
197
|
# @param headers
|
165
198
|
def respond(body, headers)
|
166
199
|
consume_request if @parsing
|
167
|
-
data = format_headers(headers, body)
|
200
|
+
data = [format_headers(headers, body)]
|
168
201
|
if body
|
169
202
|
if @parser.http_minor == 0
|
170
203
|
data << body
|
171
204
|
else
|
172
|
-
data << body.bytesize.to_s(16) << CRLF << body << CRLF_ZERO_CRLF_CRLF
|
205
|
+
# data << body.bytesize.to_s(16) << CRLF << body << CRLF_ZERO_CRLF_CRLF
|
206
|
+
data << "#{body.bytesize.to_s(16)}\r\n#{body}\r\n0\r\n\r\n"
|
173
207
|
end
|
174
208
|
end
|
175
|
-
@conn
|
209
|
+
# Polyphony.backend_sendv(@conn, data, 0)
|
210
|
+
@conn.write(*data)
|
176
211
|
end
|
177
212
|
|
178
213
|
DEFAULT_HEADERS_OPTS = {
|
@@ -187,7 +222,7 @@ module Tipi
|
|
187
222
|
# @return [void]
|
188
223
|
def send_headers(headers, opts = DEFAULT_HEADERS_OPTS)
|
189
224
|
data = format_headers(headers, true)
|
190
|
-
@conn.write(data
|
225
|
+
@conn.write(data)
|
191
226
|
end
|
192
227
|
|
193
228
|
# Sends a response body chunk. If no headers were sent, default headers are
|
@@ -198,9 +233,9 @@ module Tipi
|
|
198
233
|
# @return [void]
|
199
234
|
def send_chunk(chunk, done: false)
|
200
235
|
data = []
|
201
|
-
data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
236
|
+
data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" if chunk
|
202
237
|
data << "0\r\n\r\n" if done
|
203
|
-
@conn.write(data.join)
|
238
|
+
@conn.write(data.join) unless data.empty?
|
204
239
|
end
|
205
240
|
|
206
241
|
# Finishes the response to the current request. If no headers were sent,
|
@@ -222,8 +257,9 @@ module Tipi
|
|
222
257
|
# @param empty_response [boolean] whether a response body will be sent
|
223
258
|
# @return [String] formatted response headers
|
224
259
|
def format_headers(headers, body)
|
225
|
-
status = headers[':status']
|
226
|
-
|
260
|
+
status = headers[':status']
|
261
|
+
status ||= (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
|
262
|
+
lines = format_status_line(body, status)
|
227
263
|
headers.each do |k, v|
|
228
264
|
next if k =~ /^:/
|
229
265
|
|
data/lib/tipi/http2_adapter.rb
CHANGED
@@ -15,6 +15,7 @@ module Tipi
|
|
15
15
|
@conn = conn
|
16
16
|
@opts = opts
|
17
17
|
@upgrade_headers = upgrade_headers
|
18
|
+
@first = true
|
18
19
|
|
19
20
|
@interface = ::HTTP2::Server.new
|
20
21
|
@connection_fiber = Fiber.current
|
@@ -37,7 +38,7 @@ module Tipi
|
|
37
38
|
|
38
39
|
def upgrade
|
39
40
|
@conn << UPGRADE_MESSAGE
|
40
|
-
settings = @upgrade_headers['
|
41
|
+
settings = @upgrade_headers['http2-settings']
|
41
42
|
Fiber.current.schedule(nil)
|
42
43
|
@interface.upgrade(settings, @upgrade_headers, '')
|
43
44
|
ensure
|
@@ -57,7 +58,8 @@ module Tipi
|
|
57
58
|
end
|
58
59
|
|
59
60
|
def start_stream(stream, &block)
|
60
|
-
stream = HTTP2StreamHandler.new(stream, &block)
|
61
|
+
stream = HTTP2StreamHandler.new(stream, @conn, @first, &block)
|
62
|
+
@first = nil if @first
|
61
63
|
@streams[stream] = true
|
62
64
|
end
|
63
65
|
|
data/lib/tipi/http2_stream.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'http/2'
|
4
|
-
|
4
|
+
require 'qeweney/request'
|
5
5
|
|
6
6
|
module Tipi
|
7
7
|
# Manages an HTTP 2 stream
|
8
8
|
class HTTP2StreamHandler
|
9
9
|
attr_accessor :__next__
|
10
|
+
attr_reader :conn
|
10
11
|
|
11
|
-
def initialize(stream, &block)
|
12
|
+
def initialize(stream, conn, first, &block)
|
12
13
|
@stream = stream
|
14
|
+
@conn = conn
|
15
|
+
@first = first
|
13
16
|
@connection_fiber = Fiber.current
|
14
17
|
@stream_fiber = spin { |req| handle_request(req, &block) }
|
15
18
|
|
@@ -43,7 +46,11 @@ module Tipi
|
|
43
46
|
end
|
44
47
|
|
45
48
|
def on_headers(headers)
|
46
|
-
@request = Request.new(headers.to_h, self)
|
49
|
+
@request = Qeweney::Request.new(headers.to_h, self)
|
50
|
+
if @first
|
51
|
+
@request.headers[':first'] = true
|
52
|
+
@first = false
|
53
|
+
end
|
47
54
|
@stream_fiber.schedule @request
|
48
55
|
end
|
49
56
|
|
@@ -55,7 +62,7 @@ module Tipi
|
|
55
62
|
@request.buffer_body_chunk(data)
|
56
63
|
end
|
57
64
|
end
|
58
|
-
|
65
|
+
|
59
66
|
def on_half_close
|
60
67
|
if @waiting_for_body_chunk
|
61
68
|
@waiting_for_body_chunk = nil
|
@@ -96,7 +103,7 @@ module Tipi
|
|
96
103
|
|
97
104
|
# response API
|
98
105
|
def respond(chunk, headers)
|
99
|
-
headers[':status'] ||=
|
106
|
+
headers[':status'] ||= Qeweney::Status::OK
|
100
107
|
@stream.headers(headers, end_stream: false)
|
101
108
|
@stream.data(chunk, end_stream: true)
|
102
109
|
@headers_sent = true
|
@@ -105,21 +112,26 @@ module Tipi
|
|
105
112
|
def send_headers(headers, empty_response = false)
|
106
113
|
return if @headers_sent
|
107
114
|
|
108
|
-
headers[':status'] ||= (empty_response ?
|
115
|
+
headers[':status'] ||= (empty_response ? Qeweney::Status::NO_CONTENT : Qeweney::Status::OK).to_s
|
109
116
|
@stream.headers(headers, end_stream: false)
|
110
117
|
@headers_sent = true
|
111
118
|
end
|
112
119
|
|
113
120
|
def send_chunk(chunk, done: false)
|
114
121
|
send_headers({}, false) unless @headers_sent
|
115
|
-
|
122
|
+
|
123
|
+
if chunk
|
124
|
+
@stream.data(chunk, end_stream: done)
|
125
|
+
elsif done
|
126
|
+
@stream.close
|
127
|
+
end
|
116
128
|
end
|
117
129
|
|
118
130
|
def finish
|
119
131
|
if @headers_sent
|
120
132
|
@stream.close
|
121
133
|
else
|
122
|
-
headers[':status'] ||=
|
134
|
+
headers[':status'] ||= Qeweney::Status::NO_CONTENT
|
123
135
|
@stream.headers(headers, end_stream: true)
|
124
136
|
end
|
125
137
|
end
|
data/lib/tipi/rack_adapter.rb
CHANGED
@@ -62,7 +62,7 @@ module Tipi
|
|
62
62
|
when 'REQUEST_METHOD' then request.method
|
63
63
|
when 'PATH_INFO' then request.path
|
64
64
|
when 'QUERY_STRING' then request.query_string || ''
|
65
|
-
when 'SERVER_NAME' then request.headers['
|
65
|
+
when 'SERVER_NAME' then request.headers['host']
|
66
66
|
when 'rack.input' then InputStream.new(request)
|
67
67
|
when HTTP_HEADER_RE then request.headers[$1.downcase]
|
68
68
|
else RACK_ENV[key]
|
data/lib/tipi/version.rb
CHANGED
data/lib/tipi/websocket.rb
CHANGED
@@ -7,32 +7,17 @@ module Tipi
|
|
7
7
|
# Websocket connection
|
8
8
|
class Websocket
|
9
9
|
def self.handler(&block)
|
10
|
-
proc
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
proc do |adapter, headers|
|
11
|
+
req = Qeweney::Request.new(headers, adapter)
|
12
|
+
websocket = req.upgrade_to_websocket
|
13
|
+
block.(websocket)
|
14
|
+
end
|
15
|
+
end
|
14
16
|
|
15
|
-
def initialize(
|
16
|
-
@
|
17
|
+
def initialize(conn, headers)
|
18
|
+
@conn = conn
|
17
19
|
@headers = headers
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
S_WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
22
|
-
UPGRADE_RESPONSE = <<~HTTP.gsub("\n", "\r\n")
|
23
|
-
HTTP/1.1 101 Switching Protocols
|
24
|
-
Upgrade: websocket
|
25
|
-
Connection: Upgrade
|
26
|
-
Sec-WebSocket-Accept: %<accept>s
|
27
|
-
|
28
|
-
HTTP
|
29
|
-
|
30
|
-
def setup(headers)
|
31
|
-
key = headers['Sec-WebSocket-Key']
|
32
|
-
@version = headers['Sec-WebSocket-Version'].to_i
|
33
|
-
accept = Digest::SHA1.base64digest([key, S_WS_GUID].join)
|
34
|
-
@client << format(UPGRADE_RESPONSE, accept: accept)
|
35
|
-
|
20
|
+
@version = headers['sec-websocket-version'].to_i
|
36
21
|
@reader = ::WebSocket::Frame::Incoming::Server.new(version: @version)
|
37
22
|
end
|
38
23
|
|
@@ -41,22 +26,41 @@ module Tipi
|
|
41
26
|
return msg.to_s
|
42
27
|
end
|
43
28
|
|
44
|
-
@
|
29
|
+
@conn.recv_loop do |data|
|
45
30
|
@reader << data
|
46
31
|
if (msg = @reader.next)
|
47
|
-
|
32
|
+
return msg.to_s
|
48
33
|
end
|
49
34
|
end
|
50
|
-
|
35
|
+
|
51
36
|
nil
|
52
37
|
end
|
38
|
+
|
39
|
+
def recv_loop
|
40
|
+
if (msg = @reader.next)
|
41
|
+
yield msg.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
@conn.recv_loop do |data|
|
45
|
+
@reader << data
|
46
|
+
while (msg = @reader.next)
|
47
|
+
yield msg.to_s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
53
51
|
|
52
|
+
OutgoingFrame = ::WebSocket::Frame::Outgoing::Server
|
53
|
+
|
54
54
|
def send(data)
|
55
|
-
frame =
|
55
|
+
frame = OutgoingFrame.new(
|
56
56
|
version: @version, data: data, type: :text
|
57
57
|
)
|
58
|
-
@
|
58
|
+
@conn << frame.to_s
|
59
59
|
end
|
60
60
|
alias_method :<<, :send
|
61
|
+
|
62
|
+
def close
|
63
|
+
@conn.close
|
64
|
+
end
|
61
65
|
end
|
62
66
|
end
|
data/test/helper.rb
CHANGED
@@ -33,8 +33,7 @@ class MiniTest::Test
|
|
33
33
|
|
34
34
|
def teardown
|
35
35
|
# puts "* teardown #{self.name.inspect} Fiber.current: #{Fiber.current.inspect}"
|
36
|
-
Fiber.current.
|
37
|
-
Fiber.current.await_all_children
|
36
|
+
Fiber.current.shutdown_all_children
|
38
37
|
rescue => e
|
39
38
|
puts e
|
40
39
|
puts e.backtrace.join("\n")
|
data/test/test_http_server.rb
CHANGED
@@ -23,13 +23,13 @@ class IO
|
|
23
23
|
|
24
24
|
def self.mockup_connection(input, output, output2)
|
25
25
|
eg(
|
26
|
-
:read => ->(*args) {
|
27
|
-
:read_loop => ->(*args, &block) {
|
28
|
-
:recv_loop => ->(*args, &block) {
|
29
|
-
:readpartial => ->(*args) {
|
30
|
-
:recv => ->(*args) {
|
31
|
-
:<< => ->(*args) {
|
32
|
-
:write => ->(*args) {
|
26
|
+
:read => ->(*args) { input.read(*args) },
|
27
|
+
:read_loop => ->(*args, &block) { input.read_loop(*args, &block) },
|
28
|
+
:recv_loop => ->(*args, &block) { input.read_loop(*args, &block) },
|
29
|
+
:readpartial => ->(*args) { input.readpartial(*args) },
|
30
|
+
:recv => ->(*args) { input.readpartial(*args) },
|
31
|
+
:<< => ->(*args) { output.write(*args) },
|
32
|
+
:write => ->(*args) { output.write(*args) },
|
33
33
|
:close => -> { output.close },
|
34
34
|
:eof? => -> { output2.closed? }
|
35
35
|
)
|
@@ -91,7 +91,6 @@ class HTTP1ServerTest < MiniTest::Test
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def test_that_server_maintains_connection_when_using_keep_alives
|
94
|
-
puts 'test_that_server_maintains_connection_when_using_keep_alives'
|
95
94
|
@server, connection = spin_server do |req|
|
96
95
|
req.respond('Hi', {})
|
97
96
|
end
|
@@ -123,7 +122,7 @@ class HTTP1ServerTest < MiniTest::Test
|
|
123
122
|
|
124
123
|
def test_pipelining_client
|
125
124
|
@server, connection = spin_server do |req|
|
126
|
-
if req.headers['
|
125
|
+
if req.headers['foo'] == 'bar'
|
127
126
|
req.respond('Hello, foobar!', {})
|
128
127
|
else
|
129
128
|
req.respond('Hello, world!', {})
|
@@ -160,14 +159,12 @@ class HTTP1ServerTest < MiniTest::Test
|
|
160
159
|
request = req
|
161
160
|
req.send_headers
|
162
161
|
req.each_chunk do |c|
|
163
|
-
puts "chunk: #{c.inspect}"
|
164
162
|
chunks << c
|
165
163
|
req << c.upcase
|
166
164
|
end
|
167
165
|
req.finish
|
168
166
|
end
|
169
167
|
|
170
|
-
p connection
|
171
168
|
connection << <<~HTTP.http_lines
|
172
169
|
POST / HTTP/1.1
|
173
170
|
Transfer-Encoding: chunked
|
@@ -213,7 +210,8 @@ class HTTP1ServerTest < MiniTest::Test
|
|
213
210
|
|
214
211
|
opts = {
|
215
212
|
upgrade: {
|
216
|
-
echo: lambda do |
|
213
|
+
echo: lambda do |adapter, _headers|
|
214
|
+
conn = adapter.conn
|
217
215
|
conn << <<~HTTP.http_lines
|
218
216
|
HTTP/1.1 101 Switching Protocols
|
219
217
|
Upgrade: echo
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
require 'tipi'
|
5
|
+
|
6
|
+
class String
|
7
|
+
def http_lines
|
8
|
+
gsub "\n", "\r\n"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class IO
|
13
|
+
# Creates two mockup sockets for simulating server-client communication
|
14
|
+
def self.server_client_mockup
|
15
|
+
server_in, client_out = IO.pipe
|
16
|
+
client_in, server_out = IO.pipe
|
17
|
+
|
18
|
+
server_connection = mockup_connection(server_in, server_out, client_out)
|
19
|
+
client_connection = mockup_connection(client_in, client_out, server_out)
|
20
|
+
|
21
|
+
[server_connection, client_connection]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.mockup_connection(input, output, output2)
|
25
|
+
eg(
|
26
|
+
:read => ->(*args) { input.read(*args) },
|
27
|
+
:read_loop => ->(*args, &block) { input.read_loop(*args, &block) },
|
28
|
+
:recv_loop => ->(*args, &block) { input.read_loop(*args, &block) },
|
29
|
+
:readpartial => ->(*args) { input.readpartial(*args) },
|
30
|
+
:recv => ->(*args) { input.readpartial(*args) },
|
31
|
+
:<< => ->(*args) { output.write(*args) },
|
32
|
+
:write => ->(*args) { output.write(*args) },
|
33
|
+
:close => -> { output.close },
|
34
|
+
:eof? => -> { output2.closed? }
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class RequestHeadersTest < MiniTest::Test
|
40
|
+
def teardown
|
41
|
+
@server&.interrupt if @server&.alive?
|
42
|
+
snooze
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def spin_server(opts = {}, &handler)
|
47
|
+
server_connection, client_connection = IO.server_client_mockup
|
48
|
+
coproc = spin do
|
49
|
+
Tipi.client_loop(server_connection, opts, &handler)
|
50
|
+
end
|
51
|
+
[coproc, client_connection, server_connection]
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_request_headers
|
55
|
+
req = nil
|
56
|
+
@server, connection = spin_server do |r|
|
57
|
+
req = r
|
58
|
+
req.respond('Hello, world!')
|
59
|
+
end
|
60
|
+
|
61
|
+
connection << "GET /titi HTTP/1.1\r\nHost: blah.com\r\nFoo: bar\r\nhi: 1\r\nHi: 2\r\nhi: 3\r\n\r\n"
|
62
|
+
|
63
|
+
snooze
|
64
|
+
|
65
|
+
assert_kind_of Qeweney::Request, req
|
66
|
+
assert_equal 'blah.com', req.headers['host']
|
67
|
+
assert_equal 'bar', req.headers['foo']
|
68
|
+
assert_equal ['1', '3', '2'], req.headers['hi']
|
69
|
+
assert_equal 'get', req.headers[':method']
|
70
|
+
assert_equal '/titi', req.headers[':path']
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_request_host
|
74
|
+
req = nil
|
75
|
+
@server, connection = spin_server do |r|
|
76
|
+
req = r
|
77
|
+
req.respond('Hello, world!')
|
78
|
+
end
|
79
|
+
|
80
|
+
connection << "GET /titi HTTP/1.1\nHost: blah.com\nFoo: bar\nhi: 1\nHi: 2\nhi: 3\n\n"
|
81
|
+
snooze
|
82
|
+
assert_equal 'blah.com', req.host
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_request_connection
|
86
|
+
req = nil
|
87
|
+
@server, connection = spin_server do |r|
|
88
|
+
req = r
|
89
|
+
req.respond('Hello, world!')
|
90
|
+
end
|
91
|
+
|
92
|
+
connection << "GET /titi HTTP/1.1\nConnection: keep-alive\nFoo: bar\nhi: 1\nHi: 2\nhi: 3\n\n"
|
93
|
+
snooze
|
94
|
+
assert_equal 'keep-alive', req.connection
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_request_upgrade_protocol
|
98
|
+
req = nil
|
99
|
+
@server, connection = spin_server do |r|
|
100
|
+
req = r
|
101
|
+
req.respond('Hello, world!')
|
102
|
+
end
|
103
|
+
|
104
|
+
connection << "GET /titi HTTP/1.1\nConnection: upgrade\nUpgrade: foobar\n\n"
|
105
|
+
snooze
|
106
|
+
assert_equal 'foobar', req.upgrade_protocol
|
107
|
+
end
|
108
|
+
end
|