AsyncHTTPClient
This package provides simple HTTP Client library built on top of SwiftNIO.
This library provides the following:
- Asynchronous and non-blocking request methods
- Simple follow-redirects (cookie headers are dropped)
- Streaming body download
- TLS support
- Cookie parsing (but not storage)
NOTE: You will need Xcode 10.2 or Swift 5.0 to try out AsyncHTTPClient
.
Getting Started
Adding the dependency
Add the following entry in your Package.swift
to start using HTTPClient
:
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0")
and AsyncHTTPClient
dependency to your target:
.target(name: "MyApp", dependencies: ["AsyncHTTPClient"]),
Request-Response API
The code snippet below illustrates how to make a simple GET request to a remote server.
Please note that the example will spawn a new EventLoopGroup
which will create fresh threads which is a very costly operation. In a real-world application that uses SwiftNIO for other parts of your application (for example a web server), please prefer eventLoopGroupProvider: .shared(myExistingEventLoopGroup)
to share the EventLoopGroup
used by AsyncHTTPClient with other parts of your application.
If your application does not use SwiftNIO yet, it is acceptable to use eventLoopGroupProvider: .createNew
but please make sure to share the returned HTTPClient
instance throughout your whole application. Do not create a large number of HTTPClient
instances with eventLoopGroupProvider: .createNew
, this is very wasteful and might exhaust the resources of your program.
import AsyncHTTPClient
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
httpClient.get(url: "https://swift.org").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
}
}
}
You should always shut down HTTPClient
instances you created using try httpClient.syncShutdown()
. Please note that you must not call httpClient.syncShutdown
before all requests of the HTTP client have finished, or else the in-flight requests will likely fail because their network connections are interrupted.
Usage guide
Most common HTTP methods are supported out of the box. In case you need to have more control over the method, or you want to add headers or body, use the HTTPRequest
struct:
import AsyncHTTPClient
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
defer {
try? httpClient.syncShutdown()
}
var request = try HTTPClient.Request(url: "https://swift.org", method: .POST)
request.headers.add(name: "User-Agent", value: "Swift HTTPClient")
request.body = .string("some-body")
httpClient.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
Enable follow-redirects behavior using the client configuration:
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew,
configuration: HTTPClient.Configuration(followRedirects: true))
Timeouts
Timeouts (connect and read) can also be set using the client configuration:
let timeout = HTTPClient.Timeout(connect: .seconds(1), read: .seconds(1))
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew,
configuration: HTTPClient.Configuration(timeout: timeout))
or on a per-request basis:
let timeout = HTTPClient.Timeout(connect: .seconds(1), read: .seconds(1))
httpClient.execute(request: request, timeout: timeout)
Streaming
When dealing with larger amount of data, it's critical to stream the response body instead of aggregating in-memory. Handling a response stream is done using a delegate protocol. The following example demonstrates how to count the number of bytes in a streaming response body:
import NIO
import NIOHTTP1
class CountingDelegate: HTTPClientResponseDelegate {
typealias Response = Int
var count = 0
func didSendRequestHead(task: HTTPClient.Task<Response>, _ head: HTTPRequestHead) {
// this is executed right after request head was sent, called once
}
func didSendRequestPart(task: HTTPClient.Task<Response>, _ part: IOData) {
// this is executed when request body part is sent, could be called zero or more times
}
func didSendRequest(task: HTTPClient.Task<Response>) {
// this is executed when request is fully sent, called once
}
func didReceiveHead(task: HTTPClient.Task<Response>, _ head: HTTPResponseHead) -> EventLoopFuture<Void> {
// this is executed when we receive HTTP Reponse 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<Response>, _ buffer: ByteBuffer) -> EventLoopFuture<Void> {
// 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<Response>) 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<Response>, _ error: Error) {
// this is called when we receive any network-related error, called once
}
}
let request = try HTTPClient.Request(url: "https://swift.org")
let delegate = CountingDelegate()
httpClient.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in
print(count)
}
Github
link |
Stars: 194 |
Help us keep the lights on
Dependencies
Used By
- Einstore/GitHubKit
- sergiusX/funcServer
- madsodgaard/GatewayAPIKit
- vapor-community/StripeKit
- vapor/toolbox
- Adrift001/QiNiuSDK
- Yasumoto/Enpitsu
- jp-fan-app/swift-client
- cpageler93/JiraSwift
- Einstore/Webug
- LiveUI/S3
- rahul9712/vapor-swift-nio
- touee/bilibili-scraper
- vapor-community/TelesignKit
- vapor-community/GoogleCloudKit
- Einstore/Systemator
- vapor-community/onesignal
- jasonmartens/swift-kafka
- suprie/swift_googleChat_bot
- fabianfett/swift-aws-lambda
- timprepscius/AltCoin
- spprichard/rawlie-eats-pi-client
- zachwick/LuluKit
- givip/Telegrammer
- touee/SwiftTask
- azurlane-api/azurlane-swift
- kyleishie/SwiftLambda
- timprepscius/SimpleProxy
- fabianfett/swift-app-store-receipt-validation
- IBM-Swift/SwiftyRequest
Total: 35
Releases
1.0.1 - Dec 10, 2019
SemVer Patch
- fix NIO deprecations & update to secure versions (#141)
- added extra test cases (#132, #140)
- update copyrights note (#125)
- check for api breakage and prepare to test with thread sanitizer in ci (#122)
- Update dependency given in README to 1.0.0 (#121, patch credit to @ArmandGrillet)
1.0.0 - Oct 23, 2019
- add response decompression support (#86)
- add support for redirect limits (#113)
1.0.0-alpha.4 - Sep 25, 2019
- added swift 5.1 support in CI (#111)
- cancel is now sent as outbound user event (#110)
- trailing slash in path now preserved (#107)
EventLoop
preferences was refactored to make programming model simpler (#102)
1.0.0-alpha.3 - Sep 10, 2019
- Give generic parameters nice names (#99)
- Add authorization to proxy (#94)
- Refactor proxy configuration (#90)
- Redirects ignore EventLoop preference - (#89)
- Make sure HTTPClient is shutdown (#98)
- Fix missing set to maxAge in Cookie init function (#91)
- Nest timeout configuration type inside configuration (#93)
- Tolerate futures from arbitrary event loops (#96)
1.0.0-alpha.2 - Aug 21, 2019
- added NIO event loop preference as an argument for execute (#79)
- renamed
rename didReceivePart
todidReceiveBodyPart
- added option to ignore unclean SSL shutdowns