23 Commits

Author SHA1 Message Date
Fabian Fett 67ac92dc76 Support sending and receiving trailers in HTTPExecutableRequest (#882)
This pr adds all the internal wiring in `HTTP1ClientChannelHandler` and
`HTTP2ClientRequestHandler` to send and receive HTTP trailers.
2026-02-10 11:53:56 +01:00
Fabian Fett e2ab0d176f Full support for bidirectional streaming (#879)
> ## Note: 
> This is a long LLM generated PR description. However it captures very
well, what has been changed and has already been reduced for brevity.
The PR is sadly quite complex but I think the description captures the
changes quite well.

This is foundational work needed to properly support HTTP trailers and
scenarios where the server sends a complete response before the client
finishes uploading (e.g., early rejection, 100-continue flows, or
bidirectional streaming protocols).

## Changes

### State Machine Improvements

- **Added `endForwarded` state** to
`Transaction.StateMachine.RequestStreamState`
- This new state distinguishes between "request data forwarded to the
channel" and "request data written to the network"
- Properly handles the race condition where response completes before
the request write completes

- **Renamed `succeedRequest` → `forwardResponseEnd`** in both
`HTTPRequestStateMachine.Action` and
`HTTP1ConnectionStateMachine.Action`
- Better reflects the semantic meaning: we're forwarding the end of the
response stream, not necessarily succeeding the entire request yet
  - More accurate naming for bidirectional streaming scenarios

### Protocol Changes

- **Added `requestBodyStreamSent()` to `HTTPExecutableRequest`
protocol**
- Called by the channel handler when the request body stream has been
fully written to the network
- Allows proper coordination between request and response stream
completion
  - Implemented in both `Transaction` and `RequestBag`

### Request State Machine Updates

- **Updated `FinalSuccessfulRequestAction`**
- Changed `.sendRequestEnd(EventLoopPromise<Void>?)` to simpler
`.requestDone`
- Added `.none` case for when response completes but request is still
in-flight
- Removed the need to pass promises around, simplifying the state
machine

- **`sendRequestEnd` action now includes
`FinalSuccessfulRequestAction`**
- Allows the state machine to signal what should happen after the
request completes
- Enables proper cleanup coordination (idle connection, close, or
continue)

### Channel Handler Updates

- **HTTP1ClientChannelHandler**
- `sendRequestEnd` now properly handles scenarios where response has
already completed
- Added future callback to coordinate request completion with final
actions
- Properly manages connection state (idle vs close) based on both
streams completing

- **HTTP2ClientRequestHandler**
  - Updated to handle new `sendRequestEnd` signature
  - Properly ignores HTTP/1-specific final actions (like `.requestDone`)

### RequestBag State Machine

- **Added `endReceived` state to `ResponseStreamState`**
- Tracks when the response has completed while request is still ongoing
- Enables proper sequencing: response end → request end → task
completion

- **Updated `FinishAction`**
- Added `.forwardStreamFinishedAndSucceedTask` for the case where both
streams complete simultaneously
  - Ensures delegate methods are called in the correct order

### Error Handling

- **Improved failure handling in `Transaction.StateMachine`**
- Now properly handles errors that occur after response completes but
before request finishes
  - Added `cancelExecutor` action to the fail path
- Executor is now passed to `failRequestStreamContinuation` for proper
cleanup

## Technical Details

### The Problem

Previously, when a server sent a complete response before the client
finished uploading the request body, AHC would:
1. Receive the full response (head, body, end)
2. But NOT inform the user that the response was complete if the request
was still streaming
3. Only succeed the request after both streams completed

This made it impossible to implement proper bidirectional streaming or
handle scenarios like:
- Server rejecting a large upload early (e.g., 413 Payload Too Large)
- 100-continue flows where the server responds before request completes
- HTTP trailers sent by the server

### The Solution

The new state machine properly tracks four completion states:
1. **Neither complete**: Normal request/response in flight
2. **Response complete, request ongoing**: New
`endForwarded`/`endReceived` states
3. **Request complete, response ongoing**: Existing logic
4. **Both complete**: Request succeeds

The key insight is the `endForwarded` state, which represents "we've
given all request data to the channel, but it hasn't been written to the
network yet". This allows us to:
- Immediately forward response completion to the user
- Wait for the write to complete before cleaning up resources
- Properly sequence connection state transitions

## Future Work

This PR lays the groundwork for:
- Proper internal HTTP trailer support (both sending and receiving)

---------

Co-authored-by: George Barnett <gbarnett@apple.com>
2026-02-03 12:21:31 +01:00
Fabian Fett 4b99975677 Rename succeedRequest to receiveResponseEnd (#877) 2026-01-08 11:36:36 +00:00
George Barnett ce04df0613 Don't hold a lock over a continuation in Transaction (#871)
Motivation:

The various 'withMumbleContinuation' APIs are supposed to be invoked
synchronously with the caller. This assumption allows a lock to be
acquired before the call and released from the body of the
'withMumbleContinuation' after e.g. storing the continuation. However
this isn't the case and the job may be re-enqueued on the executor
meaning that this is pattern is vulnerable to deadlocks.

Modifications:

- Drop and reacquire the lock in Transaction

Result:

Lower chance of deadlock
2025-11-26 14:13:29 +00:00
Konrad `ktoso` Malawski 8430dd49d4 Introduce built-in swift-distributed-tracing support (#857)
Co-authored-by: Moritz Lang <16192401+slashmo@users.noreply.github.com>
Co-authored-by: George Barnett <gbarnett@apple.com>
2025-10-07 16:30:31 +09:00
George Barnett 6b5f8c9679 Fix a few more Sendability warnings in Sources (#840)
Motivation:

There are a couple of sendability warnings leftover in Sources.

Transaction moves a closure into a task. The closure isn't Sendable (and
shouldn't be). However, higher up the stack there's a closure which
generates the non-sendable closure which can be sendable.

Modifications:

- Pass the sendable closure generating closure down rather
- Add a few more explicit sendable annotations

Result:

Fewer warnings
2025-04-30 16:40:50 +01:00
Greg Cotten 31122eaf7c Add Request/Response History to all public Response types (#817)
Work to close
https://github.com/swift-server/async-http-client/issues/790

The fact that `HTTPClient.Request` is not Sendable make me think we're
going to need to store something else, such as a `URL` and
`HTTPRequestHead`, instead?
2025-03-03 15:48:27 +00:00
Johannes Weiss 2119f0d9cc fix 784: dont crash on huge in-memory bodies (#785)
fixes #784 

`writeChunks` had 3 bugs:
1. An actually wrong `UnsafeMutableTransferBox` -> removed that type
which should never be created
2. A loooong future chain (instead of one final promise) -> implemented
3. Potentially infinite recursion which lead to the crash in #784) ->
fixed too
2024-11-26 14:52:39 +00:00
Rick Newton-Rogers c621142327 Adopt GitHub actions (#780)
Migrate CI to use GitHub Actions.

### Motivation:

To migrate to GitHub actions and centralised infrastructure.

### Modifications:

Changes of note:
* Adopt swift-format using rules from SwiftNIO.
* Remove scripts and docker files which are no longer needed.
* Disabled warnings-as-errors on Swift 6.0 CI pipelines for now.

### Result:

Feature parity with old CI.
2024-10-29 15:01:46 +00:00
Johannes Weiss d766674c7e HTTPClientRequest: allow custom TLS config (#709) 2023-10-16 07:00:58 -07:00
Corey 333e60cc90 Fix building transaction extension for Apple Platforms (#685) 2023-04-14 06:03:14 -07:00
David Nadoba e18db27dd3 Replace TransactionBody with NIOAsyncSequenceProducer (#677)
* save progress

* Replace `TransactionBody` with `NIOAsyncSequenceProducer`

* test

* revert unnesscary changes

* Add end-to-end test

which currently fails because of https://github.com/apple/swift-nio/issues/2398

* soundness

* Use latest swift-nio release

* throw CancellationError on task cancelation

* Fix Swift 5.5 & 5.6
2023-04-12 15:11:56 +01:00
carolinacass 91b26407ad Update collect to use content-length to make early checks
Motivation:
not accumulate too many bytes

Modifications:
Implementing collect function to use NIOCore version to prevent overflowing

Co-authored-by: Cory Benfield <lukasa@apple.com>
2023-04-04 16:56:58 +01:00
David Nadoba 0bdc425a84 Remove #if compiler(>=5.5) (#641)
* Remove `#if compiler(>=5.5)`

* Run SwiftFormat
2022-10-12 16:18:47 +01:00
David Nadoba d7b69d9d56 Make HTTPClientResponse.init public (#632) 2022-10-11 10:36:21 +01:00
Fabian Fett 897d49aa1b Replace Lock with NIOLock (#628)
SwiftNIO 2.42.0 has deprecated Lock and replaced it with a new NIOLock. This commit removes all uses of Lock and replaces them with NIOLock. Further, now, we must require SwiftNIO 2.42.0
2022-09-27 15:42:47 +02:00
David Nadoba 9c7ab039fe Allow HTTPClientRequest to be executed multiple times if body is an AsyncSequence (#620) 2022-08-23 13:46:17 +01:00
David Nadoba a9c3cfb387 Report last connection error if request deadline is exceeded with async/await API (#608)
Co-authored-by: Cory Benfield <lukasa@apple.com>
2022-08-04 18:24:34 +02:00
Franz Busch 24425989da Call didSendRequestPart after the write has hit the socket (#566)
### Motivation

Today `didSendRequestPart` is called after a request body part has been passed to the executor. However, this does not mean that the write hit the socket. Users may depend on this behavior to implement back-pressure. For this reason, we should only call this `didSendRequestPart` once the write was successful.

### Modification

Pass a promise to the actual channel write and only call the delegate once that promise succeeds.

### Result

The delegate method `didSendRequestPart` is only called after the write was successful. Fixes #565.

Co-authored-by: Fabian Fett <fabianfett@apple.com>
2022-04-26 16:04:54 +02:00
David Nadoba d372bdc213 Make async/await available on older Apple Platforms (#527)
### Motivation
With Xcode 13.2, and therefore Swift 5.5.2, Swift Concurrecy is supported on older Apple OSs. async/await suport will no longer be available on Swift before `5.5.2` but this isn't a breaking change because we have not yet made anything of it public.

### Changes
- replace all `#if compiler(>=5.5) && canImport(_Concurrency)` with `#if compiler(>=5.5.2) && canImport(_Concurrency)`
- replace all `available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)` with `available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)`
2021-12-17 16:08:24 +01:00
David Nadoba d95277640f Respect deadline on new HTTPClient.execute for async/await (#529)
* Schedule deadline timeout
* Add state machine tests and enable skipped test for http1
2021-12-17 09:36:41 +01:00
David Nadoba 5db7719a27 async/await execute (#524)
* async/await execute

* remove default length for `HTTPClientRequest.Body`

* make redirect logic iterative

* move Task creation into `TransactionCancelHandler`
2021-12-14 20:24:45 +01:00
Fabian Fett 24b0a14e9c Add async Transaction (#518)
This introduces an async Transaction object. The object is the translation layer between the user facing async API and the NIO channel handler.
2021-12-09 18:04:01 +01:00