diff --git a/Examples/GetHTML/GetHTML.swift b/Examples/GetHTML/GetHTML.swift new file mode 100644 index 000000000..dfefa922b --- /dev/null +++ b/Examples/GetHTML/GetHTML.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AsyncHTTPClient +import NIOCore + +@main +struct GetHTML { + static func main() async throws { + let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + do { + let request = HTTPClientRequest(url: "https://apple.com") + let response = try await httpClient.execute(request, timeout: .seconds(30)) + print("HTTP head", response) + let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB + print(String(buffer: body)) + } catch { + print("request failed:", error) + } + // it is important to shutdown the httpClient after all requests are done, even if one failed + try await httpClient.shutdown() + } +} diff --git a/Examples/GetJSON/GetJSON.swift b/Examples/GetJSON/GetJSON.swift new file mode 100644 index 000000000..ae58ffeaa --- /dev/null +++ b/Examples/GetJSON/GetJSON.swift @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AsyncHTTPClient +import Foundation +import NIOCore +import NIOFoundationCompat + +struct Comic: Codable { + var num: Int + var title: String + var day: String + var month: String + var year: String + var img: String + var alt: String + var news: String + var link: String + var transcript: String +} + +@main +struct GetJSON { + static func main() async throws { + let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + do { + let request = HTTPClientRequest(url: "https://xkcd.com/info.0.json") + let response = try await httpClient.execute(request, timeout: .seconds(30)) + print("HTTP head", response) + let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB + // we use an overload defined in `NIOFoundationCompat` for `decode(_:from:)` to + // efficiently decode from a `ByteBuffer` + let comic = try JSONDecoder().decode(Comic.self, from: body) + dump(comic) + } catch { + print("request failed:", error) + } + // it is important to shutdown the httpClient after all requests are done, even if one failed + try await httpClient.shutdown() + } +} diff --git a/Examples/Package.swift b/Examples/Package.swift new file mode 100644 index 000000000..58714eb5d --- /dev/null +++ b/Examples/Package.swift @@ -0,0 +1,64 @@ +// swift-tools-version:5.5 +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2022 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import PackageDescription + +let package = Package( + name: "async-http-client-examples", + platforms: [ + .macOS(.v10_15), + .iOS(.v13), + .tvOS(.v13), + .watchOS(.v6), + ], + products: [ + .executable(name: "GetHTML", targets: ["GetHTML"]), + .executable(name: "GetJSON", targets: ["GetJSON"]), + .executable(name: "StreamingByteCounter", targets: ["StreamingByteCounter"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-nio.git", .branch("main")), + + // in real-world projects this would be + // .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0") + .package(name: "async-http-client", path: "../"), + ], + targets: [ + // MARK: - Examples + + .executableTarget( + name: "GetHTML", + dependencies: [ + .product(name: "AsyncHTTPClient", package: "async-http-client"), + .product(name: "NIOCore", package: "swift-nio"), + ], path: "GetHTML" + ), + .executableTarget( + name: "GetJSON", + dependencies: [ + .product(name: "AsyncHTTPClient", package: "async-http-client"), + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + ], path: "GetJSON" + ), + .executableTarget( + name: "StreamingByteCounter", + dependencies: [ + .product(name: "AsyncHTTPClient", package: "async-http-client"), + .product(name: "NIOCore", package: "swift-nio"), + ], path: "StreamingByteCounter" + ), + ] +) diff --git a/Examples/README.md b/Examples/README.md new file mode 100644 index 000000000..849999f99 --- /dev/null +++ b/Examples/README.md @@ -0,0 +1,27 @@ +# Examples +This folder includes a couple of Examples for `AsyncHTTPClient`. +You can run them by opening the `Package.swift` in this folder through Xcode. +In Xcode you can then select the scheme for the example you want run e.g. `GetHTML`. + +You can also run the examples from the command line by executing the follow command in this folder: +``` +swift run GetHTML +``` +To run other examples you can just replace `GetHTML` with the name of the example you want to run. + +## [GetHTML](./GetHTML/GetHTML.swift) + +This examples sends a HTTP GET request to `https://apple.com/` and first `await`s and `print`s the HTTP Response Head. +Afterwards it buffers the full response body in memory and prints the response as a `String`. + +## [GetJSON](./GetJSON/GetJSON.swift) + +This examples sends a HTTP GET request to `https://xkcd.com/info.0.json` and first `await`s and `print`s the HTTP Response Head. +Afterwards it buffers the full response body in memory, decodes the buffer using a `JSONDecoder` and `dump`s the decoded response. + +## [StreamingByteCounter](./StreamingByteCounter/StreamingByteCounter.swift) + +This examples sends a HTTP GET request to `https://apple.com/` and first `await`s and `print`s the HTTP Response Head. +Afterwards it asynchronously iterates over all body fragments, counts the received bytes and prints a progress indicator (if the server send a content-length header). +At the end the total received bytes are printed. +Note that we drop all received fragment and therefore do **not** buffer the whole response body in-memory. diff --git a/Examples/StreamingByteCounter/StreamingByteCounter.swift b/Examples/StreamingByteCounter/StreamingByteCounter.swift new file mode 100644 index 000000000..dc340d14b --- /dev/null +++ b/Examples/StreamingByteCounter/StreamingByteCounter.swift @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AsyncHTTPClient +import NIOCore + +@main +struct StreamingByteCounter { + static func main() async throws { + let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + 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))%") + } + } + print("did receive \(receivedBytes) bytes") + } catch { + print("request failed:", error) + } + // it is important to shutdown the httpClient after all requests are done, even if one failed + try await httpClient.shutdown() + } +}