2

I can't figure out how to properly send POST parameters.

My Swift 3:

let parameters = ["name": "thom", "password": "12345"] as Dictionary<String, String>
let url = URL(string: "https://mywebsite.com/test.php")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
do
{
    request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
}
catch let error
{
    print(error.localizedDescription)
}
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request as URLRequest, completionHandler: 
{
    data, response, error in
    guard error == nil else
    {
        print(error as Any)
        return
    }           
    guard let data = data else
    {
        return
    }
    do 
    {
        if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] 
        {
            print(json)
            print(json["post"]!)
        }
        else
        {
            print("no json")
        }
    }
    catch let error
    {
        print(error.localizedDescription)
    }
})
task.resume()

My PHP:

<?php
header('Content-Type: application/json');
if(empty($_POST)) echo json_encode(array('post'=>'empty'));
else echo json_encode($_POST+array('post'=>'not_empty'));
exit;

If I set the content-type header (in Swift) to application/json I get:

["post": empty]
empty

If I set it to application/x-www-form-urlencoded I get:

["{\"name\":\"thom\",\"password\":\"12345\"}": , "post": not_empty]
not_empty

How do I send the dictionary to my server as $_POST key/value pairs, not as a json_encoded string?

Works for a Living
  • 1,262
  • 2
  • 19
  • 44
  • This might be interesting for you https://stackoverflow.com/questions/40433229/php-receives-empty-post-variables-from-ajax-call/40433254#40433254 – Charlotte Dunois Dec 11 '16 at 22:44
  • It's interesting, but not sure how it helps. I need the parameter dictionary to be passed to my $_POST variable as key/value pairs. If I don't json_encode my parameters on the client side, I get an error saying I can't pass type AnyObject to type Data? – Works for a Living Dec 11 '16 at 22:48
  • http://stackoverflow.com/questions/26364914/http-request-in-swift-with-post-method – Charlotte Dunois Dec 11 '16 at 22:50
  • I've seen that thread. That's where I got my swift code (from the answer with zero votes). I don't want to pass a query string to the server. I want to pass the dictionary. – Works for a Living Dec 11 '16 at 22:51
  • You need to build a http query string from the dictionary. You cannot pass a dictionary. JSON would also work, but you need to parse the PHP input yourself. – Charlotte Dunois Dec 11 '16 at 22:54
  • I'm not finding a straightforward way to do that in Swift without looping and then trimming the final `&`, which there isn't a straightforward way I can find to do either. – Works for a Living Dec 11 '16 at 23:06
  • I got it done like this: var postString = ""; for(key,value) in parameters { postString += key+"="+value+"&" } postString = postString.substring(to: postString.index(before: postString.endIndex)) print(postString) request.httpBody = postString.data(using: .utf8) – Works for a Living Dec 11 '16 at 23:10
  • Shouldn't there be a better way? – Works for a Living Dec 11 '16 at 23:10
  • 1
    Once you have your array of `key=value` strings, then use `joined(separator: "&")` to combine them together. – Rob Dec 11 '16 at 23:32

1 Answers1

5

You want to percent-escape the request into a x-www-form-urlencoded request, like so:

let parameters = ["name": "thom", "password": "12345"]
let url = URL(string: "https://mywebsite.com/test.php")!

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.updateHttpBody(with: parameters)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

let task = session.dataTask(with: request) { data, response, error in
    guard let data = data, error == nil else {
        print("\(error)")
        return
    }

    // handle response here
}
task.resume()

Where

extension URLRequest {

    /// Populate the `httpBody` of `application/x-www-form-urlencoded` request.
    ///
    /// - parameter parameters:   A dictionary of keys and values to be added to the request

    mutating func updateHttpBody(with parameters: [String : String]) {
        let parameterArray = parameters.map { (key, value) -> String in
            return "\(key.addingPercentEncodingForQueryValue()!)=\(value.addingPercentEncodingForQueryValue()!)"
        }
        httpBody = parameterArray.joined(separator: "&").data(using: .utf8)
    }
}

extension String {

    /// Percent escape value to be added to a HTTP request
    ///
    /// This percent-escapes all characters besides the alphanumeric character set and "-", ".", "_", and "*".
    /// This will also replace spaces with the "+" character as outlined in the application/x-www-form-urlencoded spec:
    ///
    /// http://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm
    ///
    /// - returns: Return percent escaped string.

    func addingPercentEncodingForQueryValue() -> String? {
        let generalDelimitersToEncode = ":#[]@?/"
        let subDelimitersToEncode = "!$&'()*+,;="

        var allowed = CharacterSet.urlQueryAllowed
        allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")

        return addingPercentEncoding(withAllowedCharacters: allowed)?.replacingOccurrences(of: " ", with: "+")
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044