From 7989a97726ce026be4f4858c5b50a3ee44f7e8d2 Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Mon, 7 Feb 2022 17:51:53 +0100 Subject: [PATCH 1/3] update README.md for async/await --- README.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1ce470bbc..009918f48 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ This package provides simple HTTP Client library built on top of SwiftNIO. This library provides the following: +- First class support for Swift Concurrency (since version 1.9.0) - Asynchronous and non-blocking request methods - Simple follow-redirects (cookie headers are dropped) - Streaming body download @@ -11,7 +12,7 @@ This library provides the following: --- -**NOTE**: You will need [Xcode 11.4](https://apps.apple.com/gb/app/xcode/id497799835?mt=12) or [Swift 5.2](https://swift.org/download/#swift-52) to try out `AsyncHTTPClient`. +**NOTE**: You will need [Xcode 13.2](https://apps.apple.com/gb/app/xcode/id497799835?mt=12) or [Swift 5.5.2](https://swift.org/download/#swift-552) to try out `AsyncHTTPClient`s new async/await APIs. --- @@ -21,7 +22,7 @@ This library provides the following: Add the following entry in your Package.swift to start using HTTPClient: ```swift -.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0") +.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0") ``` and `AsyncHTTPClient` dependency to your target: ```swift @@ -40,7 +41,21 @@ If your application does not use SwiftNIO yet, it is acceptable to use `eventLoo import AsyncHTTPClient let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) -httpClient.get(url: "https://swift.org").whenComplete { result in + +/// MARK: - Using Swift Concurrency +let request = HTTPClientRequest(url: "https://apple.com/") +let response = try await httpClient.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.get(url: "https://apple.com/").whenComplete { result in switch result { case .failure(let error): // process error @@ -58,7 +73,32 @@ You should always shut down `HTTPClient` instances you created using `try httpCl ## 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: +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 AsyncHTTPClient + +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +do { + 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.execute(request, timeout: .seconds(30)) + if response.status == .ok { + // handle response + } else { + // handle remote error + } +} catch { + // handle error +} +// it's important to shutdown the httpClient after all requests are done, even if one failed +try await httpClient.shutdown() +``` + +#### Using SwiftNIO EventLoopFuture ```swift import AsyncHTTPClient @@ -67,7 +107,7 @@ defer { try? httpClient.syncShutdown() } -var request = try HTTPClient.Request(url: "https://swift.org", method: .POST) +var request = try HTTPClient.Request(url: "https://apple.com/", method: .POST) request.headers.add(name: "User-Agent", value: "Swift HTTPClient") request.body = .string("some-body") @@ -105,7 +145,38 @@ 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. 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: +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 +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + +let request = HTTPClientRequest(url: "https://apple.com/") +let response = try await httpClient.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. +for try await buffer in response.body { + // For this example, we are just interested in the size of the fragment + receivedBytes += buffer.readableBytes + + if 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))%") + } + // in case backpressure is needed, all reads will be paused until the end of the for loop. +} +print("did receive \(receivedBytes) bytes") +``` + +#### Using HTTPClientResponseDelegate and SwiftNIO EventLoopFuture + ```swift import NIOCore import NIOHTTP1 @@ -158,7 +229,7 @@ class CountingDelegate: HTTPClientResponseDelegate { } } -let request = try HTTPClient.Request(url: "https://swift.org") +let request = try HTTPClient.Request(url: "https://apple.com/") let delegate = CountingDelegate() httpClient.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in From 6e974af0de72f882125d8c00608833627b5c7223 Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Tue, 8 Feb 2022 13:16:50 +0100 Subject: [PATCH 2/3] fix review comments --- README.md | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 009918f48..c1573f55f 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,9 @@ You should always shut down `HTTPClient` instances you created using `try httpCl ## 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 AsyncHTTPClient @@ -99,6 +101,7 @@ try await httpClient.shutdown() ``` #### Using SwiftNIO EventLoopFuture + ```swift import AsyncHTTPClient @@ -151,28 +154,33 @@ The following example demonstrates how to count the number of bytes in a streami #### Using Swift Concurrency ```swift let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) - -let request = HTTPClientRequest(url: "https://apple.com/") -let response = try await httpClient.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. -for try await buffer in response.body { - // For this example, we are just interested in the size of the fragment - receivedBytes += buffer.readableBytes - - if 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))%") +do { + let request = HTTPClientRequest(url: "https://apple.com/") + let response = try await httpClient.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.readableBytes + + if 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))%") + } } - // in case backpressure is needed, all reads will be paused until the end of the for loop. + print("did receive \(receivedBytes) bytes") +} catch { + print("request failed:", error) } -print("did receive \(receivedBytes) bytes") +// it is important to shutdown the httpClient after all requests are done, even if one failed +try await httpClient.shutdown() ``` #### Using HTTPClientResponseDelegate and SwiftNIO EventLoopFuture From dd0ab0a9c717306c4e8b58dafd9ef5da0827e7ea Mon Sep 17 00:00:00 2001 From: David Nadoba Date: Wed, 9 Feb 2022 17:38:39 +0100 Subject: [PATCH 3/3] add space before and after division operator --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1573f55f..e379b079b 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ do { if let expectedBytes = expectedBytes { // if the body size is known, we calculate a progress indicator - let progress = Double(receivedBytes)/Double(expectedBytes) + let progress = Double(receivedBytes) / Double(expectedBytes) print("progress: \(Int(progress * 100))%") } }