Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
c7f244c
[breaking]: HTTP 2.0 rewrite
quinnj Mar 21, 2026
7bfc881
fix(headers): harden repeated header semantics
quinnj Mar 21, 2026
c72c995
fix(http1): harden outgoing header serialization
quinnj Mar 21, 2026
216c484
fix(http1): tighten framing and request validation
quinnj Mar 22, 2026
6e7f076
fix(http2): validate client header blocks
quinnj Mar 22, 2026
a2bfcb3
fix(http2): fragment header blocks and reuse HPACK state
quinnj Mar 22, 2026
2a84924
fix(http2): bound HPACK and header sizes
quinnj Mar 22, 2026
326f394
fix(http2): honor peer settings and stream caps
quinnj Mar 22, 2026
16d9cd5
fix(http2): support legal trailer lifecycles
quinnj Mar 22, 2026
2a0ff5f
fix(http2): reject invalid framer stream ids
quinnj Mar 22, 2026
661e203
fix(http1): close transport parity gaps
quinnj Mar 22, 2026
687a4a7
fix(proxy): harden CGI env handling
quinnj Mar 22, 2026
86398c3
fix(http1): suppress HEAD response bodies
quinnj Mar 22, 2026
c27225b
test(transport): wait for async request-body close
quinnj Mar 22, 2026
e4b7f68
fix(websocket): validate handshake keys and RSV bits
quinnj Mar 22, 2026
45f44bc
docs(scope): document deferred Go parity areas
quinnj Mar 22, 2026
f6ac656
feat(client): accept URIs and refine writes
quinnj Mar 23, 2026
69d8a75
feat(client): add verbose request logging
quinnj Mar 23, 2026
2276e4b
feat(timeouts): add request timeout plumbing
quinnj Mar 23, 2026
688f733
fix(timeouts): enforce connect and header phases
quinnj Mar 23, 2026
4770bc6
fix(timeouts): enforce read and write idles
quinnj Mar 23, 2026
8ba2511
docs(timeouts): clarify connect timeout scope
quinnj Mar 23, 2026
92d9df7
docs(timeouts): update guides and migration notes
quinnj Mar 23, 2026
e9b41e4
feat(server): add request body limiting middleware
quinnj Mar 23, 2026
5a32ce5
feat(server): add servecontent with conditionals
quinnj Mar 23, 2026
9f02b9b
feat(server): add static file handlers
quinnj Mar 23, 2026
7bc9deb
feat(handlers): add request handler timeouts
quinnj Mar 23, 2026
c2e37ca
feat(client): add @client middleware macro
quinnj Mar 24, 2026
e0591e4
feat(display): render requests and responses
quinnj Mar 24, 2026
6feb689
feat(precompile): add package workload
quinnj Mar 24, 2026
3d137c6
feat(precompile): broaden package workload
quinnj Mar 24, 2026
2fd53c1
test(precompile): add shared workload smoke test
quinnj Mar 24, 2026
209e056
test(trim): add minimal live exchange workload
quinnj Mar 25, 2026
6a2fc9c
fix(precompile): unify workload and readiness
quinnj Mar 27, 2026
1c028cd
Snapshot trim verifier burndown progress
quinnj Apr 1, 2026
885e3a0
Redesign H1 client response body handling
quinnj Apr 1, 2026
81d052f
http: simplify body handling and trim coverage
quinnj Apr 3, 2026
f5d3d5d
http: simplify trim-friendly client and server paths
quinnj Apr 7, 2026
cc1e85a
http: add request tracing and verbose hooks
quinnj Apr 7, 2026
c14e3fa
http: restore CI docs and test coverage
quinnj Apr 7, 2026
4dc68c0
test: stabilize raw HTTP server reads
quinnj Apr 7, 2026
4baf95b
test: relax Windows trim compile timeout
quinnj Apr 7, 2026
69d7a65
test: raise trim runtime timeout on Windows
quinnj Apr 7, 2026
d7c5a84
test: tolerate websocket trim runtime on Windows
quinnj Apr 7, 2026
60b3654
http: support text response bodies in servers
quinnj Apr 8, 2026
ae5cab9
test: tolerate h2 trailer shutdown ordering
quinnj Apr 8, 2026
0c7ffbe
test: tolerate windows trim compile timeouts
quinnj Apr 8, 2026
84a8345
server: add fileserver SPA fallback
quinnj Apr 8, 2026
952134a
http: make fileserver trim-friendly
quinnj Apr 8, 2026
44dc963
ci: bound Windows precompile hangs
quinnj Apr 8, 2026
ebbc8db
test: relax Windows handler timeout budget
quinnj Apr 8, 2026
dd72561
precompile: bound HTTP workload hangs
quinnj Apr 8, 2026
29effcd
test: extend fileserver trim timeout on Windows
quinnj Apr 8, 2026
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
63 changes: 18 additions & 45 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
name: CI

