Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions src/http2_server.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,6 @@ end
return ProtocolError("HTTP/2 connection is closed")
end

mutable struct _H2ServerConnControl
@atomic shutdown_requested::Bool
@atomic goaway_sent::Bool
@atomic graceful_last_stream_id::UInt32
end

function _H2ServerConnControl()
return _H2ServerConnControl(false, false, UInt32(0))
end

mutable struct _H2ServerBody <: AbstractBody
conn::Union{TCP.Conn,TLS.Conn}
write_lock::ReentrantLock
Expand Down Expand Up @@ -1743,7 +1733,7 @@ function _serve_h2_conn!(server::Server, tracked::_ServerConn, reader_source)::N
_write_frame_h2_server_threadsafe!(write_lock, conn, WindowUpdateFrame(UInt32(0), UInt32(server.http2_settings.connection_window_size - _H2_DEFAULT_WINDOW_SIZE)), _server_write_deadline_ns(server))
end
_clear_deadlines!(conn)
_set_conn_shutdown_hook!(tracked, () -> _request_h2_conn_shutdown!(conn, write_lock, conn_control))
_set_conn_shutdown_hook!(tracked, _H2ShutdownHook(write_lock, conn_control))
_set_conn_state!(tracked, _ConnState.IDLE)
while true
_server_shutting_down(server) && return nothing
Expand Down
12 changes: 9 additions & 3 deletions src/http_core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2010,10 +2010,15 @@ struct _IncomingResponse{B<:AbstractBody}
rawbody::B
end

# Default for `Response`'s `body` keyword. A singleton (rather than the
# `nobody` vector) lets inference fold the "no keyword body" check, so a
# response built from a positional body has one concrete type.
struct _NoBodyKeyword end

function Response(
status::Integer,
response_body::B=EmptyBody();
body=nobody,
body=_NoBodyKeyword(),
reason::AbstractString="",
headers=Headers(),
trailers=Headers(),
Expand All @@ -2026,9 +2031,10 @@ function Response(
previous::Union{Nothing,Response}=nothing,
redirect_count::Integer=0,
) where {B}
actual_body = body === nobody ? _response_body_arg(response_body) : _response_body_arg(body)
positional = body isa _NoBodyKeyword || body === nobody
actual_body = positional ? _response_body_arg(response_body) : _response_body_arg(body)
actual_content_length = if content_length < 0
body === nobody ? _compat_body_length(response_body) : _compat_body_length(body)
positional ? _compat_body_length(response_body) : _compat_body_length(body)
else
Int64(content_length)
end
Expand Down
24 changes: 21 additions & 3 deletions src/http_server.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,28 @@ end
IDENTITY = 4
end

mutable struct _H2ServerConnControl
@atomic shutdown_requested::Bool
@atomic goaway_sent::Bool
@atomic graceful_last_stream_id::UInt32
end

function _H2ServerConnControl()
return _H2ServerConnControl(false, false, UInt32(0))
end

# HTTP/2 is the only protocol with a shutdown notification. Store its concrete
# state rather than a closure, so graceful shutdown is statically dispatched
# under juliac --trim.
struct _H2ShutdownHook
write_lock::ReentrantLock
control::_H2ServerConnControl
end

mutable struct _ServerConn
conn::Union{TCP.Conn,TLS.Conn}
lock::ReentrantLock
shutdown_hook::Union{Nothing,Function}
shutdown_hook::Union{Nothing,_H2ShutdownHook}
@atomic state::_ConnState.T
@atomic state_unix_sec::Int64
end
Expand Down Expand Up @@ -425,7 +443,7 @@ end
return nothing
end

function _set_conn_shutdown_hook!(conn::_ServerConn, hook::Union{Nothing,Function})::Nothing
function _set_conn_shutdown_hook!(conn::_ServerConn, hook::Union{Nothing,_H2ShutdownHook})::Nothing
lock(conn.lock)
try
conn.shutdown_hook = hook
Expand All @@ -444,7 +462,7 @@ function _notify_conn_shutdown!(conn::_ServerConn)::Nothing
unlock(conn.lock)
end
hook === nothing && return nothing
hook()
_request_h2_conn_shutdown!(conn.conn, hook.write_lock, hook.control)
return nothing
end

Expand Down
4 changes: 3 additions & 1 deletion src/http_server_files.jl
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,9 @@ function servecontent(
etag::Union{Nothing,AbstractString}=nothing,
headers=Headers(),
allow_ranges::Bool=true,
)::Response
)
# No `::Response` return annotation: it widens every caller to the abstract
# type, which juliac --trim cannot dispatch on when writing the response.
response_headers = copy(mkheaders(headers))
modtime_value = modtime === nothing ? nothing : _servecontent_modtime(modtime::DateTime)
etag === nothing || setheader(response_headers, "ETag", String(etag))
Expand Down
3 changes: 2 additions & 1 deletion test/http_trim_client_server.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ function run_http_trim_client_server()::Nothing
startswith(response, "HTTP/1.1 200 OK\r\n") || error("expected 200 response, got $(repr(response))")
occursin("\r\n\r\nhello", response) || error("unexpected response body: $(repr(response))")
finally
server === nothing || trim_close_http_server(server::HT.Server)
# graceful close: also notifies open HTTP/2 connections to shut down
server === nothing || close(server::HT.Server)
yield()
GC.gc()
HTTP.@try_ignore Reseau.IOPoll.shutdown!()
Expand Down
6 changes: 6 additions & 0 deletions test/http_trim_open_fileserver.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ function run_http_trim_open_fileserver()::Nothing
sniffed = HT.servecontent(HT.Request("GET", "/"), collect(codeunits("<html><p>hi</p></html>")))
HT.header(sniffed, "Content-Type") == "text/html; charset=utf-8" || error("unexpected sniffed content type")
HT.sniff("{\"ok\":true}") == "application/json; charset=utf-8" || error("unexpected sniffed JSON content type")

# servecontent returns concrete response types, so callers can read the
# body (here a byte range) without dynamic dispatch.
ranged = HT.servecontent(HT.Request("GET", "/", ["Range" => "bytes=0-3"]), collect(codeunits("<html>")))
ranged.status == 206 || error("expected range response status 206")
trim_body_string(ranged.body) == "<htm" || error("unexpected range response body")
finally
trim_shutdown_runtime()
end
Expand Down
Loading