{"id":13461805,"url":"https://github.com/swift-server/async-http-client","last_synced_at":"2026-02-11T12:15:18.111Z","repository":{"id":34763933,"uuid":"178287727","full_name":"swift-server/async-http-client","owner":"swift-server","description":"HTTP client library built on SwiftNIO","archived":false,"fork":false,"pushed_at":"2025-05-12T09:59:51.000Z","size":2835,"stargazers_count":974,"open_issues_count":117,"forks_count":125,"subscribers_count":27,"default_branch":"main","last_synced_at":"2025-05-13T17:07:45.598Z","etag":null,"topics":["http-client","swift-nio","swift-server","swift5"],"latest_commit_sha":null,"homepage":"https://swiftpackageindex.com/swift-server/async-http-client/main/documentation/asynchttpclient","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/swift-server.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-03-28T21:53:57.000Z","updated_at":"2025-05-11T14:34:31.000Z","dependencies_parsed_at":"2023-10-04T22:32:57.431Z","dependency_job_id":"b85eff19-7873-45d8-832e-4af860049848","html_url":"https://github.com/swift-server/async-http-client","commit_stats":{"total_commits":460,"total_committers":66,"mean_commits":6.96969696969697,"dds":0.8,"last_synced_commit":"bdaa3b18358b60e268afe82e8feb16418874f41b"},"previous_names":["swift-server/swift-nio-http-client"],"tags_count":64,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swift-server%2Fasync-http-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swift-server%2Fasync-http-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swift-server%2Fasync-http-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swift-server%2Fasync-http-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/swift-server","download_url":"https://codeload.github.com/swift-server/async-http-client/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253990506,"owners_count":21995776,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http-client","swift-nio","swift-server","swift5"],"created_at":"2024-07-31T11:00:58.102Z","updated_at":"2026-02-11T12:15:18.106Z","avatar_url":"https://github.com/swift-server.png","language":"Swift","funding_links":[],"categories":["Swift","Projects using SwiftNIO"],"sub_categories":["Packages \u0026 executables"],"readme":"# AsyncHTTPClient\nThis package provides an HTTP Client library built on top of SwiftNIO.\n\nThis library provides the following:\n- First class support for Swift Concurrency\n- Asynchronous and non-blocking request methods\n- Simple follow-redirects (cookie headers are dropped)\n- Streaming body download\n- TLS support\n- Automatic HTTP/2 over HTTPS\n- Cookie parsing (but not storage)\n\n## Getting Started\n\n#### Adding the dependency\nAdd the following entry in your \u003ccode\u003ePackage.swift\u003c/code\u003e to start using \u003ccode\u003eHTTPClient\u003c/code\u003e:\n\n```swift\n.package(url: \"https://github.com/swift-server/async-http-client.git\", from: \"1.9.0\")\n```\nand  `AsyncHTTPClient` dependency to your target:\n```swift\n.target(name: \"MyApp\", dependencies: [.product(name: \"AsyncHTTPClient\", package: \"async-http-client\")]),\n```\n\n#### Request-Response API\n\nThe code snippet below illustrates how to make a simple GET request to a remote server.\n\n```swift\nimport AsyncHTTPClient\n\n/// MARK: - Using Swift Concurrency\nlet request = HTTPClientRequest(url: \"https://apple.com/\")\nlet response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))\nprint(\"HTTP head\", response)\nif response.status == .ok {\n    let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB\n    // handle body\n} else {\n    // handle remote error\n}\n\n\n/// MARK: - Using SwiftNIO EventLoopFuture\nHTTPClient.shared.get(url: \"https://apple.com/\").whenComplete { result in\n    switch result {\n    case .failure(let error):\n        // process error\n    case .success(let response):\n        if response.status == .ok {\n            // handle response\n        } else {\n            // handle remote error\n        }\n    }\n}\n```\n\nIf 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.\n 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.\n\n### async/await examples\n\nExamples for the async/await API can be found in the [`Examples` folder](./Examples) in this Repository.\n\n## Usage guide\n\nThe 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:\n\n#### Using Swift Concurrency\n\n```swift\nimport AsyncHTTPClient\n\ndo {\n    var request = HTTPClientRequest(url: \"https://apple.com/\")\n    request.method = .POST\n    request.headers.add(name: \"User-Agent\", value: \"Swift HTTPClient\")\n    request.body = .bytes(ByteBuffer(string: \"some data\"))\n\n    let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))\n    if response.status == .ok {\n        // handle response\n    } else {\n        // handle remote error\n    }\n} catch {\n    // handle error\n}\n```\n\n#### Using SwiftNIO EventLoopFuture\n\n```swift\nimport AsyncHTTPClient\n\nvar request = try HTTPClient.Request(url: \"https://apple.com/\", method: .POST)\nrequest.headers.add(name: \"User-Agent\", value: \"Swift HTTPClient\")\nrequest.body = .string(\"some-body\")\n\nHTTPClient.shared.execute(request: request).whenComplete { result in\n    switch result {\n    case .failure(let error):\n        // process error\n    case .success(let response):\n        if response.status == .ok {\n            // handle response\n        } else {\n            // handle remote error\n        }\n    }\n}\n```\n\n### Redirects following\n\nThe 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:\n\n```swift\nlet httpClient = HTTPClient(eventLoopGroupProvider: .singleton,\n                            configuration: HTTPClient.Configuration(followRedirects: true))\n```\n\n### Timeouts\nTimeouts (connect and read) can also be set using the client configuration:\n```swift\nlet timeout = HTTPClient.Configuration.Timeout(connect: .seconds(1), read: .seconds(1))\nlet httpClient = HTTPClient(eventLoopGroupProvider: .singleton,\n                            configuration: HTTPClient.Configuration(timeout: timeout))\n```\nor on a per-request basis:\n```swift\nhttpClient.execute(request: request, deadline: .now() + .milliseconds(1))\n```\n\n### Streaming\nWhen dealing with larger amount of data, it's critical to stream the response body instead of aggregating in-memory.\nThe following example demonstrates how to count the number of bytes in a streaming response body:\n\n#### Using Swift Concurrency\n```swift\ndo {\n    let request = HTTPClientRequest(url: \"https://apple.com/\")\n    let response = try await HTTPClient.shared.execute(request, timeout: .seconds(30))\n    print(\"HTTP head\", response)\n\n    // if defined, the content-length headers announces the size of the body\n    let expectedBytes = response.headers.first(name: \"content-length\").flatMap(Int.init)\n\n    var receivedBytes = 0\n    // asynchronously iterates over all body fragments\n    // this loop will automatically propagate backpressure correctly\n    for try await buffer in response.body {\n        // for this example, we are just interested in the size of the fragment\n        receivedBytes += buffer.readableBytes\n\n        if let expectedBytes = expectedBytes {\n            // if the body size is known, we calculate a progress indicator\n            let progress = Double(receivedBytes) / Double(expectedBytes)\n            print(\"progress: \\(Int(progress * 100))%\")\n        }\n    }\n    print(\"did receive \\(receivedBytes) bytes\")\n} catch {\n    print(\"request failed:\", error)\n}\n```\n\n#### Using HTTPClientResponseDelegate and SwiftNIO EventLoopFuture\n\n```swift\nimport NIOCore\nimport NIOHTTP1\n\nclass CountingDelegate: HTTPClientResponseDelegate {\n    typealias Response = Int\n\n    var count = 0\n\n    func didSendRequestHead(task: HTTPClient.Task\u003cResponse\u003e, _ head: HTTPRequestHead) {\n        // this is executed right after request head was sent, called once\n    }\n\n    func didSendRequestPart(task: HTTPClient.Task\u003cResponse\u003e, _ part: IOData) {\n        // this is executed when request body part is sent, could be called zero or more times\n    }\n\n    func didSendRequest(task: HTTPClient.Task\u003cResponse\u003e) {\n        // this is executed when request is fully sent, called once\n    }\n\n    func didReceiveHead(\n        task: HTTPClient.Task\u003cResponse\u003e,\n        _ head: HTTPResponseHead\n    ) -\u003e EventLoopFuture\u003cVoid\u003e {\n        // this is executed when we receive HTTP response head part of the request\n        // (it contains response code and headers), called once in case backpressure\n        // is needed, all reads will be paused until returned future is resolved\n        return task.eventLoop.makeSucceededFuture(())\n    }\n\n    func didReceiveBodyPart(\n        task: HTTPClient.Task\u003cResponse\u003e,\n        _ buffer: ByteBuffer\n    ) -\u003e EventLoopFuture\u003cVoid\u003e {\n        // this is executed when we receive parts of the response body, could be called zero or more times\n        count += buffer.readableBytes\n        // in case backpressure is needed, all reads will be paused until returned future is resolved\n        return task.eventLoop.makeSucceededFuture(())\n    }\n\n    func didFinishRequest(task: HTTPClient.Task\u003cResponse\u003e) throws -\u003e Int {\n        // this is called when the request is fully read, called once\n        // this is where you return a result or throw any errors you require to propagate to the client\n        return count\n    }\n\n    func didReceiveError(task: HTTPClient.Task\u003cResponse\u003e, _ error: Error) {\n        // this is called when we receive any network-related error, called once\n    }\n}\n\nlet request = try HTTPClient.Request(url: \"https://apple.com/\")\nlet delegate = CountingDelegate()\n\nHTTPClient.shared.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in\n    print(count)\n}\n```\n\n### File downloads\n\nBased on the `HTTPClientResponseDelegate` example above you can build more complex delegates,\nthe built-in `FileDownloadDelegate` is one of them. It allows streaming the downloaded data\nasynchronously, while reporting the download progress at the same time, like in the following\nexample:\n\n```swift\nlet request = try HTTPClient.Request(\n    url: \"https://swift.org/builds/development/ubuntu1804/latest-build.yml\"\n)\n\nlet delegate = try FileDownloadDelegate(path: \"/tmp/latest-build.yml\", reportProgress: {\n    if let totalBytes = $0.totalBytes {\n        print(\"Total bytes count: \\(totalBytes)\")\n    }\n    print(\"Downloaded \\($0.receivedBytes) bytes so far\")\n})\n\nHTTPClient.shared.execute(request: request, delegate: delegate).futureResult\n    .whenSuccess { progress in\n        if let totalBytes = progress.totalBytes {\n            print(\"Final total bytes count: \\(totalBytes)\")\n        }\n        print(\"Downloaded finished with \\(progress.receivedBytes) bytes downloaded\")\n    }\n```\n\n### Unix Domain Socket Paths\nConnecting to servers bound to socket paths is easy:\n```swift\nHTTPClient.shared.execute(\n    .GET,\n    socketPath: \"/tmp/myServer.socket\",\n    urlPath: \"/path/to/resource\"\n).whenComplete (...)\n```\n\nConnecting over TLS to a unix domain socket path is possible as well:\n```swift\nHTTPClient.shared.execute(\n    .POST,\n    secureSocketPath: \"/tmp/myServer.socket\",\n    urlPath: \"/path/to/resource\",\n    body: .string(\"hello\")\n).whenComplete (...)\n```\n\nDirect URLs can easily be constructed to be executed in other scenarios:\n```swift\nlet socketPathBasedURL = URL(\n    httpURLWithSocketPath: \"/tmp/myServer.socket\",\n    uri: \"/path/to/resource\"\n)\nlet secureSocketPathBasedURL = URL(\n    httpsURLWithSocketPath: \"/tmp/myServer.socket\",\n    uri: \"/path/to/resource\"\n)\n```\n\n### Disabling HTTP/2\nThe exclusive use of HTTP/1 is possible by setting `httpVersion` to `.http1Only` on `HTTPClient.Configuration`:\n```swift\nvar configuration = HTTPClient.Configuration()\nconfiguration.httpVersion = .http1Only\nlet client = HTTPClient(\n    eventLoopGroupProvider: .singleton,\n    configuration: configuration\n)\n```\n\n## Security\n\nPlease have a look at [SECURITY.md](SECURITY.md) for AsyncHTTPClient's security process.\n\n## Supported Versions\n\nThe most recent versions of AsyncHTTPClient support Swift 6.0 and newer. The minimum Swift version supported by AsyncHTTPClient releases are detailed below:\n\nAsyncHTTPClient     | Minimum Swift Version\n--------------------|----------------------\n`1.0.0 ..\u003c 1.5.0`   | 5.0\n`1.5.0 ..\u003c 1.10.0`  | 5.2\n`1.10.0 ..\u003c 1.13.0` | 5.4\n`1.13.0 ..\u003c 1.18.0` | 5.5.2\n`1.18.0 ..\u003c 1.20.0` | 5.6\n`1.20.0 ..\u003c 1.21.0` | 5.7\n`1.21.0 ..\u003c 1.26.0` | 5.8\n`1.26.0 ..\u003c 1.27.0` | 5.9\n`1.27.0 ..\u003c 1.30.0` | 5.10\n`1.30.0 ...`        | 6.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswift-server%2Fasync-http-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswift-server%2Fasync-http-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswift-server%2Fasync-http-client/lists"}