-
-
Notifications
You must be signed in to change notification settings - Fork 83
/
Copy pathserver_spec.lua
362 lines (361 loc) · 10.3 KB
/
server_spec.lua
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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
describe("http.server module", function()
local http_server = require "http.server"
local http_client = require "http.client"
local http_tls = require "http.tls"
local http_headers = require "http.headers"
local cqueues = require "cqueues"
local ca = require "cqueues.auxlib"
local ce = require "cqueues.errno"
local cs = require "cqueues.socket"
local openssl_ctx = require "openssl.ssl.context"
local non_verifying_tls_context = http_tls.new_client_context()
non_verifying_tls_context:setVerify(openssl_ctx.VERIFY_NONE)
it("rejects missing 'ctx' field", function()
local s, c = ca.assert(cs.pair())
assert.has.errors(function()
http_server.new {
socket = s;
onstream = error;
}
end)
s:close()
c:close()
end)
it("rejects invalid 'cq' field", function()
local s, c = ca.assert(cs.pair())
assert.has.errors(function()
http_server.new {
socket = s;
tls = false;
onstream = error;
cq = 5;
}
end)
s:close()
c:close()
end)
it("__tostring works", function()
local s, c = ca.assert(cs.pair())
s = http_server.new {
socket = s;
tls = false;
onstream = error;
}
assert.same("http.server{", tostring(s):match("^.-%{"))
s:close()
c:close()
end)
it(":onerror with no arguments doesn't clear", function()
local s, c = ca.assert(cs.pair())
s = http_server.new {
socket = s;
tls = false;
onstream = error;
}
local onerror = s:onerror()
assert.same("function", type(onerror))
assert.same(onerror, s:onerror())
s:close()
c:close()
end)
local function simple_test(family, tls, client_version, server_version)
local cq = cqueues.new()
local options = {
family = family;
tls = tls;
version = server_version;
}
if family == cs.AF_UNIX then
local socket_path = os.tmpname()
finally(function()
os.remove(socket_path)
end)
options.path = socket_path
options.unlink = true
else
options.host = "localhost"
options.port = 0
end
local onstream = spy.new(function(s, stream)
assert.is_equal(1, s:connections())
stream:get_headers()
stream:shutdown()
s:close()
end)
options.onstream = onstream
local s = assert(http_server.listen(options))
assert.is_equal(0, s:connections())
assert(s:listen())
cq:wrap(function()
assert_loop(s)
end)
cq:wrap(function()
local client_path
local client_family, client_host, client_port = s:localname()
if client_family == cs.AF_UNIX then
client_path = client_host
client_host = nil
end
local client_options = {
family = client_family;
host = client_host;
port = client_port;
path = client_path;
tls = tls;
ctx = non_verifying_tls_context;
version = client_version;
}
local conn = assert(http_client.connect(client_options))
local stream = conn:new_stream()
local headers = http_headers.new()
headers:append(":authority", "myauthority")
headers:append(":method", "GET")
headers:append(":path", "/")
headers:append(":scheme", "http")
assert(stream:write_headers(headers, true))
stream:get_headers()
if server_version then
if conn.version == 1.1 then
-- 1.1 client might have 1.0 server
assert.same(server_version, stream.peer_version)
else
assert.same(server_version, conn.version)
end
end
conn:close()
end)
assert_loop(cq, TEST_TIMEOUT)
assert.truthy(cq:empty())
assert.spy(onstream).was.called()
assert.is_equal(0, s:connections())
end
it("works with plain http 1.1 using IP", function()
simple_test(cs.AF_INET, false, 1.1)
end)
it("works with https 1.1 using IP", function()
simple_test(cs.AF_INET, true, 1.1)
end)
it("works with plain http 2.0 using IP", function()
simple_test(cs.AF_INET, false, 2.0)
end);
(http_tls.has_alpn and it or pending)("works with https 2.0 using IP", function()
simple_test(cs.AF_INET, true, 2.0)
end)
--[[ TLS tests are pending for now as UNIX sockets don't automatically
generate a TLS context ]]
it("works with plain http 1.1 using UNIX socket", function()
simple_test(cs.AF_UNIX, false, 1.1)
end)
pending("works with https 1.1 using UNIX socket", function()
simple_test(cs.AF_UNIX, true, 1.1)
end)
it("works with plain http 2.0 using UNIX socket", function()
simple_test(cs.AF_UNIX, false, 2.0)
end);
pending("works with https 2.0 using UNIX socket", function()
simple_test(cs.AF_UNIX, true, 2.0)
end)
describe("pin server version", function()
it("works when set to http 1.0 without TLS", function()
simple_test(cs.AF_INET, false, nil, 1.0)
end)
it("works when set to http 1.1 without TLS", function()
simple_test(cs.AF_INET, false, nil, 1.1)
end)
it("works when set to http 1.0 with TLS", function()
simple_test(cs.AF_INET, true, nil, 1.0)
end)
it("works when set to http 1.1 with TLS", function()
simple_test(cs.AF_INET, true, nil, 1.1)
end)
-- This test doesn't seem to work on travis
pending("works when set to http 2.0 with TLS", function()
simple_test(cs.AF_INET, true, nil, 2.0)
end)
end);
(http_tls.has_alpn and it or pending)("works to set server version when alpn proto is not a normal http one", function()
local ctx = http_tls.new_client_context()
ctx:setAlpnProtos { "foo" }
simple_test(cs.AF_INET, ctx, nil, nil)
simple_test(cs.AF_INET, ctx, nil, 1.1)
simple_test(cs.AF_INET, ctx, 2.0, 2.0)
end)
it("taking socket from underlying connection is handled well by server", function()
local cq = cqueues.new()
local onstream = spy.new(function(server, stream)
local sock = stream.connection:take_socket()
server:close()
assert.same("test", sock:read("*a"))
sock:close()
end);
local server = assert(http_server.new {
tls = false;
onstream = onstream;
})
local s, c = ca.assert(cs.pair())
server:add_socket(s)
cq:wrap(function()
assert_loop(server)
end)
cq:wrap(function()
assert(c:write("test"))
assert(c:flush())
c:close()
end)
assert_loop(cq, TEST_TIMEOUT)
assert.truthy(cq:empty())
assert.spy(onstream).was.called()
end)
it("an idle http2 stream doesn't block the server", function()
local server = assert(http_server.new {
tls = false;
version = 2;
onstream = function(_, stream)
if stream.id == 1 then
stream:get_next_chunk()
else
assert.same(3, stream.id)
assert.same({}, {stream:get_next_chunk()})
local headers = http_headers.new()
headers:append(":status", "200")
assert(stream:write_headers(headers, true))
end
end;
})
local s, c = ca.assert(cs.pair())
server:add_socket(s)
local cq = cqueues.new()
cq:wrap(function()
assert_loop(server)
end)
cq:wrap(function()
local conn = assert(http_client.negotiate(c, {
version = 2;
}))
local headers = http_headers.new()
headers:append(":authority", "myauthority")
headers:append(":method", "GET")
headers:append(":path", "/")
headers:append(":scheme", "http")
local stream1 = assert(conn:new_stream())
assert(stream1:write_headers(headers, false))
local stream2 = assert(conn:new_stream())
assert(stream2:write_headers(headers, true))
assert(stream2:get_headers())
conn:close()
server:close()
end)
assert_loop(cq, TEST_TIMEOUT)
assert.truthy(cq:empty())
end)
it("times out clients if intra_stream_timeout is exceeded", function()
local server = assert(http_server.new {
tls = false;
onstream = function(_, stream)
assert(stream:get_headers())
local headers = http_headers.new()
headers:append(":status", "200")
assert(stream:write_headers(headers, true))
end;
intra_stream_timeout = 0.1;
})
local s, c = ca.assert(cs.pair())
server:add_socket(s)
local cq = cqueues.new()
cq:wrap(function()
assert_loop(server)
end)
cq:wrap(function()
local conn = assert(http_client.negotiate(c, {
version = 1.1;
}))
local headers = http_headers.new()
headers:append(":method", "GET")
headers:append(":scheme", "http")
headers:append(":path", "/")
headers:append(":authority", "foo")
-- Normal request
local stream1 = conn:new_stream()
assert(stream1:write_headers(headers, true))
assert(stream1:get_headers())
-- Wait for less than intra_stream_timeout: should work as normal
cqueues.sleep(0.05)
local stream2 = conn:new_stream()
assert(stream2:write_headers(headers, true))
assert(stream2:get_headers())
-- Wait for more then intra_stream_timeout: server should have closed connection
cqueues.sleep(0.2)
local stream3 = conn:new_stream()
assert.same(ce.EPIPE, select(3, stream3:write_headers(headers, true)))
end)
assert_loop(cq, TEST_TIMEOUT)
assert.truthy(cq:empty())
end)
it("allows pausing+resuming the server", function()
local s = assert(http_server.listen {
host = "localhost";
port = 0;
onstream = function(_, stream)
assert(stream:get_headers())
local headers = http_headers.new()
headers:append(":status", "200")
assert(stream:write_headers(headers, true))
end;
})
assert(s:listen())
local client_family, client_host, client_port = s:localname()
local client_options = {
family = client_family;
host = client_host;
port = client_port;
}
local headers = http_headers.new()
headers:append(":authority", "myauthority")
headers:append(":method", "GET")
headers:append(":path", "/")
headers:append(":scheme", "http")
local cq = cqueues.new()
cq:wrap(function()
assert_loop(s)
end)
local function do_req(timeout)
local conn = assert(http_client.connect(client_options))
local stream = assert(conn:new_stream())
assert(stream:write_headers(headers, true))
local ok, err, errno = stream:get_headers(timeout)
conn:close()
return ok, err, errno
end
cq:wrap(function()
s:pause()
assert.same(ce.ETIMEDOUT, select(3, do_req(0.1)))
s:resume()
assert.truthy(do_req())
s:pause()
assert.same(ce.ETIMEDOUT, select(3, do_req(0.1)))
s:resume()
assert.truthy(do_req())
s:close()
end)
assert_loop(cq, TEST_TIMEOUT)
assert.truthy(cq:empty())
end)
it("shouldn't throw an error calling :listen() after :close()", function()
local s = assert(http_server.listen {
host = "localhost";
port = 0;
onstream = function() end;
})
s:close()
s:listen()
end)
it("shouldn't throw an error calling :localname() after :close()", function()
local s = assert(http_server.listen {
host = "localhost";
port = 0;
onstream = function() end;
})
s:close()
s:localname()
end)
end)