# Build & Execute a Request - [Build & Execute a Request](#build--execute-a-request) - [Initialize a Request](#initialize-a-request) - [Standard](#standard) - [URI Template](#uri-template) - [Builder Pattern](#builder-pattern) - [Setup Query Parameters](#setup-query-parameters) - [Setup Headers](#setup-headers) - [Setup Request Body](#setup-request-body) - [URL Query Parameters](#url-query-parameters) - [Raw Data & Stream](#raw-data--stream) - [Plain Strings](#plain-strings) - [JSON Data](#json-data) - [Multipart-Form-Data](#multipart-form-data) - [The HTTP Client](#the-http-client) - [Shared Client](#shared-client) - [Custom Client](#custom-client) - [Execute a Request](#execute-a-request) - [Modify a Request](#modify-a-request) - [Cancel a Request](#cancel-a-request) - [The HTTP Response](#the-http-response) - [Decode using Codable & Custom Decoding](#decode-using-codable--custom-decoding) - [Decode Raw JSON using JSONSerialization](#decode-raw-json-using-jsonserialization) RealHTTP offers a type-safe, perfectly Swift integrated way to build and configure a new http request. At the simplest, you just need to provide a valid url, either as a `URL` or a `String` (conversion happens automatically): ```swift let todo = try await HTTPRequest("https://jsonplaceholder.typicode.com/todos/1") .fetch(Todo.self) ``` The preceding code builds a `GET` `HTTPRequest` and executes it into the `shared` `HTTPClient` instance. The result is then converted into a `Todo` object via the `Decodable` protocol. All asynchronously, all in one line of code. However not all requests are so simple; you may need to configure parameters, headers, and the body, or even some other settings like timeout or retry/cache strategies. We'll look at this below. ## Initialize a Request You have three different convenient ways to create a new request depending how many settings you want to change. ### Standard You can use this method when your configuration is pretty simple, just the HTTP method and the absolute URL. This example creates a post to add a new todo to the [jsonplaceholder](https://jsonplaceholder.typicode.com) site using automatic json conversion *(you will learn more about body encoding below)*. ```swift let req = try HTTPRequest(method: .post, "https://jsonplaceholder.typicode.com/posts", body: try .json(["title": "foo", "body": "bar", "userId": 1])) let _ = try await req.fetch() ``` ### URI Template RealHTTP also allows you to create a request via URI Template, as specified by [RFC6570](https://tools.ietf.org/html/rfc6570) using the [Kylef](https://github.com/kylef/URITemplate.swift) Swift implementation. A URI Template is a compact sequence of characters for describing a range of Uniform Resource Identifiers through variable expansion. ```swift let req = try HTTPRequest(URI: "https://jsonplaceholder.typicode.com/posts/{postId}", variables: ["postId": 1]) let _ = try await req.fetch() ``` ### Builder Pattern The most complete way to configure a request is by using the builder pattern initialization. It allows you to specify any property of the `HTTPRequest` inside a callback function which encapsulates and makes clear the init process. ```swift let req = HTTPRequest { // Setup default params $0.url = URL(string: "https://.../login")! $0.method = .get $0.timeout = 100 // Setup some additional settings $0.redirectMode = redirect $0.maxRetries = 4 $0.allowsCellularAccess = false // Setup URL query params & body $0.addQueryParameter(name: "full", value: "1") $0.addQueryParameter(name: "autosignout", value: "30") $0.body = .json(["username": username, "pwd": pwd]) } let _ = try await req.fetch() ``` You can configure the behavior and settings of your request directly inside the callback, as shown above. ## Setup Query Parameters You can add URL query parameters in several different ways: - `req.add(parameters: [String: Any])` allows you to append a dictionary of String/Any objects to your query. It also allows you to specify how to encode array values (by default `.withBrackets`) and boolean values (by default `.asNumbers`). - `req.add(parameters: [String: String])` if your dictionary is just a map of String, String. Or you can pass directly the `URLQueryItem` instances via: - `req.add(queryItems: [URLQueryItem]) ` - `req.add(queryItem: URLQueryItem)` - `req.addQueryParameter(name: String, value: String)`: in this case the `URLQueryItem` is created from passed parameters. For example: ```swift let req = HTTPRequest { $0.url = URL(string: "https://.../login")! $0.add(parameters: ["username": "Michael Bublé", "pwd": "abc", "autosignout": true]) $0.addQueryParameter(name: "full", value: "1") ``` will produce the following url: `https://.../login?username=Michael+Bubl%C3%A9&pwd=abc&autosignout:1&full1`. As you can see, values are encoded automatically, including percent escape and utf8 characters (emoji are supported!). ## Setup Headers Request's headers can be set using the `req.header = ` property which requires an `HTTPHeader` object. This object is just a type-safe interface to set headers; you can use one of the preset keys by passing one of the valid enum values, or add your own by passing a plain string: ```swift let req = HTTPRequest(...) req.headers = HTTPHeaders([ .init(name: "X-API-Key", value: "abc"), // custom key .init(name: .userAgent, value: "MyCoolApp"), // preset key .init(name: .cacheControl, value: HTTPCacheControl.noTransform.headerValue) ]) ``` Values are eventually combined with the destination `HTTPClient`'s `headers` to produce a final list of headers to send (the request's headers takes the precedence and may override default headers from the client). ## Setup Request Body The body must conform to the `HTTPBody` protocol. RealHTTP provides several built-in types that conform to this protocol in order to simplify your setup. Specifically, when you call `req.body = ...` you can use one of the following options. ### URL Query Parameters To set the body of a request for URL query parameter forms (`application/x-www-form-urlencoded;`) you can use the `.formURLEncodedBody(_ parameters: [String: Any])` method: ```swift let req = HTTPRequest(...) req.body = .formURLEncodedBody(["username": "Michael Bublé", "pwd": "abc"]) // Will produce a body with this string: pwd=abc&username=Michael%20Bubl%C3%A9 // and content type headers `application/x-www-form-urlencoded;` ``` ### Raw Data & Stream To set a raw `Data` object as the body, call the `.data(_ content: Data, contentType mimeType: MIMEType)` method. It allows you to specify the content-type from a preset list of `MIMEType` objects. It supports streams (`NSInputStream`) both from `Data` or file `URL`: ```swift // Different set of raw data req.body = .data(someData, contentType: .gzip) // some gizip raw data req.body = .data(.data(someData), contentType: .otf) // otf font raw data req.body = .data(.fileURL(localFileURL), contentType: .zip) // if you have big data you can transfer it via stream ``` ### Plain Strings The `.string(_ content: String, contentType: MIMEType)` encodes a plain string as the body, along with the specified content-type (default is `text/plain`). ```swift req.body = .string("😃😃😃", contentType: .html) ``` ### JSON Data RealHTTP natively supports JSON data. You can use any `Encodable` conforming object or any object which can be transformed with the built-in `JSONSerialization`: - `.json(_ object: T, encoder: JSONEncoder` to serialize an `Encodable` object as the body of the request. - `.json(_ object: Any, options: JSONSerialization.WritingOptions = []) ` uses the `JSONSerialization` class ```swift public struct UserCredentials: Codable { var username: String var pwd: String } let credentials = UserCredentials(username: "", pwd: "abc") let req = HTTPRequest(...) req.body = try .json(credentials) ``` It will produce a body with the following JSON: ```json {"pwd":"abc","username":"Michael Bublé"} ``` ### Multipart-Form-Data RealHTTP also supports Multipart Form Data construction with an easy-to-use form builder which supports: Key/Value entries, Local URL files, and Streams! ```swift let req = HTTPRequest(method: .post, URL: ...) req.body = try .multipart(boundary: nil, { form in // Key/Value support try form.add(string: "320x240", name: "size") try form.add(string: "Michael Bublé", name: "author") // Local file URL support try form.add(fileURL: credentialsFileURL, name: "credentials") // Data stream support try form.add(fileStream: localFileURL, headers: .init()) }) ``` ## The HTTP Client Once you have configured a request you're ready to execute it. In order to be executed, a request must be passed to a client. The class `HTTPClient` represents a container of common configuration settings which can manage a session. For example, a client can be configured to use a base URL for each request (you will not set the `url` inside the request configuration, just the `path`), in order to send a common set of headers for each request executed. It also manages received/sent cookies. Under the hood, the client is a queue, so you can also set the maximum number of concurrent connections (if not specified, the OS will do it according to the available resources). HTTPClient also contains `validators`: validators are chainable pieces of code which are used to validate the response of a request and decide whether to use an optional retry strategy, return with an error, or accept the server data. You can use this object to create your common web service validation logic instead of duplicating your code (see the section ["Advanced HTTPClient"](3.Advanced_HTTPClient.md) for more info). ### Shared Client `HTTPClient.shared` is the shared client. No `baseURL` is set for the shared client, so your request must contain the absolute url (via the `url` parameter) in order to be executed correctly. When you call the `fetch()` method without passing a client, the shared client is used. ```swift // Full URL is required to execute request in shared client let req = try HTTPRequest(method: .post, "https://jsonplaceholder.typicode.com/posts") let _ = try await req.fetch() // if not specified, HTTPClient.shared is used ``` ### Custom Client At times, you may need to take more control over your client or isolate specific application logic. For example, we use different clients for communicating with B2B vs B2C web services. This allows us to have fine-grained control over our settings (cookies, session management, concurrent operations and more). The following example creates a new client with some settings: ```swift public lazy var b2cClient: HTTPClient = { var config = URLSessionConfiguration.default config.httpShouldSetCookies = true config.networkServiceType = .responsiveData let client = HTTPClient(baseURL: "https://myappb2c.ws.org/api/v2/",configuration: config) // Setup some common HTTP Headers for all requests client.headers = HTTPHeaders([ .init(name: .userAgent, value: myAgent), .init(name: "X-API-Experimental", value: "true") ]) return client }() ``` Now we can use it to perform a new request: ```swift let loginCredentials = UserCred(username: "..." pwd: "...") // conforms to Encodable let req = HTTPRequest { $0.path = "login" // full url will be b2cClient.baseURL + path $0.method = .post $0.addQueryParameter(name: "autosignout", value: "30") $0.body = .json(loginCredentials) // automatic conversion to json in body } // URL is: https://myappb2c.ws.org/api/v2/login?autosignout=30 // Execute async request and decode the response to LoggedUser object (Codable). let user = req.fetch(b2cClient).decode(LoggedUser.self) ``` ## Execute a Request As shown above, executing an asynchronous request is as easy as calling its `fetch()` method. This is an `async` `throwable` method, so you need to call it in an async scope. The following is an example that uses the `Task` and `@MainActor` to execute an async request and update the UI on main thread: ```swift let task = detach { do { let user = req.fetch(b2cClient).decode(LoggedUser.self) self.updateUserProfile(.success(user)) } catch { self.updateUserProfile(.failure(error)) } } @MainActor private func updateUserProfile(_ data: Result) { // executed on main thread } ``` These topics are not related to the http library, so if you would like more information, check out some of the @MainActor docs ([here](https://www.swiftbysundell.com/articles/the-main-actor-attribute/), [here](https://developer.apple.com/documentation/swift/mainactor) or [here](https://www.avanderlee.com/swift/mainactor-dispatch-main-thread/)). ## Modify a Request You may want to intercept the moment where the destination client produces a `URLRequest` instance from an `HTTPRequest` in order to alter some values. RealHTTP offers the `urlRequestModifier` method to intercept and modify the request. In this example we remove some headers and disable the execution when on a cellular network: ```swift let req = HTTPRequest(...) req.urlRequestModifier = { request in request.allowsCellularAccess = false request.headers.remove(name: .cacheControl) request.headers.remove(name: "X-API-Key") } ``` ## Cancel a Request As any other async operation you can force the library to cancel a running request. This may be due to not needing that resource anymore or due to some other constraints in your app lifecycle. In all of these cases, use the `cancel()` method to stop the request and ignore the response. ```swift let req = HTTPRequest(...) let res = try await req.fetch() // Somewhere in your code from another thread res.cancel() ``` ## The HTTP Response Once `fetch()` is done you will get an `HTTPResponse` object which contains the raw response from the server. This object contains some interesting properties: - `data`: the raw body received (as `Data`) - `metrics`: collected URL metrics during the request (`HTTPMetrics`) - `httpResponse`: the `HTTPURLResponse` received - `statusCode`: the HTTP Status Code received - `error`: if an error has occured, you can find the details here - `headers`: received headers (`HTTPHeaders`) You don't usually want to handle the raw response, but would rather transform it into a typed object. `HTTPResponse` provides several `decode()` methods you can use to transform raw data into something useful: ### Decode using Codable & Custom Decoding The `decode()` method allows you to transform the response into an object that conforms to `HTTPDecodableResponse`. `HTTPDecodableResponse` is automatically implemented by `Decodable` so any object that conforms to the `Decodable` or `Codable` protocol can be transformed automatically. Moreover, if you need to perform a custom decoding (ie using [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON) or other libraries), you can conform your object to `HTTPDecodableResponse` and implement the only required method: ```swift import SwiftyJSON public struct MyUser: HTTPDecodableResponse { var name: String var age: Int // Implement your own logic to decode a custom object. // You can return `nil`, your instance, or throw an error if needed. public static func decode(_ response: HTTPResponse) throws -> RequestsTests.MyUser? { let json = JSON(data: response.data) guard json["isValid"].boolValue else { throw Error("Invalid object") } return MyUser(name: json["fullName"].stringValue, age: json["age"].intValue) } } ``` Whether you are using a type that conforms to `Codable` or to `HTTPDecodableResponse`, you just need to call `decode()`: ```swift let user: MyUser? = try await loginUser(user: "mark", pwd: "...").fetch().decode(MyUser.self) ``` Et voilà! ### Decode Raw JSON using JSONSerialization To transform a raw response to a JSON object using `JSONSerialization` class you just need to call `decode()` by passing your object and, optionally, an options parameter: ```swift let req = try HTTPRequest(...) let result = try await req.fetch(newClient).decodeJSONData([String: Any].self, options: .fragmentsAllowed) ```