From ddab2835c583d45dec62680ca8d3cbde55e0bae6 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 22 Aug 2023 23:30:20 +1000 Subject: [PATCH] http/h1_stream: handle EOF when `body_read_type==length` If a client closes the connection before sending the expected number of bytes then return `EPIPE`. This fixes a potential infinite draining loop when trying to trying to `:shutdown()` a stream. --- http/h1_stream.lua | 2 ++ spec/h1_stream_spec.lua | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/http/h1_stream.lua b/http/h1_stream.lua index b2469a14..b0ca8219 100644 --- a/http/h1_stream.lua +++ b/http/h1_stream.lua @@ -861,6 +861,8 @@ function stream_methods:read_next_chunk(timeout) if chunk ~= nil then self.body_read_left = length_n - #chunk end_stream = (self.body_read_left == 0) + elseif err == nil then + return nil, ce.strerror(ce.EPIPE), ce.EPIPE end elseif length_n == 0 then chunk = "" diff --git a/spec/h1_stream_spec.lua b/spec/h1_stream_spec.lua index f9cfea94..1303f946 100644 --- a/spec/h1_stream_spec.lua +++ b/spec/h1_stream_spec.lua @@ -295,6 +295,33 @@ describe("http1 stream", function() server:close() client:close() end) + it("Doesn't hang when a content-length delimited stream is closed", function() + local server, client = new_pair(1.1) + local cq = cqueues.new() + cq:wrap(function() + local stream = client:new_stream() + local headers = new_headers() + headers:append(":method", "GET") + headers:append(":scheme", "http") + headers:append(":authority", "myauthority") + headers:append(":path", "/a") + assert(stream:write_headers(headers, true)) + end) + cq:wrap(function() + local stream = server:get_next_incoming_stream() + assert(stream:get_headers()) + local res_headers = new_headers() + res_headers:append(":status", "200") + res_headers:append("content-length", "100") + assert(stream:write_headers(res_headers, false)) + assert(stream:write_chunk("foo", false)) + assert(stream:shutdown()) + end) + assert_loop(cq, TEST_TIMEOUT) + assert.truthy(cq:empty()) + server:close() + client:close() + end) it("allows pipelining", function() local server, client = new_pair(1.1) local cq = cqueues.new()