Versions: Julia 1.12.7, HTTP.jl 2.6.7. The same code is on master (d6f049a).
What happens
The HTTP/1.1 client writes the Host header field after every other header, so a request typically looks like:
GET / HTTP/1.1\r\n
User-Agent: example\r\n
Accept: */*\r\n
Accept-Encoding: gzip, deflate\r\n
Host: 127.0.0.1:18798\r\n
Content-Length: 0\r\n
\r\n
RFC 9112 §3.2: "A user agent that sends Host SHOULD send it as the first field line after the request-line." curl, Go's net/http and Python's http.client all send Host first.
This is not only cosmetic. With an otherwise identical request, a CDN-fronted HTTPS site answered 403 when Host came last and 200 when it came first.
Reproducer
A socket server that prints the raw request bytes:
python3 - <<'EOF' &
import socket
s = socket.socket(); s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("127.0.0.1", 18798)); s.listen(1)
c, _ = s.accept()
data = b""
while b"\r\n\r\n" not in data:
data += c.recv(4096)
print(data.decode().replace("\r\n", "\\r\\n\n"))
c.sendall(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok")
c.close()
EOF
using HTTP
HTTP.get("http://127.0.0.1:18798/", ["User-Agent" => "example", "Accept" => "*/*"])
Cause
_prepare_request_headers_for_write adds Host with setheader, which appends a key that is not already present, so Host lands after the caller's headers and the defaults added before it.
Suggested fix
When writing an HTTP/1.1 request, emit Host as the first field line, whether it came from request.host or from the caller's headers.
Workaround
Pass Host explicitly as the first request header. A caller-supplied Host keeps its position:
HTTP.get(url, ["Host" => "example.com", "User-Agent" => "example"])
Versions: Julia 1.12.7, HTTP.jl 2.6.7. The same code is on
master(d6f049a).What happens
The HTTP/1.1 client writes the
Hostheader field after every other header, so a request typically looks like:RFC 9112 §3.2: "A user agent that sends Host SHOULD send it as the first field line after the request-line." curl, Go's
net/httpand Python'shttp.clientall sendHostfirst.This is not only cosmetic. With an otherwise identical request, a CDN-fronted HTTPS site answered 403 when
Hostcame last and 200 when it came first.Reproducer
A socket server that prints the raw request bytes:
Cause
_prepare_request_headers_for_writeaddsHostwithsetheader, which appends a key that is not already present, soHostlands after the caller's headers and the defaults added before it.Suggested fix
When writing an HTTP/1.1 request, emit
Hostas the first field line, whether it came fromrequest.hostor from the caller's headers.Workaround
Pass
Hostexplicitly as the first request header. A caller-suppliedHostkeeps its position: