Search code examples
postswiftparametershttp-posthttprequest

HTTP Request in Swift with POST method


I'm trying to run a HTTP Request in Swift, to POST 2 parameters to a URL.

Example:

Link: www.thisismylink.com/postName.php

Params:

id = 13
name = Jack

What is the simplest way to do that?

I don't even want to read the response. I just want to send that to perform changes on my database through a PHP file.


Solution

  • The key is that you want to:

    • set the httpMethod to POST;
    • optionally, set the Content-Type header, to specify how the request body was encoded, in case server might accept different types of requests;
    • optionally, set the Accept header, to request how the response body should be encoded, in case the server might generate different types of responses; and
    • set the httpBody to be properly encoded for the specific Content-Type; e.g. if application/x-www-form-urlencoded request, we need to percent-encode the body of the request.

    E.g., in Swift 3 and later you can:

    let url = URL(string: "https://httpbin.org/post")!
    var request = URLRequest(url: url)
    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    request.setValue("application/json", forHTTPHeaderField: "Accept")
    request.httpMethod = "POST"
    let parameters: [String: Any] = [
        "id": 13,
        "name": "Jack & Jill"
    ]
    request.httpBody = parameters.percentEncoded()
    
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard 
            let data = data, 
            let response = response as? HTTPURLResponse, 
            error == nil 
        else {                                                               // check for fundamental networking error
            print("error", error ?? URLError(.badServerResponse))
            return
        }
        
        guard (200 ... 299) ~= response.statusCode else {                    // check for http errors
            print("statusCode should be 2xx, but is \(response.statusCode)")
            print("response = \(response)")
            return
        }
        
        // do whatever you want with the `data`, e.g.:
        
        do {
            let responseObject = try JSONDecoder().decode(ResponseObject<Foo>.self, from: data)
            print(responseObject)
        } catch {
            print(error) // parsing error
            
            if let responseString = String(data: data, encoding: .utf8) {
                print("responseString = \(responseString)")
            } else {
                print("unable to parse response as string")
            }
        }
    }
    
    task.resume()
    
    

    Where the following extensions facilitate the percent-encoding request body, converting a Swift Dictionary to a application/x-www-form-urlencoded formatted Data:

    extension Dictionary {
        func percentEncoded() -> Data? {
            map { key, value in
                let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
                let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
                return escapedKey + "=" + escapedValue
            }
            .joined(separator: "&")
            .data(using: .utf8)
        }
    }
    
    extension CharacterSet { 
        static let urlQueryValueAllowed: CharacterSet = {
            let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
            let subDelimitersToEncode = "!$&'()*+,;="
            
            var allowed: CharacterSet = .urlQueryAllowed
            allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
            return allowed
        }()
    }
    

    And the following Decodable model objects facilitate the parsing of the application/json response using JSONDecoder:

    // sample Decodable objects for https://httpbin.org
    
    struct ResponseObject<T: Decodable>: Decodable {
        let form: T    // often the top level key is `data`, but in the case of https://httpbin.org, it echos the submission under the key `form`
    }
    
    struct Foo: Decodable {
        let id: String
        let name: String
    }
    

    This checks for both fundamental networking errors as well as high-level HTTP errors. This also properly percent escapes the parameters of the query.

    Note, I used a name of Jack & Jill, to illustrate the proper x-www-form-urlencoded result of name=Jack%20%26%20Jill, which is “percent encoded” (i.e. the space is replaced with %20 and the & in the value is replaced with %26).


    See previous revision of this answer for Swift 2 rendition.