HTTP Request in Swift with POST method
Asked Answered
C

7

236

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.

Cirone answered 14/10, 2014 at 15:47 Comment(1)
S
480

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.

Stamford answered 14/10, 2014 at 15:59 Comment(17)
FYI, if you want to do real requests (including percent escaping, creating complex requests, simplify the parsing of the responses), consider using AlamoFire, from the author of AFNetworking. But if you just want to do a trivial POST request, you can use the above.Stamford
Thanks Rob, That was just what I was looking for! Nothing more than a simple POST. Great answer!Cirone
After a few hours of looking for several different solutions, lines 3 and 4 is saving my life as I could not for the life of me get NSJSONSerialization.dataWithJSONObject to work!Quartet
for PHP to see $_POST make sure to use file name in url, e.g. postName.php.Duwalt
@Duwalt - Rather than drawing connections between $_POST and filenames, I'd reduce this to something simpler: The PHP script won't run at all if you don't get the URL correct. But it's not always the case that you must include the filename (e.g. the server may be doing URL routing or have default filenames). In this case, the OP gave us a URL that included a filename, so I simply used the same URL as he did.Stamford
Can you help if I want to tell server that 13&name=Jack is one value for key idJamisonjammal
You percent encode the value, like shown here: https://mcmap.net/q/119536/-http-post-parameters-are-passed-json-encoded-to-_post.Stamford
It doesn't work for me. See #48715502Luciennelucier
Alamofire is a pain in the ass for synchronous requests.Pond
Alamofire is no better and no worse than URLSession in this regard. All networking APIs are inherently asynchronous, as well they should be. Now, if you're looking for other graceful ways of dealing with asynchronous requests, you can consider wrapping them (either URLSession requests or Alamofire ones) in asynchronous, custom Operation subclass. Or you can use some promises library, like PromiseKit.Stamford
Rather than force unwrapping your url, use guard let url = URL(string: "www.myurl.com/whatever") else {return}Paw
@DeepBlue - I understand what you’re saying, but I respectfully disagree. To silently fail if there is a problem is a very bad idea. Perhaps you could do guard let url = ... else { fatalError("Invalid URL") }, but that’s syntactic noise with little benefit. You’re going down the road of writing a lot of error handling code for something that is not a end-user runtime problem, but rather a programming problem mistake. The analogy is implicitly unwrapped @IBOutlet references. Do you write tons of guard let label = ... code for all of your outlets? No. That would be silly. Same here.Stamford
Don’t get me wrong. If there are things that aren’t immediately obvious or could fail for reasons outside of the programmer’s control (like parsing the JSON response and/or handing network errors), then using forced unwrapping operator is a huge mistake. Definitely safely unwrap those. But for something like an @IBOutlet or this URL example, then it’s counterproductive to add that syntactic noise, IMHO. And to do a guard with an else clause that just does return, hiding any underlying issues, is a really bad idea.Stamford
@Stamford what if I upload an image to the backend as well with this? I'm unable to post an image with data, I'm getting status code --> 400Embrangle
@Embrangle - When sending binary payload, such as an image, we will often use a multipart/form-data request rather than the above application/x-www-form-urlencoded request. See https://mcmap.net/q/119538/-upload-image-with-parameters-in-swift for an example. (Technically you could use the pattern here but base-64 encode the data client-side, manually base-64 decode server-side, and making the payload 33% larger/slower in the process. For reasons that are probably obvious, that's not a great pattern, hence why we often prefer multipart/form-data requests.)Stamford
The more I learn about Swift, I really began to wonder why no one questions the complexity of Alamofire.Metaprotein
For trivial examples, URLSession is fine. But as networking code get more complicated (and it gets complicated quickly), Alamofire is a life-saver.Stamford
O
116

Swift 4 and above

