A parser for HTTP messages in Swift, it parses both requests and responses by wrapping llhttp, the HTTP parser used in Node.js.
This library provides incremental HTTP parsing, allowing you to parse either HTTP requests and responses as data arrives from the network without having to frame complete messages.
- Complete HTTP Support: Handles requests, responses, keep-alive, chunked encoding, protocol upgrades
- Incremental Parsing: Pass in HTTP data as it arrives, perfect for streaming scenarios
- High Performance: Built on llhttp, one of the fastest and battle-tested HTTP parsers available
- Multiple Abstraction Levels: Get every low-level llhttp event or get completely parsed HTTP messages
- Swift Concurrency Support: Thread-safe actors by default, with preconcurrency fallback options for older code
Add this package to your Swift Package Manager dependencies:
dependencies: [
.package(url: "https://github.com/nonstrict-hq/llhttp", from: "1.0.0")
]- LLHTTP: Thin wrapper around the C library llhttp that emits events and doesn't keep data in memory.
- HTTPMessagesParser: Receive complete HTTP requests/responses in a Swift async streams, completely with the body in memory.
If you just want to handle complete HTTP messages:
import llhttp
// Use `HTTPMessagesParser<HTTPRequest>` or `HTTPMessagesParser<HTTPResponse>` if you know what kind of messages you need to parse.
let parser = HTTPMessagesParser<HTTPMessage>()
// Keep reading data from the network connection and parse it
Task {
do {
while true {
let content = try await connection.receive(atMost: 512).content
for message in try await parser.parse(content) {
// Handle incoming HTTP message as you like
switch message {
case .request(let request):
print("Got request: \(request.method) \(request.url)")
case .response(let response):
print("Got response: \(response.status)")
}
}
}
} catch {
// Calling finish might return messages that are considered complete now we know no more data is comming in. (For example a single HTTP request with no content length set.)
// It can also fail if the HTTP message is invalid at this point and throw an error.
let messages = try? await parser.finish()
}
}Tip: Use HTTPMessagesParser<HTTPMessage>.Preconcurrency if you don't want to use async/await, for example when using the old completion block based networking APIs.
Note: This parser needs to keep the whole HTTP message including body in memory and therefore won't work to receive large requests/responses like big files for example. You will run out of memory in that case.
For fine-grained control over parsing events:
let parser = LLHTTP(mode: .both)
await parser.setCallbacks { signal, state in
// TODO: Handle signal
return .proceed
} payloadHandler: { payload, state in
// TODO: Handle payload
return .proceed
} headersCompleteHandler: { state in
// TODO: Handle headers complete
return .proceed
}
try await parser.parse(httpData)Note: This is a one-on-one wrapper around the event-based llhttp C-implementation.
.request- Parse only HTTP requests.response- Parse only HTTP responses.both- Auto-detect based on first message
For compatibility with legacy systems you can turn on lenient flags so incorrect HTTP messages are accepted.
When using callback based networking APIs or in older codebases it might be impractical to use actors and make your callback closurus Sendable. Therefore there are also Preconcurrency variants of both LLHTTP and HTTPMessagesParser. These are non-sendable and non-reentrant safe, but also much more convenient to use when you have callback based APIs that deliver the HTTP stream to you.
This Swift wrapper created by Nonstrict B.V. and licensed under the MIT License.
The llhttp C library is created by Fedor Indutny and licensed under the MIT License.