Introducing SwiftHttp
An superior Swift HTTP library to quickly create communication layers with API endpoints. The library tries to separate the consumer request logic from the request constructing and response dealing with. That is the primary cause why it has a HttpClient
protocol which can be utilized to carry out knowledge, obtain and add duties. You may implement your individual HttpClient, however SwiftHttp comes with a built-in UrlSessionHttpClient
based mostly on Basis networking.
So the consumer is liable for executing the requests, however we nonetheless have to explain the request itself one way or the other. That is the place the HttpRawRequest
object comes into play. You may simply create a base HttpUrl and carry out a request utilizing the HttpRawRequest object. When working with a uncooked request you possibly can specify extra header fields and a uncooked physique knowledge object too. 💪
let url = HttpUrl(scheme: "https",
host: "jsonplaceholder.typicode.com",
port: 80,
path: ["todos"],
useful resource: nil,
question: [:],
fragment: nil)
let req = HttpRawRequest(url: url, methodology: .get, headers: [:], physique: nil)
let consumer = UrlSessionHttpClient(session: .shared, log: true)
let response = strive await consumer.dataTask(req)
let todos = strive JSONDecoder().decode([Todo].self, from: response.knowledge)
The HTTP consumer can carry out community calls utilizing the brand new async / await Swift concurrency API. It’s doable to cancel a community request by wrapping it right into a structured concurrency Process.
let process = Process {
let api = TodoApi()
_ = strive await api.record()
}
DispatchQueue.world().asyncAfter(deadline: .now() + .milliseconds(10)) {
process.cancel()
}
do {
let _ = strive await process.worth
}
catch {
if (error as? URLError)?.code == .cancelled {
print("cancelled")
}
}
This can be a neat tick, you may also examine the rationale contained in the catch block, whether it is an URLError with a .cancelled code then the request was cancelled, in any other case it have to be some type of community error.
So that is how you need to use the consumer to carry out or cancel a community process, however normally you do not wish to work with uncooked knowledge, however encodable and decodable objects. If you work with such objects, you would possibly wish to validate the response headers and ship extra headers to tell the server about the kind of the physique knowledge. Simply take into consideration the Content material-Kind / Settle for header fields. 🤔
So we’d wish to ship extra headers alongside the request, plus it might be good to validate the standing code and response headers earlier than we attempt to parse the information. This looks as if a movement of frequent operations, first we encode the information, set the extra header fields, and when the response arrives we validate the standing code and the header fields, lastly we attempt to decode the information object. This can be a typical use case and SwiftHttp calls this workflow as a pipeline.
There are 4 varieties of built-in HTTP pipelines:
- Uncooked – Ship a uncooked knowledge request, return a uncooked knowledge response
- Encodable – Ship an encodable object, return a uncooked knowledge response
- Decodable – Ship a uncooked knowledge request, return a decodable object
- Codable – Ship an encodable object, return a decodable object
We will use a HttpRawPipeline and execute our request utilizing a consumer as an executor.
let baseUrl = HttpUrl(host: "jsonplaceholder.typicode.com")
let consumer = UrlSessionHttpClient(session: .shared, log: true)
let pipeline = HttpRawPipeline(url: baseUrl.path("todos"), methodology: .get)
let response = strive await pipeline.execute(consumer.dataTask)
let todos = strive JSONDecoder().decode([Todo].self, from: response.knowledge)
print(response.statusCode)
print(todos.rely)
On this case we have been utilizing the dataTask operate, however if you happen to count on the response to be an enormous file, you would possibly wish to think about using a downloadTask, or if you happen to’re importing a considerable amount of knowledge when sending the request, you must select the uploadTask operate. 💡
So on this case we needed to manually decode the Todo object from the uncooked HTTP response knowledge, however we are able to use the decodable pipeline to make issues much more easy.
let baseUrl = HttpUrl(host: "jsonplaceholder.typicode.com")
let consumer = UrlSessionHttpClient(session: .shared, log: true)
let pipeline = HttpDecodablePipeline<[Todo]>(url: baseUrl.path("todos"),
methodology: .get,
decoder: .json(JSONDecoder(), validators: [
HttpStatusCodeValidator(.ok),
HttpHeaderValidator(.key(.contentType)) {
$0.contains("application/json")
},
]))
let todos = strive await pipeline.execute(consumer.dataTask)
print(todos.rely)
As you possibly can see, on this case the as an alternative of returning the response, the pipeline can carry out extra validation and the decoding utilizing the offered decoder and validators. You may create your individual validators, there’s a HttpResponseValidator
protocol for this objective.
The encodable pipeline works like the identical, you possibly can specify the encoder, you possibly can present the encodable object and you will get again a HttpResponse
occasion.
let consumer = UrlSessionHttpClient(session: .shared, log: true)
let todo = Todo(id: 1, title: "lorem ipsum", accomplished: false)
let pipeline = HttpEncodablePipeline(url: baseUrl.path("todos"),
methodology: .put up,
physique: todo,
encoder: .json())
let response = strive await pipeline.execute(consumer.dataTask)
print(response.statusCode == .created)
The codable pipeline is a mixture of the encodable and decodable pipeline. 🙃
let baseUrl = HttpUrl(host: "jsonplaceholder.typicode.com")
let consumer = UrlSessionHttpClient(session: .shared, log: true)
let todo = Todo(id: 1, title: "lorem ipsum", accomplished: false)
let pipeline = HttpCodablePipeline<Todo, Todo>(url: baseUrl.path("todos", String(1)),
methodology: .put,
physique: todo,
encoder: .json(),
decoder: .json())
let todo = strive await pipeline.execute(consumer.dataTask)
print(todo.title)
As you possibly can see that is fairly a typical sample, and once we’re speaking with a REST API, we will carry out kind of the very same community calls for each single endpoint. SwiftHttp has a pipeline assortment protocol that you need to use to carry out requests with out the necessity of explicitly establishing these pipelines. Here is an instance:
import SwiftHttp
struct Todo: Codable {
let id: Int
let title: String
let accomplished: Bool
}
struct TodoApi: HttpCodablePipelineCollection {
let consumer: HttpClient = UrlSessionHttpClient(log: true)
let apiBaseUrl = HttpUrl(host: "jsonplaceholder.typicode.com")
func record() async throws -> [Todo] {
strive await decodableRequest(executor: consumer.dataTask,
url: apiBaseUrl.path("todos"),
methodology: .get)
}
}
let todos = strive await api.record()
When utilizing a HttpCodablePipelineCollection
you possibly can carry out an encodable, decodable or codable request utilizing an executor object. It will scale back the boilerplate code wanted to carry out a request and every thing goes to be kind protected due to the generic protocol oriented networking layer. You may setup as many pipeline collections as you want, it’s doable to make use of a shared consumer or you possibly can create a devoted consumer for every.
By the best way, if one thing goes incorrect with the request, or one of many validators fail, you possibly can all the time examine for the errors utilizing a do-try-catch block. 😅
do {
_ = strive await api.record()
}
catch HttpError.invalidStatusCode(let res) {
let decoder = HttpResponseDecoder<CustomError>(decoder: JSONDecoder())
do {
let error = strive decoder.decode(res.knowledge)
print(res.statusCode, error)
}
catch {
print(error.localizedDescription)
}
}
catch {
print(error.localizedDescription)
}
That is how SwiftHttp works in a nutshell, after all you possibly can setup customized encoders and decoders, however that is one other subject. If you’re within the undertaking, be at liberty to provide it a star on GitHub. We’ll use it sooner or later quite a bit each on the consumer and server facet. ⭐️⭐️⭐️