Search code examples
swiftafnetworking-2nsurlsession

Replicate AFNetworking POST request with NSURLSession


POST request with AFNetworking:

let urlString = "http://example.com/file.php"
let dictionary = ["key1": [1,2,3], "key2": [2,4,6]]

var error: NSError?
let data = NSJSONSerialization.dataWithJSONObject(dictionary, options: NSJSONWritingOptions.allZeros, error: &error)
let jsonString = NSString(data: data!, encoding: NSUTF8StringEncoding)
let parameters = ["data" :  jsonString!]

let manager = AFHTTPSessionManager()
manager.responseSerializer = AFHTTPResponseSerializer()
manager.POST(urlString, parameters: parameters, success:
    {
        requestOperation, response in

        let result = NSString(data: response as! NSData, encoding: NSUTF8StringEncoding)!

        println(result)
    },
    failure:
    {
        requestOperation, error in
})

POST request with NSURLSession:

let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
request.HTTPMethod = "POST"

let bodyData = NSJSONSerialization.dataWithJSONObject(parameters, options: NSJSONWritingOptions.allZeros, error: &error)!
request.HTTPBody = bodyData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("\(bodyData.length)", forHTTPHeaderField: "Content-Length")

NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
    let result = NSString(data: data, encoding: NSUTF8StringEncoding)!

    println(result)
}).resume()

On server I have:

$data = json_decode($_POST["data"], true);
if (!$data) {
    echo "Error: Invalid POST data";
    return;
}
//do some stuff

echo "success";

On second case I get "Error: Invalid POST data". What I doing wrong?


Solution

  • It's because the AFNetworking example is not creating a JSON request whereas your NSURLSession example is. The AFNetworking example is creating a application/x-www-form-urlencoded request (where the value is a JSON string that you manually created). You can either change your server code to accept JSON requests or change the request to be a application/x-www-form-urlencoded request.


    If you look at the AFNetworking request body in something like Charles, you can see it generates something like:

    data=%7B%22key1%22%3A%5B1%2C2%2C3%5D%2C%22key3%22%3A%5B%22Harold%20%26%20Maude%22%5D%2C%22key2%22%3A%5B2%2C4%2C6%5D%7D
    

    If you un-percent-escape the value associated with data, that's effectively

    data={"key1":[1,2,3],"key3":["Harold & Maude"],"key2":[2,4,6]}
    

    (Note, I added the key3 to show that the percent escaping is escaping standard reserved characters, plus & and +, too.)

    If you want to do this yourself with NSURLSession, you'd have to build that and then percent escape it like so:

    let allowed = NSCharacterSet.alphanumericCharacterSet().mutableCopy() as! NSMutableCharacterSet
    allowed.addCharactersInString("-._~")
    let bodyString = "data=" + jsonString.stringByAddingPercentEncodingWithAllowedCharacters(allowed)!
    

    Frankly, this is pretty strange approach, embedding JSON within a application/x-www-form-urlencoded request. I'd just change the server to accept a standard JSON request (bypassing $_POST variables altogether):

    $handle = fopen("php://input", "rb");
    $raw_post_data = '';
    while (!feof($handle)) {
        $raw_post_data .= fread($handle, 8192);
    }
    fclose($handle);
    
    $body = json_decode($raw_post_data, true);
    

    By the way, once the server code accepts pure JSON request, the Swift 1.x client code would be:

    let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
    request.HTTPMethod = "POST"
    
    request.HTTPBody = NSJSONSerialization.dataWithJSONObject(parameters, options: NSJSONWritingOptions.allZeros, error: &error)!
    request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    
    NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
        let result = NSString(data: data, encoding: NSUTF8StringEncoding)!
    
        println(result)
    }).resume()
    

    AFNetworking equivalent would look like:

    let urlString = "http://example.com/file.php"
    let dictionary = ["key1": [1,2,3], "key2": [2,4,6]]
    
    let manager = AFHTTPSessionManager()
    manager.requestSerializer = AFJSONRequestSerializer()
    manager.responseSerializer = AFHTTPResponseSerializer()
    manager.POST(urlString, parameters: parameters, success:
        {
            requestOperation, response in
    
            let result = NSString(data: response as! NSData, encoding: NSUTF8StringEncoding)!
    
            println(result)
        },
        failure:
        {
            requestOperation, error in
    })