Snapshot of the in-flight state that already existed, identically, in both this worktree and the main checkout before this session began: the initial HTTP Client platform implementation (previously untracked), the redis-lab removal, and the JPA / object-storage / notification integration work. Kept separate from this session's HTTP Client review response, which lands in the following commit, so the two bodies of work stay reviewable apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
89 lines
6.0 KiB
Markdown
89 lines
6.0 KiB
Markdown
# HTTP Client Platform — Support Matrix
|
|
|
|
Grades follow design §6 and §29. A row is **Stable** only when the cross-transport contract suite
|
|
proves it; anything the suite cannot prove is **Experimental** and says so.
|
|
|
|
## Spring API
|
|
|
|
| API | Grade | Role | Constraint |
|
|
|---|---|---|---|
|
|
| `RestClient` | Stable | Blocking execution | Bounded concurrency and an effective deadline are mandatory |
|
|
| `WebClient` | Stable | Reactive, streaming, SSE | No blocking work on the event loop |
|
|
| HTTP Service Client (`@HttpExchange`) | Default | Declarative typed client | Operation metadata is mandatory |
|
|
| `RestTemplate` | Migration only | Moving existing calls | No new profile or feature |
|
|
| Generic Exchange (H2) | Restricted | Dynamic method, path, body | Base URL and policy are immutable |
|
|
| Dynamic Target (H3) | Restricted | User-supplied URL | Separate SSRF policy; inherits no credential |
|
|
| Native engine | Internal | Engine-specific configuration | Never an application-facing API |
|
|
|
|
## Transports
|
|
|
|
| Transport | Blocking | Reactive | HTTP/1.1 | HTTP/2 | HTTP/3 | Grade | Verified by |
|
|
|---|---:|---:|---:|---:|---:|---|---|
|
|
| Apache HttpClient 5 (classic) | yes | no | yes | **no** | no | Stable (blocking default) | `httpClientStableContractTest`, `NegotiatedProtocolContractTest` |
|
|
| JDK HttpClient | yes | `sendAsync` | yes | yes (TLS/ALPN) | no | Stable (lightweight, blocking HTTP/2) | `NegotiatedProtocolContractTest` |
|
|
| Reactor Netty | limited | yes | yes | yes | experimental | Stable (reactive default) | `NegotiatedProtocolContractTest` |
|
|
| Jetty | facade | yes | yes | yes | yes | **Experimental** | `Http3OptInTest` only |
|
|
| Simple request factory | yes | no | limited | no | no | Local test only | rejected in production by `ClientProfileValidator` |
|
|
|
|
### Apache is HTTP/1.1 here, and why
|
|
|
|
Design §6.2 grades Apache HttpClient 5 as HTTP/2-capable, and the library is — in its **async**
|
|
client. Spring's `HttpComponentsClientHttpRequestFactory` drives the **classic** client, which
|
|
speaks HTTP/1.1 only. `NegotiatedProtocolContractTest` measures this rather than assuming it: the
|
|
classic client fails outright against a prior-knowledge h2c server.
|
|
|
|
So `ApacheBlockingTransportProvider.capabilities()` declares HTTP/1.1, and a profile that pairs
|
|
Apache with `HTTP_2` is rejected at startup instead of quietly running HTTP/1.1 while this table
|
|
claims otherwise. **Blocking HTTP/2 is served by the JDK transport**; reactive HTTP/2 by Reactor
|
|
Netty. Both are measured from the client after a real TLS handshake, not read from configuration.
|
|
|
|
The JDK transport declares `routeScopedPool=false`, `boundedPendingAcquireQueue=false`, and
|
|
`dynamicTargetStable=false`. A profile that needs any of those is rejected at startup rather than
|
|
served with weaker guarantees. Choosing between Apache and JDK is therefore a real trade: Apache
|
|
gives route-scoped pooling and Dynamic Target pinning, JDK gives HTTP/2.
|
|
|
|
## Capability gates
|
|
|
|
| Capability | Gate |
|
|
|---|---|
|
|
| Dynamic Target (H3) | Apache and Reactor Netty only; JDK and Jetty are rejected |
|
|
| HTTP/3 | `experimentalAcknowledgement` must equal `I_ACCEPT_HTTP3_EXPERIMENTAL_SEMANTICS` |
|
|
| Cross-origin redirect | opt-in per profile; credentials are stripped on the hop |
|
|
| Retry | evidence-based; never enabled by HTTP method alone |
|
|
|
|
## CI matrix
|
|
|
|
| Profile | Frequency | Release gate | Task |
|
|
|---|---|---|---|
|
|
| Spring Framework 7.0 (repository baseline) | every PR | required | `spring70CompatibilityTest` |
|
|
| Spring Framework 6.2 API surface | every PR | required | `spring62CompatibilityTest` |
|
|
| Apache HC5 + RestClient | every PR | required | `httpClientStableContractTest -Phttpclient.contract.transports=apache` |
|
|
| JDK HttpClient + RestClient | every PR | required | `httpClientStableContractTest -Phttpclient.contract.transports=jdk` |
|
|
| Reactor Netty + WebClient | every PR | required | `httpClientStableContractTest -Phttpclient.contract.transports=reactor` |
|
|
| SSRF / cardinality suite | every PR | required | `httpClientSecurityTest` |
|
|
| Toxiproxy fault suite | nightly, release | required | `httpClientFailureInjectionTest` |
|
|
| Event-loop blocking (BlockHound) | every PR | required | `httpClientBlockHoundTest` |
|
|
| Performance certification | nightly, release | required | `httpClientPerformanceTest -Pperformance.assertions.enabled=true` |
|
|
| Jetty HTTP/3 | nightly | Experimental, non-blocking | `test -Phttp3.tests.enabled=true` |
|
|
|
|
### Known limitation of the Spring 6.2 lane
|
|
|
|
This repository's Spring Boot 4.0 baseline pins Spring Framework 7, so a real 6.2 runtime cannot be
|
|
resolved here. `spring62CompatibilityTest` therefore verifies the **API surface**: the common
|
|
packages must not reference any Spring 7-only type, and `org.springframework.web.service.registry`
|
|
is confined to `…httpclient.spring7`. Executing the suite against an actual 6.2 distribution
|
|
requires a host project on that line. This limitation is stated rather than hidden behind a passing
|
|
check.
|
|
|
|
|
|
## What the suites do not prove
|
|
|
|
Stated so the matrix is read as a measurement rather than an aspiration.
|
|
|
|
| Gap | Why | What is proven instead |
|
|
|---|---|---|
|
|
| HTTP/2 frame injection (`REFUSED_STREAM`, arbitrary `GOAWAY`) | The fixture server exposes no frame-level control, and a purpose-built h2 server is a larger dependency than the guarantee is worth here | `Http2EvidenceMapperTest` proves the frame → evidence mapping, and `NegotiatedProtocolContractTest` proves h2 is really negotiated |
|
|
| Netty buffer-leak detection | Netty reports a leak when an unreferenced buffer is collected, which the suite does not force | `NettyLeakDetectionExtension` asserts the PARANOID detector is live and reports nothing; explicit release assertions in the streaming suites are the primary guarantee |
|
|
| Spring 6.2 runtime | This repository's Boot 4.0 baseline pins Spring 7 | `spring62CompatibilityTest` confines the common packages to the 6.2 API surface |
|
|
| Performance latency baseline | Numbers measured on a build agent are not a certification | `httpClientPerformanceTest` asserts structural bounds unconditionally; latency and heap bounds run under `-Pperformance.assertions.enabled=true` |
|