diff --git a/Examples/GetJSON/GetJSON.swift b/Examples/GetJSON/GetJSON.swift deleted file mode 100644 index 1af7a5144..000000000 --- a/Examples/GetJSON/GetJSON.swift +++ /dev/null @@ -1,52 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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: .singleton) - 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/JSON/JSON.swift b/Examples/JSON/JSON.swift new file mode 100644 index 000000000..619a641ec --- /dev/null +++ b/Examples/JSON/JSON.swift @@ -0,0 +1,109 @@ +//===----------------------------------------------------------------------===// +// +// 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 JSON { + + static func main() async throws { + try await getJSON() + try await postJSON() + } + + static func getJSON() async throws { + let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + 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() + } + + static func postJSON() async throws { + let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + + let comic: Comic = Comic( + num: 0, + title: "Adventures of Super Sally", + day: "17", + month: "4", + year: "2025", + img: "https://www.w3.org/Icons/w3c_main.png", + alt: "Adventures of Super Sally, a super hero with many powers", + news: "Today we learn about super heroes!", + link: "http://comics.com/super-sally", + transcript: "Once upon a time, there was a super hero named Super Sally. She had many powers and was a hero to many." + ) + + do { + var request = HTTPClientRequest(url: "https://httpbin.org/post") + request.headers.add(name: "Content-Type", value: "application/json") + request.headers.add(name: "Accept", value: "application/json") + request.method = .POST + + let jsonData = try JSONEncoder().encode(comic) + request.body = .bytes(jsonData) + + let response = try await httpClient.execute(request, timeout: .seconds(30)) + let responseBody = 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` + + // httpbin.org returns a JSON response with what we sent over the HTTP POST request, + // the json should be identical + let returnedComic = try JSONDecoder().decode(Comic.self, from: responseBody) + assert(comic.title == returnedComic.title) + assert(comic.img == returnedComic.img) + assert(comic.alt == returnedComic.alt) + assert(comic.day == returnedComic.day) + assert(comic.month == returnedComic.month) + assert(comic.year == returnedComic.year) + assert(comic.num == returnedComic.num) + assert(comic.transcript == returnedComic.transcript) + assert(comic.news == returnedComic.news) + assert(comic.link == returnedComic.link) + dump(returnedComic) + } 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 index 9986b17b5..34bfae588 100644 --- a/Examples/Package.swift +++ b/Examples/Package.swift @@ -25,7 +25,7 @@ let package = Package( ], products: [ .executable(name: "GetHTML", targets: ["GetHTML"]), - .executable(name: "GetJSON", targets: ["GetJSON"]), + .executable(name: "JSON", targets: ["JSON"]), .executable(name: "StreamingByteCounter", targets: ["StreamingByteCounter"]), ], dependencies: [ @@ -47,13 +47,13 @@ let package = Package( path: "GetHTML" ), .executableTarget( - name: "GetJSON", + name: "JSON", dependencies: [ .product(name: "AsyncHTTPClient", package: "async-http-client"), .product(name: "NIOCore", package: "swift-nio"), .product(name: "NIOFoundationCompat", package: "swift-nio"), ], - path: "GetJSON" + path: "JSON" ), .executableTarget( name: "StreamingByteCounter", diff --git a/Examples/README.md b/Examples/README.md index 849999f99..ad4bf3fd9 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -14,10 +14,11 @@ To run other examples you can just replace `GetHTML` with the name of the exampl 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) +## [JSON](./JSON/JSON.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. +This example demonstrates both GET and POST requests with JSON data: +1. It sends a HTTP GET request to `https://xkcd.com/info.0.json`, awaits and prints the HTTP Response Head, then buffers the full response body in memory, decodes the buffer using a `JSONDecoder` and dumps the decoded response. +2. It also performs a HTTP POST request to `https://httpbin.org/post` with a JSON payload, sending a Comic object and validating the response data matches what was sent. ## [StreamingByteCounter](./StreamingByteCounter/StreamingByteCounter.swift) diff --git a/README.md b/README.md index 871eb910b..a82808fdd 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,46 @@ HTTPClient.shared.execute(request: request).whenComplete { result in } ``` +#### Simple JSON request + +See [Examples/JSON/JSON.swift](./Examples/JSON/JSON.swift) for a simple JSON HTTP GET and POST example. + +```swift +import AsyncHTTPClient +import NIOFoundationCompat + +struct Car: Codable { + var make: String + var model: String + var year: Int +} + +static func getJSON() async throws { + let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + let car: Car = Car(make: "Toyota", model: "Camry", year: 2023) + let request = HTTPClientRequest(url: "https://api.example.com/cars/123") + let response = try await httpClient.execute(request, timeout: .seconds(30)) + + let carData = try await response.body.collect(upTo: 1024 * 1024) // 1 MB + let car: Car = try JSONDecoder().decode(Car.self, from: carData) + try await httpClient.shutdown() +} + +static func postJSON() async throws { + let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + let car: Car = Car(make: "Toyota", model: "Camry", year: 2023) + let jsonData = try JSONEncoder().encode(car) + + var request = HTTPClientRequest(url: "https://api.example.com/cars/123") + request.headers.add(name: "Content-Type", value: "application/json") + request.headers.add(name: "Accept", value: "application/json") + request.method = .POST + request.body = .bytes(jsonData) + try await httpClient.shutdown() +} +``` + + ### Redirects following The 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: