iosswift

Data in HTTPBody with a PUT method fails, while it works with a POST?


First of all I would like to say I got the exact same problem as the following question: How to add data to HTTPBody with PUT method in NSURLSession?, bt it wasn't answered so I made my own question.

We have written a node API for a school assignment. We've tested the whole API. (The chances of being something wrong there are slim.)

After that I went working on a iOS client to CRUD users. Making a user is going perfectly, but whenever I try to edit a user something strange happens. The data on the server arrives as undefined.

I use the following code to save a user:

func saveUser(user: User, completionHandler: (String?, User?) -> Void) {
    let url = NSURL(string: "https://pokeapi9001.herokuapp.com/api/users/")
    let request = NSMutableURLRequest(URL:url!)
    request.HTTPMethod = "POST"
    
    let postString = "email=\(user.email)&password=\(user.password!)&role=\(user.role)"
    request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
    
    request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
    
    
    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
        data, response, error in
        if error != nil {
            print("error: \(error)")
        }
        
        do {
            guard let data = data else {
                throw JSONError.NoData
            }
            guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
                throw JSONError.ConversionFailed
            }
            
            //do specific things
            
        } catch let error as JSONError {
            completionHandler(error.rawValue, nil)
        } catch let error as NSError {
            completionHandler(error.debugDescription, nil)
        }
        
    }
    task.resume()
}

Keep in mind, this is working perfectly (don't know if it is intended to be used like this).

To edit a user I use the following code:

func editUser(user: User, completionHandler: (String?, User?) -> Void) {
    let url = NSURL(string: "https://pokeapi9001.herokuapp.com/api/users/\(user.id!)")
    let request = NSMutableURLRequest(URL:url!)
    request.HTTPMethod = "PUT"
    
    let postString = "email=\(user.email)&password=\(user.password!)&role=\(user.role)"
    request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
    
    request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
    
    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
        data, response, error in
        if error != nil {
            print("error: \(error)")
        }
        
        do {
            guard let data = data else {
                throw JSONError.NoData
            }
            guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary else {
                throw JSONError.ConversionFailed
            }

            //do specific things
            
        } catch let error as JSONError {
            completionHandler(error.rawValue, nil)
        } catch let error as NSError {
            completionHandler(error.debugDescription, nil)
        }
    }
    task.resume()
}

(The original code is a bit longer but I removed parts that had nothing to do with the actual posting of the data).

I have really no idea what I'm doing wrong, could be something small and stupid. Please help.

Edit after input from @fiks

To be clear, the problem I am having is that I fill the postString the same way in the editUser method as I do in the saveUser method. (At least I think I do).

However in the saveUser method the postString seems to be correctly passed through to the API (it creates a new user with the given values). The editUser method does not pass the values through.

If I put a console log on the server it shows all values are "undefined". To test if the postString was correct on the iOS part I printed both strings out. Both of them outputted:

email=user@test.com&password=test&role=admin


Solution

  • From what I see in the postman request, you are sending a x-www-form-urlencoded request.

    You have to specify it in the code. See example: POST request using application/x-www-form-urlencoded

    Regarding Charles: since you are using https, you have to enable proxy for the host. More info here: https://www.charlesproxy.com/documentation/proxying/ssl-proxying/