on:
pull_request:
branches:
- master
push:
branches:
- master
tags: '*'
defaults:
run:
shell: bash
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
branches: [main, master]
tags: ["*"]
pull_request:
release:

jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
Expand All @@ -21,61 +15,40 @@ jobs:
fail-fast: false
matrix:
version:
- 'min'
- 'lts'
- '1.9' # test because we know there was a precompilation issue on 1.9 issues/1202
- '1' # automatically expands to the latest stable 1.x release of Julia
- 'nightly'
- '1'
- 'pre'
os:
- ubuntu-latest
- macOS-latest
- windows-latest
arch:
- default
- x64
include:
- os: windows-latest
version: '1'
arch: x86
exclude:
- os: macOS-latest
version: 'min' # no apple silicon support for Julia 1.6
arch: aarch64
version: 1
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: julia-actions/cache@v2
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
env:
PIE_SOCKET_API_KEY: ${{ secrets.PIE_SOCKET_API_KEY }}
JULIA_VERSION: ${{ matrix.version }}
JULIA_NUM_THREADS: 2
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v5
with:
files: lcov.info
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.CODECOV_TOKEN }}
docs:
name: Documentation
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: '1'
- run: |
julia --project=docs -e '
using Pkg
Pkg.develop(PackageSpec(path=pwd()))
Pkg.instantiate()'
- run: |
julia --project=docs -e '
using Documenter: doctest
using HTTP
doctest(HTTP)'
- run: julia --project=docs docs/make.jl
- uses: actions/checkout@v6
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-docdeploy@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
25 changes: 23 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Changed
- Documented the intentionally deferred Go-parity areas for the 2.x release
line, including HTTP/2 server push, `Pusher`, `ResponseController` /
hijack-style control APIs, full `httptrace` parity, and full `net/url` /
`ServeMux` parity.

## [v2.0.0] - 2026-03-18
### Changed
- The package now presents a curated 2.0 public API built around `Request`,
`Response`, `Stream`, `Client`, `Transport`, and explicit body types on top
of `Reseau`.
- High-level request helpers materialize `Response.body::Vector{UInt8}` by
default. Use `response_stream` or `HTTP.open` when you want streaming
control.
- `Response.status_code` has been renamed to `status`.
- Internal pooling, routing, HPACK, and explicit HTTP/2 implementation details
are no longer part of the documented public API surface.

### Removed
- Undocumented 1.x internals are no longer supported migration targets for the
2.0 line.

## [v1.11.0] - 2025-12-20
### Added
- Added full Server-Sent Events (SSE) support for both client and server:
- **Client-side**: `sse_callback` keyword argument for `HTTP.request` to parse SSE streams on
successful responses, invoking a callback with `HTTP.SSEEvent` for each event received.
- **Server-side**: `HTTP.sse_stream(response) do stream ... end` helper to write
- **Server-side**: `HTTP.sse_stream(200) do stream ... end` helper to write
`HTTP.SSEEvent`s and automatically close the stream when the block finishes (or use
`HTTP.sse_stream(response)` for manual management).
`response = HTTP.sse_stream(200)` for manual management).
- `HTTP.SSEEvent` struct for representing SSE events with `data`, `event`, `id`, and `retry` fields.

## [v1.10.1] - 2023-11-28
Expand Down
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ The HTTP.jl package is licensed under the MIT "Expat" License:

> src/cookies.jl based on src/net/http/cookie.go Copyright 2009 The Go Authors

