Swiftpack.co - Package - swift-server/async-http-client


This package provides simple HTTP Client library built on top of SwiftNIO.

This library provides the following:

  1. Asynchronous and non-blocking request methods
  2. Simple follow-redirects (cookie headers are dropped)
  3. Streaming body download
  4. TLS support
  5. 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 (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:

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 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 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<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

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:

let client = HTTPClient(eventLoopGroupProvider: .createNew)
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")

client.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:

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
    socketPath: "/tmp/myServer.socket", 
    urlPath: "/path/to/resource"
).whenComplete (...)

Connecting over TLS to a unix domain socket path is possible as well:

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
    secureSocketPath: "/tmp/myServer.socket", 
    urlPath: "/path/to/resource", 
    body: .string("hello")
).whenComplete (...)

Direct URLs can easily be contructed to be executed in other scenarios:

let socketPathBasedURL = URL(
    httpURLWithSocketPath: "/tmp/myServer.socket", 
    uri: "/path/to/resource"
let secureSocketPathBasedURL = URL(
    httpsURLWithSocketPath: "/tmp/myServer.socket", 
    uri: "/path/to/resource"


Stars: 389

Used By

Total: 0


AsyncHTTPClient 1.2.2 -

SemVer Patch

  • Fixes incorrect timeout documentation (#118)
  • Improves handling of errors during TLS handshake (#313)
  • Adds support for two mandatory date formats in Expires cookie header (#314)
  • Refactors and completely hides internal state of HTTP1ConnectionProvider (#234)

AsyncHTTPClient 1.2.1 -

SemVer Patch

  • Fixes a double-release issue that can lead to a crash (#295, #298)

AsyncHTTPClient 1.2.0 -

SemVer Minor

  • Connection Pooling (#105, #180, #170, #169, patch credit @adtrevor)
  • Support NIO Transport Services (#184, patch credit to @adam-fowler)
  • Logging support (#227)
  • Implement asynchronous shutdown (#183)
  • Adds version to response (#182)

SemVer Patch

  • Fixes for code snippets in README and comments (#276, #272, #271, #266, patch credit to @MaxDesiatov)
  • Added body length checks and improved flow for body-related header validation (#270, #247, #215, #269, #267, #255, #219)
  • Fixes for error propagation (#245, #221)
  • Fix location header lookup (#186)
  • Improved header names verification (#191, #211, patch credit to @fabianfett)
  • Improved handling of socket-based requests (#259, #235, #228, patch credit to @dimitribouniol)
  • Programming model fixes (#246, #241)
  • Cookie parsing fixes (#210, #208, #207)
  • Added default value for queue in .shutdown() (#279, patch credit to @adam-fowler)
  • Bad certificate can lead to errSSLHandshakeFail or errSSLBadCert (#236)
  • Use SwiftLogNoOpLogHandler from swift-log (#282, patch credit to @adam-fowler)
  • The host header should also include the port (#237,, patch credit to @adam-fowler)
  • Enable clients to call URLs that include %2F as an escaped backslash (#201, patch credit to @iainsmith)
  • Fixes and tests for connection pool (#288, #284, #192, #268, #240, #254, #225, #167)
  • Use standard ByteBuffer construction methods (#262)
  • Various tests changes (#224, #223, #216)
  • Various small changes (#165, #166, #257, #261)
  • Various CI fixes (#193, #173, #283)

AsyncHTTPClient 1.1.1 -

SemVer Patch

  • fixes case-sensitive location header parsing (#185)

AsyncHTTPClient 1.1.0 -

SemVer Minor

  • Support UNIX Domain Sockets (#151, patch credit to @krzyzanowskim)
  • add a public init to StreamWriter (#161)
  • ResponseAccumulator is now public (#155, patch credit to @fabianfett)

SemVer Patch

  • require secure NIO version (#162)
  • add a test that does many equal concurrent requests (#160)
  • fix usage of older api in readme example (#156)
  • Make public class public contrictible (#154, patch credit to @krzyzanowskim)
  • Remove parts of #139 relying on the Network framework (#147, patch credit to @adtrevor)
  • Bugfix HTTPS SNI and IP Address (#139, patch credit to @StrangeDays)
  • add test where server sends connection: close (#143)
  • various other small changes (#163, #159, #157, #145, #142)

AsyncHTTPClient 1.0.1 -

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 -

  • add response decompression support (#86)
  • add support for redirect limits (#113)

1.0.0-alpha.4 -

  • 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 -

  • 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 -

  • added NIO event loop preference as an argument for execute (#79)
  • renamed rename didReceivePart to didReceiveBodyPart
  • added option to ignore unclean SSL shutdowns

Alpha Release -