func postRequest() {
  
  // declare the parameter as a dictionary that contains string as key and value combination. considering inputs are valid
  
  let parameters: [String: Any] = ["id": 13, "name": "jack"]
  
  // create the url with URL
  let url = URL(string: "www.thisismylink.com/postName.php")! // change server url accordingly
  
  // create the session object
  let session = URLSession.shared
  
  // now create the URLRequest object using the url object
  var request = URLRequest(url: url)
  request.httpMethod = "POST" //set http method as POST
  
  // add headers for the request
  request.addValue("application/json", forHTTPHeaderField: "Content-Type") // change as per server requirements
  request.addValue("application/json", forHTTPHeaderField: "Accept")
  
  do {
    // convert parameters to Data and assign dictionary to httpBody of request
    request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
  } catch let error {
    print(error.localizedDescription)
    return
  }
  
  // create dataTask using the session object to send data to the server
  let task = session.dataTask(with: request) { data, response, error in
    
    if let error = error {
      print("Post Request Error: \(error.localizedDescription)")
      return
    }
    
    // ensure there is valid response code returned from this HTTP response
    guard let httpResponse = response as? HTTPURLResponse,
          (200...299).contains(httpResponse.statusCode)
    else {
      print("Invalid Response received from the server")
      return
    }
    
    // ensure there is data returned
    guard let responseData = data else {
      print("nil Data received from the server")
      return
    }
    
    do {
      // create json object from data or use JSONDecoder to convert to Model stuct
      if let jsonResponse = try JSONSerialization.jsonObject(with: responseData, options: .mutableContainers) as? [String: Any] {
        print(jsonResponse)
        // handle json response
      } else {
        print("data maybe corrupted or in wrong format")
        throw URLError(.badServerResponse)
      }
    } catch let error {
      print(error.localizedDescription)
    }
  }
  // perform the task
  task.resume()
}
Oink answered 11/12, 2016 at 4:2 Comment(7)
I get the following error with your code "The data couldn’t be read because it isn’t in the correct format."Heer
I think you are getting response in String format can you verify?Oink
i think the problem here in this solution is that you pass the parameter as json serializing and the web service is takeing as formdata parametersDiphenylhydantoin
yes in solution the params are json, please check with the server if it requires form data then change the content type e.g. request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")Oink
for multipart params use let boundaryConstant = "--V2ymHFg03ehbqgZCaKO6jy--"; request.addvalue("multipart/form-data boundary=(boundaryConstant)", forHTTPHeaderField: "Content-Type")Oink
This should be the correct answer as who wants to form their data in a string like the checked answer suggests... #oldSkoolIngaingaberg
Great! For nested dicts use https://mcmap.net/q/119539/-nested-dictionaries-converted-to-json-swiftDewyeyed
L
52

For anyone looking for a clean way to encode a POST request in Swift 5.

You don’t need to deal with manually adding percent encoding. Use URLComponents to create a GET request URL. Then use query property of that URL to get properly percent escaped query string.

let url = URL(string: "https://example.com")!
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)!

components.queryItems = [
    URLQueryItem(name: "key1", value: "NeedToEscape=And&"),
    URLQueryItem(name: "key2", value: "vålüé")
]

let query = components.url!.query

The query will be a properly escaped string:

key1=NeedToEscape%3DAnd%26&key2=v%C3%A5l%C3%BC%C3%A9

Now you can create a request and use the query as HTTPBody:

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = Data(query.utf8)

Now you can send the request.

Lannielanning answered 12/10, 2019 at 17:45 Comment(6)
After various examples, only this works for Swift 5.Lectionary
I mansioned GET request but i wonder how about the POST request ? How to pass parameters into the httpBody or do i need it ?Aristides
Smart solution! Thanks for sharing @pointum. I'm sure Martalp doesn't need the answer anymore, but for anybody else reading, the above does a POST request.Anacrusis
By the way, if you use this technique, please note that it will not correctly percent escape + characters. See https://mcmap.net/q/119540/-swift-get-request-with-parameters.Stamford
works perfectly, at last I just added URLSession.shared.dataTask(with: request) { data, HTTPURLResponse, Error in if (data != nil && data?.count != 0) { let response = String(data: data!, encoding: .utf8) print(response!) } }.resume()Colis
For Swift 5, this works perfectly, Thanks man.Lousy
M
13

Heres the method I used in my logging library: https://github.com/goktugyil/QorumLogs

