1

I created a simple POST Request using Postman and it succeeded. This is the screenshot of the body and response

body and response

This is the screenshot of the header in Postman

header

Now I am creating a POST request in iOS app using Swift. This is the code:

public func getResultItem(url: URL, _ completion:@escaping (ResultItem?, Error?) -> Void){
    let parameter = ["uname":"21120113120038", "pass":"123456"]

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type")
    guard let httpBody = try? JSONSerialization.data(withJSONObject: parameter, options: []) else {return}
    request.httpBody = httpBody

    URLSession.shared.dataTask(with: request) { (data, response, error) in
        var resultItem: ResultItem?

        guard let data = data else {
            completion(resultItem, error)
            return
        }

        do {
            resultItem = try JSONDecoder().decode(ResultItem.self, from: data)
            completion(resultItem, error)
        } catch let error as NSError{
            completion(resultItem, error)
        }
    }.resume()
}

The problem is, the "data" from dataTask completion handler is always 0 byte. Looks like my request is not valid so it is not returning the JSON. Since the data is 0 byte, the "do" block always fail when decoding the JSON, and turn to "catch" block. The error.description in this "catch" block here is -> 'The given data was not valid JSON'.

Which part of my code is wrong? My first assumption is there was a problem with the request. Maybe the parameter or the httpbody request is wrong, but I don't know how to fix it.

Thanks

Soroush
  • 541
  • 4
  • 17

1 Answers1

1

Your content type is application/x-www-form-urlencoded, but you're setting the body as JSON instead of URL-encoded parameters. The way your HTTP body looks with the way you have it now is something like:

{"uname":"21120113120038","pass":"123456"}

...but you want it to look like this:

uname=21120113120038&pass=123456

So, replace this line:

guard let httpBody = try? JSONSerialization.data(withJSONObject: parameter, options: []) else {return}

...with something like this:

guard let httpBody = parameter.map({
    [$0.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? "",
     $1.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? ""].joined(separator: "=")
}).joined(separator: "&").data(using: .utf8) else { return }

(However, I think that's a bit messy, so here are some extensions to clean it up somewhat:)

extension String {
    var urlEncodedQueryKeyOrValue: String {
        return addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? ""
    }
}

extension Dictionary where Key == String, Value == String {
    var wwwFormURLEncodedString: String {
        return map { "\($0.urlEncodedQueryKeyOrValue)=\($1.urlEncodedQueryKeyOrValue)" }.joined(separator: "&")
    }
}

And then you'd use it like this:

[...]
guard let httpBody = parameter.wwwFormURLEncodedString.data(using: .utf8) else { return }
request.httpBody = httpBody
[...]
TylerP
  • 9,600
  • 4
  • 39
  • 43