Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
mislabeled `TLSHandshakeError`; that type is now reserved for actual
connection-setup failures. `HTTP.isrecoverable` classifies both wrappers by
their underlying cause. ([#1353])
- Added `setheader!`, `appendheader!` and `removeheader!` as the conventional
mutating-name spellings of `setheader`, `appendheader` and `removeheader`.
Each pair is the same function (extending one name extends the other), the
historical names are not deprecated, and all six are `public`. ([#1277])

### Fixed
- Restored HTTP and WebSocket server task scheduling to Julia's `:interactive`
Expand Down Expand Up @@ -864,5 +868,6 @@ See changes for 0.9.15: this release is equivalent to 0.9.15 with [#752] reverte
[#1119]: https://github.com/JuliaWeb/HTTP.jl/issues/1119
[#1126]: https://github.com/JuliaWeb/HTTP.jl/issues/1126
[#1127]: https://github.com/JuliaWeb/HTTP.jl/issues/1127
[#1277]: https://github.com/JuliaWeb/HTTP.jl/issues/1277
[#1342]: https://github.com/JuliaWeb/HTTP.jl/issues/1342
[#1353]: https://github.com/JuliaWeb/HTTP.jl/issues/1353
3 changes: 3 additions & 0 deletions docs/src/api/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@ HTTP.headers
HTTP.hasheader
HTTP.headercontains
HTTP.setheader
HTTP.setheader!
HTTP.defaultheader!
HTTP.appendheader
HTTP.appendheader!
HTTP.removeheader
HTTP.removeheader!
HTTP.mkheaders
HTTP.get_request_context
HTTP.set_deadline!
Expand Down
6 changes: 3 additions & 3 deletions src/HTTP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ include("http_websockets.jl")
:RequestEvent, :RequestRetryError, :Response, :ResponseHeadEvent, :RetryBucket,
:RetryEvent, :RetrySkippedEvent, :SSEEvent, :SSEStream, :Server, :StatusError, :Stream,
:TLSHandshakeError, :TLSTransportError,
:TimeoutError, :TooManyRedirectsError, :Transport, :addtrailer, :appendheader,
:TimeoutError, :TooManyRedirectsError, :Transport, :addtrailer, :appendheader, :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, :peeraddr, :port, :post,
:put, :query, :read_request, :removeheader, :request, :retry_attempts, :roundtrip!,
:serve, :serve!, :servecontent, :servefile, :set_deadline!, :setheader, :setstatus,
:put, :query, :read_request, :removeheader, :removeheader!, :request, :retry_attempts, :roundtrip!,
:serve, :serve!, :servecontent, :servefile, :set_deadline!, :setheader, :setheader!, :setstatus,
:sse_stream, :startwrite, :streamhandler, :trailers, :write_request!, :write_response!,
))
end
Expand Down
49 changes: 47 additions & 2 deletions src/http_core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,8 @@ end

Replace all stored values for `key` with `value`, preserving the first matching
position if the key already exists and appending it otherwise. Returns the
mutated `headers`.
mutated `headers`. [`setheader!`](@ref) is the same function under the
conventional mutating-name spelling.
"""
function setheader(headers::Headers, header::Pair)
item = _header_pair(header.first, header.second)
Expand Down Expand Up @@ -948,7 +949,8 @@ If the previous stored header has the same name and the key is not
`Set-Cookie`, the value is merged into the previous entry with a comma
(no whitespace), as permitted by RFC 9110 §5.3 and required by common
request-signing canonicalizations.
Otherwise a new pair is appended.
Otherwise a new pair is appended. [`appendheader!`](@ref) is the same
function under the conventional mutating-name spelling.
"""
function appendheader(headers::Headers, header::Pair)
item = _header_pair(header.first, header.second)
Expand All @@ -971,6 +973,8 @@ end
removeheader(headers, key) -> Headers

Remove every stored header for `key` and return the mutated `headers`.
[`removeheader!`](@ref) is the same function under the conventional
mutating-name spelling.
"""
function removeheader(headers::Headers, key::AbstractString)
canon = canonical_header_key(key)
Expand All @@ -990,6 +994,47 @@ function removeheader(headers::Headers, key::AbstractString)
return headers
end

# Conventional mutating-name spellings (#1277). Each is the *same* function as
# the historical name (extending one extends the other), so the old names stay
# available without deprecation; new code should prefer the `!` form since
# every method mutates its first argument.
"""
setheader!(headers, key => value) -> Headers
setheader!(headers, key, value) -> Headers
setheader!(message, key => value) -> Headers
setheader!(message, key, value) -> Headers

Replace all stored values for `key` with `value` and return the mutated
`headers`, or the mutated `message` for the `Request`/`Response` forms. This
is the same function as [`setheader`](@ref) under the conventional
mutating-name spelling.
"""
const setheader! = setheader

"""
appendheader!(headers, key => value) -> Headers
appendheader!(headers, key, value) -> Headers
appendheader!(message, key => value) -> Headers
appendheader!(message, key, value) -> Headers

Append a value for `key` without removing existing values and return the
mutated `headers`, or the mutated `message` for the `Request`/`Response`
forms. This is the same function as [`appendheader`](@ref) under the
conventional mutating-name spelling.
"""
const appendheader! = appendheader

"""
removeheader!(headers, key) -> Headers
removeheader!(message, key) -> Headers

Remove every stored header for `key` and return the mutated `headers`, or the
mutated `message` for the `Request`/`Response` forms. This is the same
function as [`removeheader`](@ref) under the conventional mutating-name
spelling.
"""
const removeheader! = removeheader

@inline function _ascii_lowercase_string(s::AbstractString)::String
chars = Vector{Char}(undef, ncodeunits(s))
i = 1
Expand Down
25 changes: 25 additions & 0 deletions test/http_core_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -514,3 +514,28 @@ end
@test !endswith(empty_response_plain, "\r\n")
@test !endswith(empty_response_plain, "\n")
end

@testset "Mutating-name header aliases (#1277)" begin
@test HT.setheader! === HT.setheader
@test HT.appendheader! === HT.appendheader
@test HT.removeheader! === HT.removeheader
headers = HT.Headers()
@test HT.setheader!(headers, "X-Test" => "1") === headers
@test HT.appendheader!(headers, "X-Test", "2") === headers
@test HT.headers(headers, "X-Test") == ["1,2"]
@test HT.setheader!(headers, "X-Other", "a") === headers
@test HT.removeheader!(headers, "x-test") === headers
@test !HT.hasheader(headers, "X-Test")
@test HT.header(headers, "X-Other") == "a"
request = HT.Request("GET", "/"; host = "example.test", body = HT.EmptyBody(), content_length = 0)
@test HT.setheader!(request, "X-Req" => "1") === request
@test HT.appendheader!(request, "X-Req", "2") === request
@test HT.header(request, "X-Req") == "1,2"
@test HT.removeheader!(request, "X-Req") === request
@test !HT.hasheader(request.headers, "X-Req")
if isdefined(Base, :ispublic)
@test Base.ispublic(HTTP, :setheader!)
@test Base.ispublic(HTTP, :appendheader!)
@test Base.ispublic(HTTP, :removeheader!)
end
end
Loading