-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathtest_http_server.rb
278 lines (222 loc) · 5.88 KB
/
test_http_server.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# frozen_string_literal: true
require_relative 'helper'
require 'tipi'
class String
def crlf_lines
gsub "\n", "\r\n"
end
end
class HTTP1ServerTest < MiniTest::Test
def teardown
@server&.interrupt if @server&.alive?
sleep 0.01
super
end
def spin_server(opts = {}, &handler)
server_connection, client_connection = IO.server_client_mockup
coproc = spin do
Tipi.client_loop(server_connection, opts, &handler)
end
[coproc, client_connection, server_connection]
end
def test_that_server_uses_content_length_in_http_1_0
@server, connection = spin_server do |req|
req.respond('Hello, world!', {})
end
# using HTTP 1.0, server should close connection after responding
connection << "GET / HTTP/1.0\r\n\r\n"
response = connection.readpartial(8192)
expected = <<~HTTP.chomp.crlf_lines.chomp
HTTP/1.1 200 OK
Content-Length: 13
Hello, world!
HTTP
assert_equal(expected, response)
end
def test_that_server_uses_chunked_encoding_in_http_1_1
@server, connection = spin_server do |req|
req.respond('Hello, world!')
end
# using HTTP 1.0, server should close connection after responding
connection << "GET / HTTP/1.1\r\n\r\n"
response = connection.readpartial(8192)
expected = <<~HTTP.crlf_lines.chomp
HTTP/1.1 200 OK
Content-Length: 13
Hello, world!
HTTP
assert_equal(expected, response)
end
def test_that_server_maintains_connection_when_using_keep_alives
@server, connection = spin_server do |req|
req.respond('Hi', {})
end
connection << "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
response = connection.readpartial(8192)
sleep 0.01
assert !connection.eof?
assert_equal("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nHi", response)
connection << "GET / HTTP/1.1\r\n\r\n"
response = connection.readpartial(8192)
sleep 0.01
assert !connection.eof?
expected = <<~HTTP.crlf_lines.chomp
HTTP/1.1 200 OK
Content-Length: 2
Hi
HTTP
assert_equal(expected, response)
connection << "GET / HTTP/1.0\r\n\r\n"
response = connection.readpartial(8192)
sleep 0.01
assert connection.eof?
assert_equal("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nHi", response)
end
def test_pipelining_client
@server, connection = spin_server do |req|
if req.headers['foo'] == 'bar'
req.respond('Hello, foobar!', {})
else
req.respond('Hello, world!', {})
end
end
connection << "GET / HTTP/1.1\r\n\r\nGET / HTTP/1.1\r\nFoo: bar\r\n\r\n"
sleep 0.01
response = connection.readpartial(8192)
expected = <<~HTTP.crlf_lines.chomp
HTTP/1.1 200 OK
Content-Length: 13
Hello, world!HTTP/1.1 200 OK
Content-Length: 14
Hello, foobar!
HTTP
assert_equal(expected, response)
end
def test_body_chunks
chunks = []
request = nil
@server, connection = spin_server do |req|
request = req
req.send_headers
req.each_chunk do |c|
chunks << c
req << c.upcase
end
req.finish
end
connection << <<~HTTP.crlf_lines
POST / HTTP/1.1
Transfer-Encoding: chunked
6
foobar
HTTP
sleep 0.01
assert request
assert_equal %w[foobar], chunks
assert !request.complete?
connection << "6\r\nbazbud\r\n"
sleep 0.01
assert_equal %w[foobar bazbud], chunks
assert !request.complete?
connection << "0\r\n\r\n"
sleep 0.01
assert_equal %w[foobar bazbud], chunks
assert request.complete?
sleep 0.01
response = connection.readpartial(8192)
expected = <<~HTTP.crlf_lines
HTTP/1.1 200
Transfer-Encoding: chunked
6
FOOBAR
6
BAZBUD
0
HTTP
assert_equal(expected, response)
end
def test_upgrade
done = nil
opts = {
upgrade: {
echo: lambda do |adapter, _headers|
conn = adapter.conn
conn << <<~HTTP.crlf_lines
HTTP/1.1 101 Switching Protocols
Upgrade: echo
Connection: Upgrade
HTTP
conn.read_loop { |data| conn << data }
done = true
end
}
}
@server, connection = spin_server(opts) do |req|
req.respond('Hi')
end
connection << "GET / HTTP/1.1\r\n\r\n"
response = connection.readpartial(8192)
sleep 0.01
assert !connection.eof?
expected = <<~HTTP.crlf_lines.chomp
HTTP/1.1 200 OK
Content-Length: 2
Hi
HTTP
assert_equal(expected, response)
connection << <<~HTTP.crlf_lines
GET / HTTP/1.1
Upgrade: echo
Connection: upgrade
HTTP
snooze
response = connection.readpartial(8192)
snooze
assert !connection.eof?
expected = <<~HTTP.crlf_lines
HTTP/1.1 101 Switching Protocols
Upgrade: echo
Connection: Upgrade
HTTP
assert_equal(expected, response)
assert !done
connection << 'foo'
assert_equal 'foo', connection.readpartial(8192)
connection << 'bar'
assert_equal 'bar', connection.readpartial(8192)
connection.close
assert !done
sleep 0.01
assert done
end
def test_big_download
chunk_size = 1000
chunk_count = 1000
chunk = '*' * chunk_size
@server, connection = spin_server do |req|
req.send_headers
chunk_count.times do |i|
req << chunk
snooze
end
req.finish
req.adapter.close
end
response = +''
count = 0
connection << "GET / HTTP/1.1\r\n\r\n"
while (data = connection.read(chunk_size))
response << data
count += 1
snooze
end
chunks = "#{chunk_size.to_s(16)}\n#{'*' * chunk_size}\n" * chunk_count
expected = <<~HTTP.crlf_lines
HTTP/1.1 200
Transfer-Encoding: chunked
#{chunks}0
HTTP
assert_equal expected, response
assert count >= chunk_count
end
end