Building a resumable upload server with SwiftNIO
At a glance
| Item | Summary |
|---|---|
| Purpose | Translate multiple HTTP resumable-upload requests into one continuous SwiftNIO upload channel for application handlers. |
| Deliverable | A Swift package library, not an app or executable server. Tests exercise the handler with embedded channels. |
| Public surface | HTTPResumableUploadContext stores shared uploads; HTTPResumableUploadHandler is inserted into each connection pipeline. |
Project structure
Package.swift
├── Sources/NIOResumableUpload/
│ ├── HTTPResumableUploadContext.swift
│ ├── HTTPResumableUploadHandler.swift
│ ├── HTTPResumableUpload.swift
│ ├── HTTPResumableUploadChannel.swift
│ └── HTTPResumableUploadProtocol.swift
├── Tests/NIOResumableUploadTests/
└── Dependencies/swift-http-types/ # vendored package dependency
The architecture analysis concerns Sources/NIOResumableUpload; the dependency tree is supporting library source, not part of the sample’s upload design.
Overall architecture
flowchart LR
Requests["HTTP connection requests"] --> Handler["HTTPResumableUploadHandler"]
Handler --> Upload["HTTPResumableUpload state machine"]
Upload --> Protocol["request classifier / response builder"]
Upload --> Child["persistent HTTPResumableUploadChannel"]
Child --> AppHandlers["application upload handlers"]
Context["shared HTTPResumableUploadContext"] --> Registry["token -> upload registry"]
Registry --> Upload
Reference code
Sources/NIOResumableUpload/HTTPResumableUploadHandler.swift:25 — callers inject a shared context and the child-channel configuration.
public init(context: HTTPResumableUploadContext,
channelConfigurator: @escaping (Channel) -> Void) {
self.upload = HTTPResumableUpload(context: context,
channelConfigurator: channelConfigurator)
}Ownership and state
classDiagram
HTTPResumableUploadContext *-- Dictionary : locked token registry
HTTPResumableUploadHandler *-- HTTPResumableUpload : initial request owner
HTTPResumableUpload *-- HTTPResumableUploadChannel : persistent child
HTTPResumableUpload o-- HTTPResumableUploadHandler : current attachment
HTTPResumableUpload *-- Scheduled : idle timeout
HTTPResumableUploadChannel *-- ChannelPipeline
Ownership evidence
Sources/NIOResumableUpload/HTTPResumableUpload.swift:15 — one logical upload owns the state that survives connection replacement.
final class HTTPResumableUpload {
// ...
}The shared context owns the locked token registry. Each logical upload owns offset, completion flags, timeout, persistent child channel, and at most one attached network handler. On resumption, the new handler points to the existing upload rather than creating a second logical stream.
Class and protocol design
| Type | Responsibility | Boundary |
|---|---|---|
HTTPResumableUploadContext |
Configure origin/path/timeout and find logical uploads | Public API |
HTTPResumableUploadHandler |
Bridge parent-channel events into the state machine | Public ChannelDuplexHandler |
HTTPResumableUpload |
Classify requests and enforce upload state/offset rules | Internal implementation |
HTTPResumableUploadChannel |
Preserve an application pipeline across request connections | Internal Channel/ChannelCore |
HTTPResumableUploadProtocol |
Parse headers and construct protocol responses | Internal namespace enum |
The library uses SwiftNIO protocols as extension seams; it does not add an app-specific storage protocol because its output is the child channel supplied by the caller.
Access control
| Boundary | Effect and rationale |
|---|---|
public context, initializer, handler, and callbacks |
Defines the small integration API consumed by a server pipeline. |
| Internal logical upload, virtual channel, and protocol enum | Callers cannot couple to state-machine mechanics. |
private flags, offset, handlers, channel, and timer |
Only the logical upload may perform state transitions. |
private(set) resumePath |
Context cleanup can identify an upload, but other code cannot replace its token. |
private locked registry |
All token mutation goes through startUpload, stopUpload, and findUpload. |
Sources/NIOResumableUpload/HTTPResumableUploadContext.swift:12 shows the public facade and private registry; Sources/NIOResumableUpload/HTTPResumableUpload.swift:25 shows controlled token mutation.
Logic ownership and placement
| Logic | Owner |
|---|---|
| Upload token registry | HTTPResumableUploadContext |
| NIO lifecycle forwarding | HTTPResumableUploadHandler |
| Request/resumption state transitions | HTTPResumableUpload |
| Header parsing and response construction | HTTPResumableUploadProtocol |
| Persistent pipeline lifecycle and backpressure forwarding | HTTPResumableUploadChannel |
| End-to-end behavior verification | NIOResumableUploadTests |
Design patterns
| Pattern | Evidence | Purpose |
|---|---|---|
| Adapter | Sources/NIOResumableUpload/HTTPResumableUploadHandler.swift:13 |
Converts connection-bound NIO events into logical-upload events. |
| State machine | Sources/NIOResumableUpload/HTTPResumableUpload.swift:191 |
Dispatches creation, query, append, cancellation, and invalid requests. |
| Registry | Sources/NIOResumableUpload/HTTPResumableUploadContext.swift:16 |
Resolves a resumption token to persistent state safely across channels. |
| Virtual child channel | Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift:14 |
Makes many requests appear as one stream to app handlers. |
| Event-loop confinement | Sources/NIOResumableUpload/HTTPResumableUpload.swift:81 |
Serializes mutable upload transitions. |
Naming conventions
- All library types share the
HTTPResumableUploadprefix, making the public and internal family obvious. - Role suffixes distinguish
Context,Handler,Channel, andProtocol. - State commands use connection-neutral verbs:
attachUploadHandler,detachUploadHandler,receive, andend.
Architecture takeaways
- Keep the public pipeline API small and hide the protocol state machine.
- Give resumable state a lifetime longer than any single connection handler.
- Use a virtual channel to shield application handlers from transport reconnection.
- Protect registry access with a lock and transition state on one event loop.
Source map
| Source | Role |
|---|---|
Sources/NIOResumableUpload/HTTPResumableUploadContext.swift:12 |
Public context and registry |
Sources/NIOResumableUpload/HTTPResumableUploadHandler.swift:13 |
Public NIO adapter |
Sources/NIOResumableUpload/HTTPResumableUpload.swift:15 |
Logical upload state owner |
Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift:14 |
Persistent child channel |
Sources/NIOResumableUpload/HTTPResumableUploadProtocol.swift:15 |
Protocol parser/response namespace |