> Copyright (c) 2022: https://github.com/JuliaWeb/HTTP.jl/graphs/contributors.
> Copyright (c) 2026: https://github.com/JuliaWeb/HTTP.jl/graphs/contributors.
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
Expand Down
42 changes: 15 additions & 27 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,47 +1,35 @@
name = "HTTP"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "2.0.0"
authors = ["Jacob Quinn", "contributors: https://github.com/JuliaWeb/HTTP.jl/graphs/contributors"]
version = "1.11.0"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
ConcurrentUtilities = "f0e56b4a-5159-44fe-b623-3e5288b988bb"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
ExceptionUnwrapping = "460bff9d-24e4-43bc-9d9f-a8973cb893f4"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36"
MbedTLS = "739be429-bea8-5141-9913-cc70e7f3736d"
NetworkOptions = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
OpenSSL = "4d8831e6-92b7-49fb-bdf8-b643e874388c"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
EnumX = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SimpleBufferStream = "777ac1f9-54b0-4bf8-805c-2214025038e7"
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
Reseau = "802f3686-a58f-41ce-bb0c-3c43c75bba36"
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[sources]
Reseau = {rev = "main", url = "https://github.com/JuliaServices/Reseau.jl"}

[compat]
CodecZlib = "0.7"
ConcurrentUtilities = "2.4"
ExceptionUnwrapping = "0.1"
LoggingExtras = "0.4.9,1"
MbedTLS = "0.6.8, 0.7, 1"
OpenSSL = "1.3"
EnumX = "1"
PrecompileTools = "1.2.1"
SimpleBufferStream = "1.1"
URIs = "1.6" # We need URIs >= 1.6 to ensure we have https://github.com/JuliaWeb/URIs.jl/pull/66
julia = "1.6"
Reseau = "1"
URIs = "1.6.1"
julia = "1.10"

[extras]
BufferedStreams = "e1450e63-4bb3-523b-b2a4-4ffa8c0fd77d"
Deno_jll = "04572ae6-984a-583e-9378-9577a1c2574d"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
NetworkOptions = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
JuliaC = "acedd4c2-ced6-4a15-accc-2607eb759ba2"
Reseau = "802f3686-a58f-41ce-bb0c-3c43c75bba36"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[targets]
test = ["BufferedStreams", "Deno_jll", "Distributed", "InteractiveUtils", "JSON", "Test", "Unitful", "NetworkOptions"]
test = ["JuliaC", "Reseau", "Test"]
123 changes: 62 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,28 @@ or by using Pkg functions
julia> using Pkg; Pkg.add("HTTP")
```

## Project Status
## Overview

The package has matured and is used in many production systems.
But as with all open-source software, please try it out and report your experience.
`HTTP.jl` provides HTTP client/server support, HTTP/2, WebSockets, SSE,
cookies, multipart forms, retries, and proxy-aware transports for Julia.

The package is tested against current Julia LTS (1.6), and current master on Linux, macOS, and Windows.
Current package compat targets Julia `1.10` and later.

## Scope and Deferred Go Parity

`HTTP.jl` 2.x borrows heavily from Go's `net/http` design, but it is not a
drop-in clone of every Go API or feature.

The current release intentionally defers:

- HTTP/2 server push and a `Pusher`-style surface
- Go `ResponseController` / hijack-style server-control APIs
- full Go request lifecycle instrumentation parity
- full `net/url` and `ServeMux` feature parity

These are explicit scope decisions for the 2.x release line rather than known
bugs in the documented client, server, streaming, proxy, HTTP/2, SSE, and
WebSocket APIs.

## Contributing and Questions

Expand All @@ -35,98 +51,83 @@ Contributions are very welcome, as are feature requests and suggestions. Please

## Client Examples

[`HTTP.request`](https://juliaweb.github.io/HTTP.jl/stable/index.html#HTTP.request-Tuple{String,HTTP.URIs.URI,Array{Pair{SubString{String},SubString{String}},1},Any})
sends a HTTP Request Message and returns a Response Message.
High-level request helpers return a `Response` whose `body` is a
`Vector{UInt8}` by default.

```julia
r = HTTP.request("GET", "http://httpbin.org/ip")
r = HTTP.get("http://httpbin.org/ip")
println(r.status)
println(String(r.body))
```

[`HTTP.open`](https://juliaweb.github.io/HTTP.jl/stable/index.html#HTTP.open)
sends a HTTP Request Message and
opens an `IO` stream from which the Response can be read.
Stream directly into an `IO` sink with `response_stream`, or use `HTTP.open` when
you want pull-based control over the response stream.

```julia
HTTP.open(:GET, "https://tinyurl.com/bach-cello-suite-1-ogg") do http
open(`vlc -q --play-and-exit --intf dummy -`, "w") do vlc
write(vlc, http)
end
open("response.bin", "w") do io
HTTP.get("https://example.com/data.bin"; response_stream = io)
end