This method fills html forms inside Google Forms.

    var url = NSURL(string: urlstring)

    var request = NSMutableURLRequest(URL: url!)
    request.HTTPMethod = "POST"
    request.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
    request.HTTPBody = postData.dataUsingEncoding(NSUTF8StringEncoding)
    var connection = NSURLConnection(request: request, delegate: nil, startImmediately: true)
Makeshift answered 17/9, 2015 at 13:15 Comment(2)
what's application/x-www-form-urlencoded What are you setting?Mestee
For passing data in the request body @HoneyNeusatz
S
9
let session = URLSession.shared
        let url = "http://...."
        let request = NSMutableURLRequest(url: NSURL(string: url)! as URL)
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        var params :[String: Any]?
        params = ["Some_ID" : "111", "REQUEST" : "SOME_API_NAME"]
        do{
            request.httpBody = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions())
            let task = session.dataTask(with: request as URLRequest as URLRequest, completionHandler: {(data, response, error) in
                if let response = response {
                    let nsHTTPResponse = response as! HTTPURLResponse
                    let statusCode = nsHTTPResponse.statusCode
                    print ("status code = \(statusCode)")
                }
                if let error = error {
                    print ("\(error)")
                }
                if let data = data {
                    do{
                        let jsonResponse = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions())
                        print ("data = \(jsonResponse)")
                    }catch _ {
                        print ("OOps not good JSON formatted response")
                    }
                }
            })
            task.resume()
        }catch _ {
            print ("Oops something happened buddy")
        }
Siege answered 22/4, 2018 at 13:22 Comment(0)
F
7

All the answers here use JSON objects. This gave us problems with the $this->input->post() methods of our Codeigniter controllers. The CI_Controller cannot read JSON directly. We used this method to do it WITHOUT JSON

func postRequest() {
    // Create url object
    guard let url = URL(string: yourURL) else {return}

    // Create the session object
    let session = URLSession.shared

    // Create the URLRequest object using the url object
    var request = URLRequest(url: url)

    // Set the request method. Important Do not set any other headers, like Content-Type
    request.httpMethod = "POST" //set http method as POST

    // Set parameters here. Replace with your own.
    let postData = "param1_id=param1_value&param2_id=param2_value".data(using: .utf8)
    request.httpBody = postData

    // Create a task using the session object, to run and return completion handler
    let webTask = session.dataTask(with: request, completionHandler: {data, response, error in
    guard error == nil else {
        print(error?.localizedDescription ?? "Response Error")
        return
    }
    guard let serverData = data else {
        print("server data error")
        return
    }
    do {
        if let requestJson = try JSONSerialization.jsonObject(with: serverData, options: .mutableContainers) as? [String: Any]{
            print("Response: \(requestJson)")
        }
    } catch let responseError {
        print("Serialisation in error in creating response body: \(responseError.localizedDescription)")
        let message = String(bytes: serverData, encoding: .ascii)
        print(message as Any)
    }

    // Run the task
    webTask.resume()
}

Now your CI_Controller will be able to get param1 and param2 using $this->input->post('param1') and $this->input->post('param2')

Fraunhofer answered 9/7, 2019 at 11:50 Comment(0)
C
3
@IBAction func btn_LogIn(sender: AnyObject) {

    let request = NSMutableURLRequest(URL: NSURL(string: "http://demo.hackerkernel.com/ios_api/login.php")!)
    request.HTTPMethod = "POST"
    let postString = "email: [email protected] & password: testtest"
    request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
    let task = NSURLSession.sharedSession().dataTaskWithRequest(request){data, response, error in
        guard error == nil && data != nil else{
            print("error")
            return
        }
        if let httpStatus = response as? NSHTTPURLResponse where httpStatus.statusCode != 200{
            print("statusCode should be 200, but is \(httpStatus.statusCode)")
            print("response = \(response)")
        }
        let responseString = String(data: data!, encoding: NSUTF8StringEncoding)
        print("responseString = \(responseString)")
    }
    task.resume()
}
Charlatan answered 11/4, 2017 at 12:54 Comment(1)
This one might need updating for Swift 3/4 to use URLRequestArvo

© 2022 - 2024 — McMap. All rights reserved.