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
28 changes: 28 additions & 0 deletions src/HTTP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,34 @@ using .Handlers
include("http_sse.jl")
include("http_websockets.jl")

# Declare the documented, non-exported public API via Julia's `public` mechanism
# (Julia 1.11+), so tooling and downstream code can distinguish supported entry
# points (accessed as `HTTP.name`) from internals. Already-exported names
# (e.g. `WebSockets`, `escape`, `Form`) are public by virtue of `export` and are
# not repeated here. The version guard keeps the source parseable on Julia 1.10
# (the `public` keyword does not exist there); `eval(Expr(:public, …))` is valid
# syntax on all versions, so only the evaluation is gated.
@static if VERSION >= v"1.11.0-DEV.469"
Core.eval(@__MODULE__, Expr(:public,
Symbol("@client"),
:AbstractBody, :AddressInUseError, :CallbackBody, :CanceledError, :Client,
:ConnectError, :DNSError, :DoneEvent, :HTTP2Settings, :HTTPError, :HTTPTimeoutError,
:Handlers, :Headers, :NoProxy, :ParseError, :ProtocolError, :ProxyConfig,
:ProxyFromEnvironment, :ProxyURL, :RedirectEvent, :Request, :RequestContext,
:RequestEvent, :RequestRetryError, :Response, :ResponseHeadEvent, :RetryBucket,
:RetryEvent, :SSEEvent, :SSEStream, :Server, :StatusError, :Stream, :TLSHandshakeError,
:TimeoutError, :TooManyRedirectsError, :Transport, :addtrailer, :appendheader,
:body_close!, :body_closed, :body_read!, :cancel!, :canceled, :canonical_header_key,
:close_idle_connections!, :defaultheader!, :delete, :do!, :expired, :fileserver,
:forceclose, :get, :get!, :get_request_context, :hasheader, :head, :header,
:headercontains, :headers, :idle_connection_count, :isaborted, :isrecoverable,
:listen, :listen!, :mkheaders, :nobody, :open, :options, :patch, :port, :post, :put,
:read_request, :removeheader, :request, :retry_attempts, :roundtrip!, :serve, :serve!,
:servecontent, :servefile, :set_deadline!, :setheader, :setstatus, :sse_stream,
:startwrite, :streamhandler, :trailers, :write_request!, :write_response!,
))
end

if ccall(:jl_generating_output, Cint, ()) == 1
include("precompile.jl")
end
Expand Down
6 changes: 6 additions & 0 deletions src/http_handlers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -483,4 +483,10 @@ Retrieve any parsed cookies from a request context.
"""
getcookies(req) = get(() -> Cookie[], req.context, :cookies)

# `handlertimeout` is documented public API but not exported; mark it public on
# Julia versions that support the mechanism (the source stays parseable on 1.10).
@static if VERSION >= v"1.11.0-DEV.469"
Core.eval(@__MODULE__, Expr(:public, :handlertimeout))
end

end
10 changes: 10 additions & 0 deletions src/http_websockets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1670,4 +1670,14 @@ function listen(handler::Function, host::AbstractString="127.0.0.1", port::Integ
return server
end

# Documented public API of this submodule (accessed as `HTTP.WebSockets.name`).
# Version-gated like the main module so the source parses on Julia 1.10.
@static if VERSION >= v"1.11.0-DEV.469"
Core.eval(@__MODULE__, Expr(:public,
:CloseFrameBody, :Conn, :Server, :WebSocket, :WebSocketError, :forceclose,
:isupgrade, :listen, :listen!, :open, :ping, :pong, :receive, :send, :serve!,
:server_addr, :upgrade,
))
end

end
55 changes: 55 additions & 0 deletions test/public_api_tests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Test
using HTTP

# Every name documented in docs/src/api/*.md must be reachable as public API —
# either `export`ed or declared `public` (Julia 1.11+). This guards against a
# newly-documented entry point being left unmarked, and against the `public`
# list drifting from the docs.
@testset "public API declarations match the documented API" begin
if VERSION >= v"1.11.0-DEV.469"
apidir = joinpath(dirname(dirname(pathof(HTTP))), "docs", "src", "api")
isdir(apidir) || @warn "api docs dir not found; skipping" apidir
checked = 0
for file in (isdir(apidir) ? readdir(apidir; join=true) : String[])
endswith(file, ".md") || continue
inblock = false
for raw in eachline(file)
line = strip(raw)
if startswith(line, "```@docs")
inblock = true; continue
elseif line == "```"
inblock = false; continue
end
(inblock && startswith(line, "HTTP")) || continue
parts = split(line, '.')
length(parts) >= 2 || continue # skip the bare `HTTP` entry
# walk the module path (e.g. HTTP.WebSockets.send -> mod=WebSockets, name=send)
mod = HTTP
resolved = true
for p in parts[2:(end - 1)]
sym = Symbol(p)
if isdefined(mod, sym) && getfield(mod, sym) isa Module
mod = getfield(mod, sym)
else
resolved = false; break
end
end
resolved || continue
name = Symbol(parts[end])
isdefined(mod, name) || continue # (macros resolve fine: @client)
ispub = Base.ispublic(mod, name) || (name in Base.names(mod; all = false))
@test ispub
ispub || @info "documented but not public/exported" mod name
checked += 1
end
end
@test checked > 80 # the bulk of the documented surface was actually checked
else
@test true # `public` unsupported before 1.11; nothing to assert
end

# Internals must stay private regardless of Julia version.
@test !Base.ispublic(HTTP, :_retryable_request_error)
@test !Base.ispublic(HTTP, :_normalize_local_addr)
@test !Base.ispublic(HTTP.WebSockets, :_ws_mask_into!)
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ test_files = [
"http_handlers_tests.jl",
"http_websocket_codec_tests.jl",
"http_websocket_pmce_tests.jl",
"public_api_tests.jl",
"http_websocket_client_tests.jl",
"http_websocket_server_tests.jl",
"http_websocket_integration_tests.jl",
Expand Down
Loading