renpen
renpen

Reputation: 23

set body in NSMutableURLRequest doesn´t work

Header:

let header = ["Content-Type" : "application/x-www-form-urlencoded", "Authorization" : "Basic " + self.basicAuth];

Body:

var body : [String : AnyObject] = [:];
let body = ["grant_type" : "client_credentials", "scope" : "MessageSender"];

The Request and Serialization:

private func makeHTTPPostRequest(path: String, header: [String : String], body: [String: AnyObject], onCompletion: @escaping ServiceResponse) {
        let request = NSMutableURLRequest(url: NSURL(string: path)! as URL)

        // Set the method to POST
        request.httpMethod = "POST"

        do {
            // Set the POST body for the request
            let jsonBody = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
            request.httpBody = jsonBody
            let session = URLSession.shared
            request.allHTTPHeaderFields = header;

            let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
                if let httpResponse = response as? HTTPURLResponse {
                    if let jsonData = data {
                        let json:JSON = JSON(data: jsonData)
                        print(response)
                        print(json)
                        onCompletion(json,httpResponse, error as NSError?)
                    } else {
                        onCompletion(JSON.null,HTTPURLResponse.init(), error as NSError?)
                    }
                }

            })
            task.resume()
        } catch {
            onCompletion(JSON.null,HTTPURLResponse.init(), nil)
        }
    }
}

When the request is done, it fires a 400 response with { "error_description" : "grant_type parameter is requiered field and it has to be non empty string.", "error" : "invalid_request" }

Obviously the body is not set correctly but I really don´t know why. I´m using this piece of code in other applications with no problem... . The same request works like charm in Postman. The body in postman is set with type x-www-form-urlencoded. Maybe the JSONSerialization is wrong ?

Upvotes: 1

Views: 670

Answers (1)

OOPer
OOPer

Reputation: 47896

To send a POST request with Content-Type: application/x-www-form-urlencoded;, you need to create a URL query-like String and then convert it to a Data. Your code or any Swift Standard Library functions do not have the functionality. You may need to write it by yourself, or find a suitable third-party library. (Of course JSONSerialization is not suitable here, the String is not a JSON.)

With given a Dictionary<String, String>, you can do it like this:

var body: [String: String] = [:]
body = ["grant_type": "client_credentials", "scope": "MessageSender"]

(Simplified...)

request.httpBody = body.map{"\($0)=\($1)"}.joined(separator: "&").data(using: .utf8)
//`body.map{"\($0)=\($1)"}.joined(separator: "&")` -> grant_type=client_credentials&scope=MessageSender

(Strict... 4.10.22.6 URL-encoded form data)

extension CharacterSet {
    static let wwwFormUrlencodedAllowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._*" + "+")
}

extension String {
    var wwwFormUrlencoded: String {
        return self
            .replacingOccurrences(of: " ", with: "+")
            .addingPercentEncoding(withAllowedCharacters: .wwwFormUrlencodedAllowed)!
    }
}

class HTTPBody {
    static func wwwFormUrlencodedData(withDictionary dict: [String: String]) -> Data {
        return body
            .map{"\($0.wwwFormUrlencoded)=\($1.wwwFormUrlencoded)"}
            .joined(separator: "&").data(using: .utf8)!
    }
}

request.httpBody = HTTPBody.wwwFormUrlencodedData(withDictionary: body)

(Remember, not many servers interpret the received form data as strictly generated.)


One more, this is not a critical issue in this case, but you should better use Swift classes rather than NS-something:

typealias  ServiceResponse = (JSON, HTTPURLResponse?, Error?)->Void

private func makeHTTPPostRequest(path: String, header: [String : String], body: [String: String], onCompletion: @escaping ServiceResponse) {
    var request = URLRequest(url: URL(string: path)!)

    // Set the method to POST
    request.httpMethod = "POST"

    // Set the POST body for the request (assuming your server likes strict form data)
    request.httpBody = HTTPBody.wwwFormUrlencodedData(withDictionary: body)
    let session = URLSession.shared
    request.allHTTPHeaderFields = header;

    let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in
        if let httpResponse = response as? HTTPURLResponse {
            if let jsonData = data {
                let json:JSON = JSON(data: jsonData)
                print(response)
                print(json)
                onCompletion(json, httpResponse, error)
            } else {
                onCompletion(JSON.null, httpResponse, error)
            }
        }
    })
    task.resume()
}

Upvotes: 1

Related Questions