Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/swift-server/async-http-client
HTTP client library built on SwiftNIO
https://github.com/swift-server/async-http-client
http-client swift-nio swift-server swift5
Last synced: 4 days ago
JSON representation
HTTP client library built on SwiftNIO
- Host: GitHub
- URL: https://github.com/swift-server/async-http-client
- Owner: swift-server
- License: apache-2.0
- Created: 2019-03-28T21:53:57.000Z (almost 6 years ago)
- Default Branch: main
- Last Pushed: 2025-01-14T16:39:44.000Z (11 days ago)
- Last Synced: 2025-01-19T23:13:24.373Z (6 days ago)
- Topics: http-client, swift-nio, swift-server, swift5
- Language: Swift
- Homepage: https://swiftpackageindex.com/swift-server/async-http-client/main/documentation/asynchttpclient
- Size: 2.53 MB
- Stars: 939
- Watchers: 26
- Forks: 120
- Open Issues: 112
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.txt
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
- awesome-starts - swift-server/async-http-client - HTTP client library built on SwiftNIO (Swift)
README
# AsyncHTTPClient
This package provides an HTTP Client library built on top of SwiftNIO.This library provides the following:
- First class support for Swift Concurrency
- Asynchronous and non-blocking request methods
- Simple follow-redirects (cookie headers are dropped)
- Streaming body download
- TLS support
- Automatic HTTP/2 over HTTPS
- Cookie parsing (but not storage)## Getting Started
#### Adding the dependency
Add the following entry in yourPackage.swift
to start usingHTTPClient
:```swift
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0")
```
and `AsyncHTTPClient` dependency to your target:
```swift
.target(name: "MyApp", dependencies: [.product(name: "AsyncHTTPClient", package: "async-http-client")]),
```#### Request-Response API
The code snippet below illustrates how to make a simple GET request to a remote server.
```swift
import AsyncHTTPClient/// MARK: - Using Swift Concurrency
let request = HTTPClientRequest(url: "https://apple.com/")
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
print("HTTP head", response)
if response.status == .ok {
let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB
// handle body
} else {
// handle remote error
}/// MARK: - Using SwiftNIO EventLoopFuture
HTTPClient.shared.get(url: "https://apple.com/").whenComplete { result in
switch result {
case .failure(let error):
// process error
case .success(let response):
if response.status == .ok {
// handle response
} else {
// handle remote error
}
}
}
```If you create your own `HTTPClient` instances, you should shut them down using `httpClient.shutdown()` when you're done using them. Failing to do so will leak resources.
Please note that you must not call `httpClient.shutdown` before all requests of the HTTP client have finished, or else the in-flight requests will likely fail because their network connections are interrupted.### async/await examples
Examples for the async/await API can be found in the [`Examples` folder](./Examples) in this Repository.
## Usage guide
The default HTTP Method is `GET`. In case you need to have more control over the method, or you want to add headers or body, use the `HTTPClientRequest` struct:
#### Using Swift Concurrency
```swift
import AsyncHTTPClientdo {
var request = HTTPClientRequest(url: "https://apple.com/")
request.method = .POST
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
request.body = .bytes(ByteBuffer(string: "some data"))let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
if response.status == .ok {
// handle response
} else {
// handle remote error
}
} catch {
// handle error
}
```#### Using SwiftNIO EventLoopFuture
```swift
import AsyncHTTPClientvar request = try HTTPClient.Request(url: "https://apple.com/", method: .POST)
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
request.body = .string("some-body")HTTPClient.shared.execute(request: request).whenComplete { result in
switch result {
case .failure(let error):
// process error
case .success(let response):
if response.status == .ok {
// handle response
} else {
// handle remote error
}
}
}
```### Redirects following
The globally shared instance `HTTPClient.shared` follows redirects by default. If you create your own `HTTPClient`, you can enable the follow-redirects behavior using the client configuration:
```swift
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton,
configuration: HTTPClient.Configuration(followRedirects: true))
```### Timeouts
Timeouts (connect and read) can also be set using the client configuration:
```swift
let timeout = HTTPClient.Configuration.Timeout(connect: .seconds(1), read: .seconds(1))
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton,
configuration: HTTPClient.Configuration(timeout: timeout))
```
or on a per-request basis:
```swift
httpClient.execute(request: request, deadline: .now() + .milliseconds(1))
```### Streaming
When dealing with larger amount of data, it's critical to stream the response body instead of aggregating in-memory.
The following example demonstrates how to count the number of bytes in a streaming response body:#### Using Swift Concurrency
```swift
do {
let request = HTTPClientRequest(url: "https://apple.com/")
let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))
print("HTTP head", response)// if defined, the content-length headers announces the size of the body
let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init)var receivedBytes = 0
// asynchronously iterates over all body fragments
// this loop will automatically propagate backpressure correctly
for try await buffer in response.body {
// for this example, we are just interested in the size of the fragment
receivedBytes += buffer.readableBytesif let expectedBytes = expectedBytes {
// if the body size is known, we calculate a progress indicator
let progress = Double(receivedBytes) / Double(expectedBytes)
print("progress: \(Int(progress * 100))%")
}
}
print("did receive \(receivedBytes) bytes")
} catch {
print("request failed:", error)
}
```#### Using HTTPClientResponseDelegate and SwiftNIO EventLoopFuture
```swift
import NIOCore
import NIOHTTP1class CountingDelegate: HTTPClientResponseDelegate {
typealias Response = Intvar count = 0
func didSendRequestHead(task: HTTPClient.Task, _ head: HTTPRequestHead) {
// this is executed right after request head was sent, called once
}func didSendRequestPart(task: HTTPClient.Task, _ part: IOData) {
// this is executed when request body part is sent, could be called zero or more times
}func didSendRequest(task: HTTPClient.Task) {
// this is executed when request is fully sent, called once
}func didReceiveHead(
task: HTTPClient.Task,
_ head: HTTPResponseHead
) -> EventLoopFuture {
// this is executed when we receive HTTP response head part of the request
// (it contains response code and headers), called once in case backpressure
// is needed, all reads will be paused until returned future is resolved
return task.eventLoop.makeSucceededFuture(())
}func didReceiveBodyPart(
task: HTTPClient.Task,
_ buffer: ByteBuffer
) -> EventLoopFuture {
// this is executed when we receive parts of the response body, could be called zero or more times
count += buffer.readableBytes
// in case backpressure is needed, all reads will be paused until returned future is resolved
return task.eventLoop.makeSucceededFuture(())
}func didFinishRequest(task: HTTPClient.Task) throws -> Int {
// this is called when the request is fully read, called once
// this is where you return a result or throw any errors you require to propagate to the client
return count
}func didReceiveError(task: HTTPClient.Task, _ error: Error) {
// this is called when we receive any network-related error, called once
}
}let request = try HTTPClient.Request(url: "https://apple.com/")
let delegate = CountingDelegate()HTTPClient.shared.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in
print(count)
}
```### File downloads
Based on the `HTTPClientResponseDelegate` example above you can build more complex delegates,
the built-in `FileDownloadDelegate` is one of them. It allows streaming the downloaded data
asynchronously, while reporting the download progress at the same time, like in the following
example:```swift
let request = try HTTPClient.Request(
url: "https://swift.org/builds/development/ubuntu1804/latest-build.yml"
)let delegate = try FileDownloadDelegate(path: "/tmp/latest-build.yml", reportProgress: {
if let totalBytes = $0.totalBytes {
print("Total bytes count: \(totalBytes)")
}
print("Downloaded \($0.receivedBytes) bytes so far")
})HTTPClient.shared.execute(request: request, delegate: delegate).futureResult
.whenSuccess { progress in
if let totalBytes = progress.totalBytes {
print("Final total bytes count: \(totalBytes)")
}
print("Downloaded finished with \(progress.receivedBytes) bytes downloaded")
}
```### Unix Domain Socket Paths
Connecting to servers bound to socket paths is easy:
```swift
HTTPClient.shared.execute(
.GET,
socketPath: "/tmp/myServer.socket",
urlPath: "/path/to/resource"
).whenComplete (...)
```Connecting over TLS to a unix domain socket path is possible as well:
```swift
HTTPClient.shared.execute(
.POST,
secureSocketPath: "/tmp/myServer.socket",
urlPath: "/path/to/resource",
body: .string("hello")
).whenComplete (...)
```Direct URLs can easily be constructed to be executed in other scenarios:
```swift
let socketPathBasedURL = URL(
httpURLWithSocketPath: "/tmp/myServer.socket",
uri: "/path/to/resource"
)
let secureSocketPathBasedURL = URL(
httpsURLWithSocketPath: "/tmp/myServer.socket",
uri: "/path/to/resource"
)
```### Disabling HTTP/2
The exclusive use of HTTP/1 is possible by setting `httpVersion` to `.http1Only` on `HTTPClient.Configuration`:
```swift
var configuration = HTTPClient.Configuration()
configuration.httpVersion = .http1Only
let client = HTTPClient(
eventLoopGroupProvider: .singleton,
configuration: configuration
)
```## Security
Please have a look at [SECURITY.md](SECURITY.md) for AsyncHTTPClient's security process.
## Supported Versions
The most recent versions of AsyncHTTPClient support Swift 5.6 and newer. The minimum Swift version supported by AsyncHTTPClient releases are detailed below:
AsyncHTTPClient | Minimum Swift Version
--------------------|----------------------
`1.0.0 ..< 1.5.0` | 5.0
`1.5.0 ..< 1.10.0` | 5.2
`1.10.0 ..< 1.13.0` | 5.4
`1.13.0 ..< 1.18.0` | 5.5.2
`1.18.0 ..< 1.20.0` | 5.6
`1.20.0 ..< 1.21.0` | 5.7
`1.21.0 ...` | 5.8