Home IOS Development Async HTTP API shoppers in Swift

Async HTTP API shoppers in Swift

0
Async HTTP API shoppers in Swift

[ad_1]

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 principle cause why it has a HttpClient protocol which can be utilized to carry out information, obtain and add duties. You’ll be able to implement your individual HttpClient, however SwiftHttp comes with a built-in UrlSessionHttpClient primarily based on Basis networking.

So the consumer is chargeable 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’ll be able to simply create a base HttpUrl and carry out a request utilizing the HttpRawRequest object. When working with a uncooked request you may specify further header fields and a uncooked physique information 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, technique: .get, headers: [:], physique: nil)


let consumer = UrlSessionHttpClient(session: .shared, log: true)
let response = attempt await consumer.dataTask(req)


let todos = attempt JSONDecoder().decode([Todo].self, from: response.information)

The HTTP consumer can carry out community calls utilizing the brand new async / await Swift concurrency API. It’s attainable to cancel a community request by wrapping it right into a structured concurrency Job.

let activity = Job {
    let api = TodoApi()
    _ = attempt await api.checklist()
}

DispatchQueue.international().asyncAfter(deadline: .now() + .milliseconds(10)) {
    activity.cancel()
}

do {
    let _ = attempt await activity.worth
}
catch {
    if (error as? URLError)?.code == .cancelled {
        print("cancelled")
    }
}

This can be a neat tick, it’s also possible to test the explanation contained in the catch block, whether it is an URLError with a .cancelled code then the request was cancelled, in any other case it should be some type of community error.

So that is how you should utilize the consumer to carry out or cancel a community activity, however normally you do not need to work with uncooked information, however encodable and decodable objects. Whenever you work with such objects, you may need to validate the response headers and ship further headers to tell the server about the kind of the physique information. Simply take into consideration the Content material-Sort / Settle for header fields. 🤔

So we’d need to ship further headers alongside the request, plus it would be good to validate the standing code and response headers earlier than we attempt to parse the information. This looks as if a circulate of widespread 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 forms of built-in HTTP pipelines:

  • Uncooked – Ship a uncooked information request, return a uncooked information response
  • Encodable – Ship an encodable object, return a uncooked information response
  • Decodable – Ship a uncooked information 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"), technique: .get)

let response = attempt await pipeline.execute(consumer.dataTask)
let todos = attempt JSONDecoder().decode([Todo].self, from: response.information)
print(response.statusCode)
print(todos.rely)

On this case we had been utilizing the dataTask operate, however if you happen to count on the response to be an enormous file, you may need to think about using a downloadTask, or if you happen to’re importing a considerable amount of information when sending the request, it is best to select the uploadTask operate. 💡

So on this case we needed to manually decode the Todo object from the uncooked HTTP response information, however we will 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"),
                                             technique: .get,
                                             decoder: .json(JSONDecoder(), validators: [
                                                HttpStatusCodeValidator(.ok),
                                                HttpHeaderValidator(.key(.contentType)) {
                                                    $0.contains("application/json")
                                                },
                                             ]))

let todos = attempt await pipeline.execute(consumer.dataTask)
print(todos.rely)

As you may see, on this case the as a substitute of returning the response, the pipeline can carry out further validation and the decoding utilizing the offered decoder and validators. You’ll be able to create your individual validators, there’s a HttpResponseValidator protocol for this goal.

The encodable pipeline works like the identical, you may specify the encoder, you may 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"),
                                     technique: .put up,
                                     physique: todo,
                                     encoder: .json())

let response = attempt 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)),
                                               technique: .put,
                                               physique: todo,
                                               encoder: .json(),
                                               decoder: .json())

let todo = attempt await pipeline.execute(consumer.dataTask)
print(todo.title)

As you may see that is fairly a typical sample, and after we’re speaking with a REST API, we’ll carry out kind of the very same community calls for each single endpoint. SwiftHttp has a pipeline assortment protocol that you should utilize to carry out requests with out the necessity of explicitly organising these pipelines. This 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 checklist() async throws -> [Todo] {
        attempt await decodableRequest(executor: consumer.dataTask,
                                   url: apiBaseUrl.path("todos"),
                                   technique: .get)
    }    
}

let todos = attempt await api.checklist()

When utilizing a HttpCodablePipelineCollection you may carry out an encodable, decodable or codable request utilizing an executor object. This may cut back the boilerplate code wanted to carry out a request and every part goes to be kind protected because of the generic protocol oriented networking layer. You’ll be able to setup as many pipeline collections as you want, it’s attainable to make use of a shared consumer or you may create a devoted consumer for every.

By the way in which, if one thing goes fallacious with the request, or one of many validators fail, you may at all times test for the errors utilizing a do-try-catch block. 😅

do {
    _ = attempt await api.checklist()
}
catch HttpError.invalidStatusCode(let res) {
    
    let decoder = HttpResponseDecoder<CustomError>(decoder: JSONDecoder())
    do {
        let error = attempt decoder.decode(res.information)
        print(res.statusCode, error)
    }
    catch {
        print(error.localizedDescription)
    }
}
catch {
    print(error.localizedDescription)
}

That is how SwiftHttp works in a nutshell, in fact you may setup customized encoders and decoders, however that is one other matter. If you’re within the venture, be happy to present it a star on GitHub. We will use it sooner or later quite a bit each on the consumer and server facet. ⭐️⭐️⭐️

[ad_2]

LEAVE A REPLY

Please enter your comment!
Please enter your name here