tipi 0.33 → 0.37.1
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 +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
|