- Notifications
You must be signed in to change notification settings - Fork136
HTTP client library built on SwiftNIO
License
swift-server/async-http-client
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
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
- Automatic HTTP/2 over HTTPS (since version 1.7.0)
- Cookie parsing (but not storage)
NOTE: You will needXcode 11.4 orSwift 5.2 to try outAsyncHTTPClient.
Add the following entry in yourPackage.swift to start usingHTTPClient:
.package(url:"https://github.com/swift-server/async-http-client.git", from:"1.0.0")
andAsyncHTTPClient dependency to your target:
.target(name:"MyApp", dependencies:[.product(name:"AsyncHTTPClient",package:"async-http-client")]),
The code snippet below illustrates how to make a simple GET request to a remote server.
Please note that the example will spawn a newEventLoopGroup which willcreate fresh threads which is a very costly operation. In a real-world application that usesSwiftNIO for other parts of your application (for example a web server), please prefereventLoopGroupProvider: .shared(myExistingEventLoopGroup) to share theEventLoopGroup used by AsyncHTTPClient with other parts of your application.
If your application does not use SwiftNIO yet, it is acceptable to useeventLoopGroupProvider: .createNew but please make sure to share the returnedHTTPClient instance throughout your whole application. Do not create a large number ofHTTPClient instances witheventLoopGroupProvider: .createNew, this is very wasteful and might exhaust the resources of your program.
import AsyncHTTPClientlethttpClient=HTTPClient(eventLoopGroupProvider:.createNew)httpClient.get(url:"https://swift.org").whenComplete{ resultinswitch 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 downHTTPClient instances you created usingtry httpClient.syncShutdown(). Please note that you must not callhttpClient.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.
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 theHTTPRequest struct:
import AsyncHTTPClientlethttpClient=HTTPClient(eventLoopGroupProvider:.createNew)defer{try? httpClient.syncShutdown()}varrequest=tryHTTPClient.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{ resultinswitch result{case.failure(let error): // process error case.success(let response):if response.status==.ok{ // handle response}else{ // handle remote error}}}
Enable follow-redirects behavior using the client configuration:
lethttpClient=HTTPClient(eventLoopGroupProvider:.createNew, configuration:HTTPClient.Configuration(followRedirects:true))
Timeouts (connect and read) can also be set using the client configuration:
lettimeout=HTTPClient.Configuration.Timeout(connect:.seconds(1), read:.seconds(1))lethttpClient=HTTPClient(eventLoopGroupProvider:.createNew, configuration:HTTPClient.Configuration(timeout: timeout))
or on a per-request basis:
httpClient.execute(request: request, deadline:.now()+.milliseconds(1))
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 NIOCoreimport NIOHTTP1classCountingDelegate:HTTPClientResponseDelegate{typealiasResponse=Intvarcount=0func 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 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 resolvedreturn 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 resolvedreturn 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 clientreturn count}func didReceiveError(task:HTTPClient.Task<Response>, _ error:Error){ // this is called when we receive any network-related error, called once}}letrequest=tryHTTPClient.Request(url:"https://swift.org")letdelegate=CountingDelegate()httpClient.execute(request: request, delegate: delegate).futureResult.whenSuccess{ countinprint(count)}
Based on theHTTPClientResponseDelegate example above you can build more complex delegates,the built-inFileDownloadDelegate is one of them. It allows streaming the downloaded dataasynchronously, while reporting the download progress at the same time, like in the followingexample:
letclient=HTTPClient(eventLoopGroupProvider:.createNew)letrequest=tryHTTPClient.Request( url:"https://swift.org/builds/development/ubuntu1804/latest-build.yml")letdelegate=tryFileDownloadDelegate(path:"/tmp/latest-build.yml", reportProgress:{iflet totalBytes= $0.totalBytes{print("Total bytes count:\(totalBytes)")}print("Downloaded\($0.receivedBytes) bytes so far")})client.execute(request: request, delegate: delegate).futureResult.whenSuccess{ progressiniflet totalBytes= progress.totalBytes{print("Final total bytes count:\(totalBytes)")}print("Downloaded finished with\(progress.receivedBytes) bytes downloaded")}
Connecting to servers bound to socket paths is easy:
lethttpClient=HTTPClient(eventLoopGroupProvider:.createNew)httpClient.execute(.GET, socketPath:"/tmp/myServer.socket", urlPath:"/path/to/resource").whenComplete(...)
Connecting over TLS to a unix domain socket path is possible as well:
lethttpClient=HTTPClient(eventLoopGroupProvider:.createNew)httpClient.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:
letsocketPathBasedURL=URL( httpURLWithSocketPath:"/tmp/myServer.socket", uri:"/path/to/resource")letsecureSocketPathBasedURL=URL( httpsURLWithSocketPath:"/tmp/myServer.socket", uri:"/path/to/resource")
The exclusive use of HTTP/1 is possible by settinghttpVersion to.http1Only onHTTPClient.Configuration:
varconfiguration=HTTPClient.Configuration()configuration.httpVersion=.http1Onlyletclient=HTTPClient( eventLoopGroupProvider:.createNew, configuration: configuration)
Please have a look atSECURITY.md for AsyncHTTPClient's security process.
About
HTTP client library built on SwiftNIO
Topics
Resources
License
Code of conduct
Contributing
Security policy
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Uh oh!
There was an error while loading.Please reload this page.