Skip to content

Commit aae1d1b

Browse files
committed
Rename GetJSON to JSON and add POST example with request/response validation
1 parent 16886fd commit aae1d1b

File tree

5 files changed

+156
-58
lines changed

5 files changed

+156
-58
lines changed

Examples/GetJSON/GetJSON.swift

-52
This file was deleted.

Examples/JSON/JSON.swift

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import AsyncHTTPClient
16+
import Foundation
17+
import NIOCore
18+
import NIOFoundationCompat
19+
20+
struct Comic: Codable{
21+
var num: Int
22+
var title: String
23+
var day: String
24+
var month: String
25+
var year: String
26+
var img: String
27+
var alt: String
28+
var news: String
29+
var link: String
30+
var transcript: String
31+
}
32+
33+
@main
34+
struct JSON {
35+
36+
static func main() async throws {
37+
try await getJSON()
38+
try await postJSON()
39+
}
40+
41+
static func getJSON() async throws {
42+
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
43+
do {
44+
let request = HTTPClientRequest(url: "https://xkcd.com/info.0.json")
45+
let response = try await httpClient.execute(request, timeout: .seconds(30))
46+
print("HTTP head", response)
47+
let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB
48+
// we use an overload defined in `NIOFoundationCompat` for `decode(_:from:)` to
49+
// efficiently decode from a `ByteBuffer`
50+
let comic = try JSONDecoder().decode(Comic.self, from: body)
51+
dump(comic)
52+
} catch {
53+
print("request failed:", error)
54+
}
55+
// it is important to shutdown the httpClient after all requests are done, even if one failed
56+
try await httpClient.shutdown()
57+
}
58+
59+
static func postJSON() async throws {
60+
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
61+
62+
let comic: Comic = Comic(
63+
num: 0,
64+
title: "Adventures of Super Sally",
65+
day: "17",
66+
month: "4",
67+
year: "2025",
68+
img: "https://www.w3.org/Icons/w3c_main.png",
69+
alt: "Adventures of Super Sally, a super hero with many powers",
70+
news: "Today we learn about super heroes!",
71+
link: "http://comics.com/super-sally",
72+
transcript: "Once upon a time, there was a super hero named Super Sally. She had many powers and was a hero to many."
73+
)
74+
75+
do {
76+
var request = HTTPClientRequest(url: "https://httpbin.org/post")
77+
request.headers.add(name: "Content-Type", value: "application/json")
78+
request.headers.add(name: "Accept", value: "application/json")
79+
request.method = .POST
80+
81+
let jsonData = try JSONEncoder().encode(comic)
82+
request.body = .bytes(jsonData)
83+
84+
let response = try await httpClient.execute(request, timeout: .seconds(30))
85+
let responseBody = try await response.body.collect(upTo: 1024 * 1024) // 1 MB
86+
// we use an overload defined in `NIOFoundationCompat` for `decode(_:from:)` to
87+
// efficiently decode from a `ByteBuffer`
88+
89+
// httpbin.org returns a JSON response with what we sent over the HTTP POST request,
90+
// the json should be identical
91+
let returnedComic = try JSONDecoder().decode(Comic.self, from: responseBody)
92+
assert(comic.title == returnedComic.title)
93+
assert(comic.img == returnedComic.img)
94+
assert(comic.alt == returnedComic.alt)
95+
assert(comic.day == returnedComic.day)
96+
assert(comic.month == returnedComic.month)
97+
assert(comic.year == returnedComic.year)
98+
assert(comic.num == returnedComic.num)
99+
assert(comic.transcript == returnedComic.transcript)
100+
assert(comic.news == returnedComic.news)
101+
assert(comic.link == returnedComic.link)
102+
dump(returnedComic)
103+
} catch {
104+
print("request failed:", error)
105+
}
106+
// it is important to shutdown the httpClient after all requests are done, even if one failed
107+
try await httpClient.shutdown()
108+
}
109+
}

Examples/Package.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ let package = Package(
2525
],
2626
products: [
2727
.executable(name: "GetHTML", targets: ["GetHTML"]),
28-
.executable(name: "GetJSON", targets: ["GetJSON"]),
28+
.executable(name: "JSON", targets: ["JSON"]),
2929
.executable(name: "StreamingByteCounter", targets: ["StreamingByteCounter"]),
3030
],
3131
dependencies: [
@@ -47,13 +47,13 @@ let package = Package(
4747
path: "GetHTML"
4848
),
4949
.executableTarget(
50-
name: "GetJSON",
50+
name: "JSON",
5151
dependencies: [
5252
.product(name: "AsyncHTTPClient", package: "async-http-client"),
5353
.product(name: "NIOCore", package: "swift-nio"),
5454
.product(name: "NIOFoundationCompat", package: "swift-nio"),
5555
],
56-
path: "GetJSON"
56+
path: "JSON"
5757
),
5858
.executableTarget(
5959
name: "StreamingByteCounter",

Examples/README.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ To run other examples you can just replace `GetHTML` with the name of the exampl
1414
This examples sends a HTTP GET request to `https://apple.com/` and first `await`s and `print`s the HTTP Response Head.
1515
Afterwards it buffers the full response body in memory and prints the response as a `String`.
1616

17-
## [GetJSON](./GetJSON/GetJSON.swift)
17+
## [JSON](./JSON/JSON.swift)
1818

19-
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.
20-
Afterwards it buffers the full response body in memory, decodes the buffer using a `JSONDecoder` and `dump`s the decoded response.
19+
This example demonstrates both GET and POST requests with JSON data:
20+
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.
21+
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.
2122

2223
## [StreamingByteCounter](./StreamingByteCounter/StreamingByteCounter.swift)
2324

README.md

+40
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,46 @@ HTTPClient.shared.execute(request: request).whenComplete { result in
113113
}
114114
```
115115

116+
#### Simple JSON request
117+
118+
See [Examples/JSON/JSON.swift](./Examples/JSON/JSON.swift) for a simple JSON HTTP GET and POST example.
119+
120+
```swift
121+
import AsyncHTTPClient
122+
import NIOFoundationCompat
123+
124+
struct Car: Codable {
125+
var make: String
126+
var model: String
127+
var year: Int
128+
}
129+
130+
static func getJSON() async throws {
131+
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
132+
let car: Car = Car(make: "Toyota", model: "Camry", year: 2023)
133+
let request = HTTPClientRequest(url: "https://api.example.com/cars/123")
134+
let response = try await httpClient.execute(request, timeout: .seconds(30))
135+
136+
let carData = try await response.body.collect(upTo: 1024 * 1024) // 1 MB
137+
let car: Car = try JSONDecoder().decode(Car.self, from: carData)
138+
try await httpClient.shutdown()
139+
}
140+
141+
static func postJSON() async throws {
142+
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
143+
let car: Car = Car(make: "Toyota", model: "Camry", year: 2023)
144+
let jsonData = try JSONEncoder().encode(car)
145+
146+
var request = HTTPClientRequest(url: "https://api.example.com/cars/123")
147+
request.headers.add(name: "Content-Type", value: "application/json")
148+
request.headers.add(name: "Accept", value: "application/json")
149+
request.method = .POST
150+
request.body = .bytes(jsonData)
151+
try await httpClient.shutdown()
152+
}
153+
```
154+
155+
116156
### Redirects following
117157

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

0 commit comments

Comments
 (0)