HTTP.open(:GET, "https://example.com/stream") do stream
println(String(read(stream)))
end
```

Handle [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) (SSE) streams by passing an `sse_callback` function to `HTTP.request`:
Handle [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
by passing an `sse_callback` function to `HTTP.request`:

```julia
events = HTTP.SSEEvent[]
HTTP.request("GET", "http://127.0.0.1:8080/events"; sse_callback = event -> push!(events, event))
```

Each callback receives an `HTTP.SSEEvent` with the parsed `data`, `event`, `id`, `retry`, and `fields` from the stream.
Each callback receives an `HTTP.SSEEvent` with the parsed `data`, `event`,
`id`, `retry`, and `fields` from the stream.

## Server Examples

[`HTTP.Servers.listen`](https://juliaweb.github.io/HTTP.jl/stable/index.html#HTTP.Servers.listen):

The server will start listening on 127.0.0.1:8081 by default.
Use `HTTP.serve!` for request/response handlers:

```julia
using HTTP

# start a blocking server
HTTP.listen() do http::HTTP.Stream
@show http.message
@show HTTP.header(http, "Content-Type")
while !eof(http)
println("body data: ", String(readavailable(http)))
end
HTTP.setstatus(http, 404)
HTTP.setheader(http, "Foo-Header" => "bar")
HTTP.startwrite(http)
write(http, "response body")
write(http, "more response body")
server = HTTP.serve!("127.0.0.1", 8081) do request
payload = "Hello from HTTP.jl"
return HTTP.Response(
200;
headers = ["Content-Type" => "text/plain"],
body = HTTP.BytesBody(codeunits(payload)),
content_length = ncodeunits(payload),
)
end
```

[`HTTP.Handlers.serve`](https://juliaweb.github.io/HTTP.jl/stable/index.html#HTTP.Handlers.serve):
```julia
using HTTP
resp = HTTP.get("http://127.0.0.1:8081"; proxy = HTTP.ProxyConfig())
println(resp.status)
println(String(resp.body))

# HTTP.listen! and HTTP.serve! are the non-blocking versions of HTTP.listen/HTTP.serve
server = HTTP.serve!() do request::HTTP.Request
@show request
@show request.method
@show HTTP.header(request, "Content-Type")
@show request.body
try
return HTTP.Response("Hello")
catch e
return HTTP.Response(400, "Error: $e")
end
end
# HTTP.serve! returns an `HTTP.Server` object that we can close manually
close(server)
HTTP.forceclose(server)
```

Use `HTTP.listen!` or `HTTP.streamhandler` when you need lower-level stream
ownership for incremental reads, writes, or trailers.

## WebSocket Examples

```julia
julia> using HTTP.WebSockets
julia> server = WebSockets.listen!("127.0.0.1", 8081) do ws
for msg in ws
send(ws, msg)
end
using HTTP

server = HTTP.WebSockets.listen!("127.0.0.1", 8081) do ws
for msg in ws
HTTP.WebSockets.send(ws, msg)
end
end

julia> WebSockets.open("ws://127.0.0.1:8081") do ws
send(ws, "Hello")
s = receive(ws)
println(s)
end;
Hello
HTTP.WebSockets.open("ws://127.0.0.1:8081"; proxy = HTTP.ProxyConfig()) do ws
HTTP.WebSockets.send(ws, "Hello")
println(HTTP.WebSockets.receive(ws))
end

julia> close(server)
HTTP.WebSockets.forceclose(server)
```

[docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg
Expand Down
Loading
Loading