# 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 offer 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 both as `URL` or `String` (conversion happens automatically): ```swift let todo = try await HTTPRequest("https://jsonplaceholder.typicode.com/todos/1") .fetch(Todo.self) ``` The following code build a `GET` `HTTPRequest` and execute it into the `shared` `HTTPClient` instance. The result is therefore converted to `Codable` conform's `Todo` object. All asynchronously, all in a one line of code. However not all requests are so simple; you may need to configure parameters, headers and the body, along with some other settings like timeout or retry/cache strategies. We'll look at this below. ## Initialize a Request You have three different convenience ways to create a new request depending how many settings you would to change. ### Standard You can use this method when your configuration is pretty simple, just the HTTP method and the absolute URL. This example create a post to add a new todo in [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 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 encapsulate and make 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 behaviour and settings of your request directly inside the callback as showed above. ## Setup Query Parameters You can add URL query parameters in different ways: - `req.add(parameters: [String: Any])` allows you to append a dictionary of String/Any objects to your query. It allows to also specify how to encode values which are arrays (by default `.withBrackets`) and boolean (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 `URLQueryItems` 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 (also emoji are supported!). ## Setup Headers Request's headers can be set using the `req.header = ` property which require an `HTTPHeader` object. This object is just an type-safe interface to set headers; you can use one of the presets keys or add your own just by passing one of the valid enum values or 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 duplicate keys of the client). ## Setup Request Body The body must be conform to `HTTPBody` protocol. RealHTTP provides several built-in types 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 in URL query parameter forms (`application/x-www-form-urlencoded;`) you can use `.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` as body calll the `.data(_ content: Data, contentType mimeType: MIMEType)`. It allows you to also specify the content-type to set from a presets list of `MIMEType` objects. It allows support stream (`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)` allows you to encode a plain string as body along with he specified content-type (default is `text/plain`). ```swift req.body = .string("😃😃😃", contentType: .html) ``` ### JSON Data RealHTTP has fully native support for JSON data. You can use any `Encodable` conform object or any object which can be transformed using the built-in `JSONSerialization`: - `.json(_ object: T, encoder: JSONEncoder` to serialize a `Encodable` object as body of the request encoded with codable. - `.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 support 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 request must be passed to a clien. The class `HTTPClient` represent 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`), to send a common set of headers for each request executed. It also managed received/sent cookie. 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 accordlying to the available resources). HTTPClient also contains `validators`: validators are chainable code which is used to validate the response of a request and decide an optional retry strategy, a return with error or accept the server data. You can use this object to create your common web service validation logic instead of duplicate 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 shared client so your request must contains the absolute url (via `url` parameter) in order to be executed correctly. When you call `fetch()` function 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 Sometimes you need to take more control about your client or you just need to isolate specific application logic. For example we're using different clients based upon our app is communicating with B2B or B2C web services. This allows us to have a fine grained control over our settings (cookies, session management, concurrent operations and more). The following example create 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: "...") // Encodable conform 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 you seen above, executing an asynchronous request is easy as calling its `fetch()` method. This is an `async` `throwable` method so you need to call it in an async scope. This is just an example which 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 stuff are not related to the http library so we'll suggest looking at some @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 Sometimes you may want to intercept the moment where the destination client produce an `URLRequest` instance from an `HTTPRequest` in order to alter some values. RealHTTP offer `urlRequestModifier` method to intercept and modify the request. In this example we remove some headers and disable the execution on 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 happens because you don't need of that resource anymore or due to some constraints in your app lifecycle. In all of these cases use `cancel()` function 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 coming from 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 here you can found the details - `headers`: received headers (`HTTPHeaders`) Usually you don't want to handle with the raw response, but you may want to transform these responses in real object. `HTTPResponse` provide several `decode()` functions you can use to transform raw data to something useful: ### Decode using Codable & Custom Decoding The `decode()` allows you to transform the response to an object conform to this protocol. `HTTPDecodableResponse` is automatically implemented by `Decodable` so any object conform to `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 this protocol 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) } } ``` Both if you are using `Codable` or custom `HTTPDecodableResponse` conform objects you just need to call `decode()` to allows conversion: ```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 need to just call `decode()` by passing your object and optional options parameter: ```swift let req = try HTTPRequest(...) let result = try await req.fetch(newClient).decodeJSONData([String: Any].self, options: .fragmentsAllowed) ```