Adopt apple/swift-http-types for the HTTP transport#135
Merged
Conversation
Replace the stringly-typed `[(String, String)]` header model, the custom `RouteMethod` enum, and the `HTTPStatus` struct with Apple's swift-http-types — `HTTPFields`, `HTTPRequest.Method`, and `HTTPResponse.Status` — across the HTTP transport. Why this improves robustness: - Header lookups are now case-insensitive and validated by the library instead of hand-rolled `caseInsensitiveCompare` re-implemented in three places (route request accessor, OAuth proxy, header test). - Response defaults can no longer duplicate a header: `HTTPFields` replaces by name rather than appending (the regression test for this is now structural, not behavioural). - Header names/values are validated at construction (RFC 9110 tokens, CRLF sanitisation) — most relevant for the OAuth proxy, which copies client-supplied and upstream headers in both directions. - MCP-specific header names (`Mcp-Session-Id`, `MCP-Protocol-Version`, `Last-Event-ID`) are now shared, typed `HTTPField.Name` constants used by both the server routing layer and the client, instead of scattered string literals a typo would silently break. `HTTPTypes` is zero-dependency and Foundation-free, so it is linked into the core target (not behind the `Server` trait) and shared by client and server. `HTTPHandler` keeps a manual NIO<->http-types conversion for now; Phase 2 replaces it with the NIOHTTPTypesHTTP1 codec. Public API: `HTTPRouteRequest`/`HTTPRouteResponse` now expose `headerFields` (`HTTPFields`); the old `headers: [(String, String)]` and the `HTTPRouteResponse(status:headers:body:)` initializer remain as deprecated shims. `addRoute(_:_:)` takes `HTTPRequest.Method` (`.post` instead of `.POST`); `HTTPStatus` becomes `HTTPResponse.Status`. All 421 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ase 2)
Insert swift-nio-extras' `HTTP1ToHTTPServerCodec` into the channel pipeline
(after `HTTPLogger`, before `HTTPHandler`) so `HTTPHandler` consumes
`HTTPRequestPart`/`HTTPResponsePart` carrying `HTTPRequest`/`HTTPResponse`
directly. The handler no longer translates between NIO and http-types by
hand: `convertMethod`, `convertHeaders`, `nioStatus`, and `nioHeaders(from:)`
are all deleted, and `RequestState` now carries an `HTTPRequest`.
`HTTPLogger` stays on the NIO side of the codec, so its request/response
logging is unchanged. The one direct-to-channel SSE write path
(`Channel.sendSSE`) now emits an `HTTPResponsePart` so it round-trips through
the codec like every other write.
The HTTP/1 codec lifts the `Host` header into the request's `:authority`
pseudo-header; `HTTPHandler.requestHeaderFields(from:)` re-exposes it as a
`Host` field so routes that read `header("Host")` (OAuth redirect/issuer URL
construction, legacy SSE) keep working.
`NIOHTTPTypes`/`NIOHTTPTypesHTTP1` are linked only under the `Server` trait;
the client-only / NIO-free build is unaffected.
All 421 tests pass; the client-only (Server-disabled) build stays NIO-free.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The HTTPStatus -> HTTPResponse.Status rename in Phase 1 pushed the `json` factory signature to 123 chars; wrap the parameters onto separate lines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Evaluates and adopts apple/swift-http-types to harden the HTTP transport, in two reviewable phases. The win is concentrated in the header/method/status currency types; the SSE/streaming/IO machinery is untouched.
Why (robustness)
The HTTP layer previously carried headers as
[(String, String)]with a customRouteMethodenum andHTTPStatusstruct, and three different header representations (NIOHTTPHeaderson the wire,[(String, String)]in routing, Foundation header dicts on the client) with hand-written conversions between them.Replacing that with
HTTPFields/HTTPRequest.Method/HTTPResponse.Status:caseInsensitiveComparere-implemented in the route accessor, the OAuth proxy, and the header test.HTTPFieldsreplaces by name rather than appending. The regression test for this is now structural.copyUpstreamHeadersnow drops invalid upstream field names instead of forwarding them blind..mcpSessionID,.mcpProtocolVersion,.lastEventID) used by both the server routing layer and the client, instead of scattered string literals a typo would silently break.HTTPTypesis zero-dependency and Foundation-free, so it lives in the core target (not behind theServertrait) and is shared by client and server. The client-only / NIO-free build stays NIO-free (verified).Phase 1 —
a57eff2— currency typesHTTPRouteRequest/HTTPRouteResponsenow exposeheaderFields: HTTPFields;RouteResponse, the router, and all built-in routes migrated.RouteMethod→HTTPRequest.Method;HTTPStatus→HTTPResponse.Status(both deleted).MCPServerProxy) sets/reads the MCP headers through the sharedHTTPField.Nameconstants via small typedURLRequest/HTTPURLResponsehelpers; the transfer still goes throughURLSession/ SwiftCross.HTTPHandlerkeeps a manual NIO↔http-types conversion for this phase.Phase 2 —
1d3bc7f— NIO interopHTTP1ToHTTPServerCodecinto the pipeline (afterHTTPLogger, beforeHTTPHandler), soHTTPHandlerworks inHTTPRequest/HTTPResponse/HTTPFieldsdirectly.convertMethod/convertHeaders/nioStatus/nioHeaders(from:)glue.HTTPLoggerstays on the NIO side of the codec, unchanged.Hostinto:authority;requestHeaderFields(from:)re-exposes it so routes readingheader("Host")keep working.Breaking changes (public API)
addRoute(_:_:)takesHTTPRequest.Method—.postinstead of.POST.HTTPRouteRequest.headers/HTTPRouteResponse.headers([(String, String)]) andHTTPRouteResponse(status:headers:body:)remain as deprecated shims overheaderFields;header(_:),bearerToken,sessionIDare unchanged.HTTPStatus→HTTPResponse.Status,RouteMethod→HTTPRequest.Method.Dependencies
apple/swift-http-typesfrom: 1.0.0(core, unconditional — zero-dep, Foundation-free).apple/swift-nio-extrasfrom: 1.25.0(NIOHTTPTypes+NIOHTTPTypesHTTP1, Server trait only).Verification
swift build✓ and fullswift test✓ (421 tests, 70 suites) after each phase.swift build --disable-default-traits --traits Client✓ — client-only build stays NIO-free.🤖 Generated with Claude Code