Skip to content

Commit 2513b20

Browse files
committed
http/websocket: fix mid-size frames sometimes failing to be received (fixes #140)
1 parent 169c1a7 commit 2513b20

File tree

2 files changed

+56
-17
lines changed

2 files changed

+56
-17
lines changed

http/websocket.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ local function read_frame(sock, deadline)
241241
if frame.length == 126 then
242242
extra_fill_unget = assert(sock:xread(2, "b", 0))
243243
frame.length = sunpack(">I2", extra_fill_unget)
244-
fill_length = fill_length - 2
244+
fill_length = fill_length - 2 + frame.length
245245
elseif frame.length == 127 then
246246
extra_fill_unget = assert(sock:xread(8, "b", 0))
247247
frame.length = sunpack(">I8", extra_fill_unget)

spec/websocket_spec.lua

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -243,23 +243,62 @@ describe("http.websocket module two sided tests", function()
243243
assert.truthy(cq:empty())
244244
end)
245245
local function send_receive_test(name, data, data_type)
246-
it(name, function()
247-
data_type = data_type or "text"
248-
local cq = cqueues.new()
249-
local c, s = new_pair()
250-
cq:wrap(function()
251-
assert(c:send(data, data_type))
252-
assert.same({data, data_type}, {assert(c:receive())})
253-
assert(c:close())
246+
local function do_test(bad_connection)
247+
local bname = name
248+
if bad_connection then
249+
bname = bname .. ", even if the connection is really bad"
250+
end
251+
it(bname, function()
252+
data_type = data_type or "text"
253+
if bad_connection then
254+
local real_xwrite
255+
local fragments = 100
256+
local delay = 1 / fragments -- Aim for 1s.
257+
real_xwrite = cs.interpose("xwrite", function(self, str, mode, timeout)
258+
if mode ~= "bn" then -- Not interesting, don't throttle.
259+
return real_xwrite(self, str, mode, timeout)
260+
end
261+
local deadline
262+
if timeout then
263+
deadline = cqueues.monotime() + timeout
264+
end
265+
local ok, op, why
266+
local nbytes = math.ceil(#str / fragments)
267+
local before_first = 0
268+
repeat
269+
-- Test range at the end to ensure that real_xwrite is called at least once.
270+
-- We rely on the fact here that :sub sanitizes the input range.
271+
ok, op, why = real_xwrite(self, str:sub(before_first + 1, before_first + nbytes), mode, deadline and (deadline - cqueues.monotime()))
272+
if not ok then
273+
break
274+
end
275+
before_first = before_first + nbytes
276+
cqueues.sleep(delay)
277+
until before_first > #str
278+
return ok, op, why
279+
end)
280+
finally(function()
281+
cs.interpose("xwrite", real_xwrite)
282+
end)
283+
end
284+
local cq = cqueues.new()
285+
local c, s = new_pair()
286+
cq:wrap(function()
287+
assert(c:send(data, data_type))
288+
assert.same({data, data_type}, {assert(c:receive())})
289+
assert(c:close())
290+
end)
291+
cq:wrap(function()
292+
assert.same({data, data_type}, {assert(s:receive())})
293+
assert(s:send(data, data_type))
294+
assert(s:close())
295+
end)
296+
assert_loop(cq, TEST_TIMEOUT)
297+
assert.truthy(cq:empty())
254298
end)
255-
cq:wrap(function()
256-
assert.same({data, data_type}, {assert(s:receive())})
257-
assert(s:send(data, data_type))
258-
assert(s:close())
259-
end)
260-
assert_loop(cq, TEST_TIMEOUT)
261-
assert.truthy(cq:empty())
262-
end)
299+
end
300+
do_test(false)
301+
do_test(true)
263302
end
264303
send_receive_test("works with small size frames", "f")
265304
send_receive_test("works with medium size frames", ("f"):rep(200))

0 commit comments

Comments
 (0)