From 641fbdba207f40f440d97f0a9ce0b08eb20b8a21 Mon Sep 17 00:00:00 2001 From: Fredrik Ekre Date: Tue, 5 Dec 2023 14:29:12 +0100 Subject: [PATCH] Close connection without response on status code 444 This patch adds the ability for stream handlers to bail out on a request and tell the internal HTTP.jl code to simply close the connection and to ignore any remaining data to be read or written. This is done by setting the response status code to the non-standard value 444, just like in nginx. If the status code is set to 444 when the request handler returns, the internal server code simply closes the connection. In particular this will skip calling `closeread` and `closewrite`, which both do some "consistency checks" that can't be avoided by using something like ```julia HTTP.setstatus(http, 444) close(http.stream) ``` inside the request handler function directly. --- src/Servers.jl | 21 ++++++++++++++------- test/server.jl | 13 +++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/Servers.jl b/src/Servers.jl index 17fa5955c..b0aa04c30 100644 --- a/src/Servers.jl +++ b/src/Servers.jl @@ -448,15 +448,22 @@ function handle_connection(f, c::Connection, listener, readtimeout, access_log) # invokelatest becuase the perf is negligible, but this makes live-editing handlers more Revise friendly @debugv 1 "invoking handler" Base.invokelatest(f, http) - # If `startwrite()` was never called, throw an error so we send a 500 and log this - if isopen(http) && !iswritable(http) + if request.response.status == 444 + # If the response code is set to 444 simply close the + # connection similar to nginx + close(c) + elseif isopen(http) && !iswritable(http) + # If `startwrite()` was never called, throw an error so we send a 500 and log this error("Server never wrote a response") + else + @debugv 1 "closeread" + closeread(http) + @debugv 1 "closewrite" + closewrite(http) + if c.state != CLOSING + c.state = IDLE + end end - @debugv 1 "closeread" - closeread(http) - @debugv 1 "closewrite" - closewrite(http) - c.state = IDLE catch e # The remote can close the stream whenever it wants to, but there's nothing # anyone can do about it on this side. No reason to log an error in that case. diff --git a/test/server.jl b/test/server.jl index af1a98741..526929005 100644 --- a/test/server.jl +++ b/test/server.jl @@ -226,6 +226,19 @@ end # @testset @test TEST_COUNT[] == 4 end # @testset +@testset "444" begin + server = HTTP.listen!() do http + HTTP.setstatus(http, 444) + end + port = server.listener.hostport + try + errr = try HTTP.get("http://localhost:$(port)", retry=false) catch e e end + @test errr isa HTTP.RequestError && errr.error isa EOFError + finally + close(server) + end +end + @testset "access logging" begin local handler = (http) -> begin if http.message.target == "/internal-error"