Swift 4 send POST request as x-www-form-urlencoded

14,462

Solution 1

You are passing the result of User.archive(w: thing) as the data embedded in the request body, which may never work. Generally, your archive(w:) and unarchive(d:) would never generate any useful results and you should better remove them immediately.

If you want to pass parameters where x-www-form-urlencoded is needed, you need to create a URL-query-like string.

Try something like this:

func login(username: String, password: String, completionHandler: @escaping (Login) -> Void) {
    let dataThing = "username=\(username)&password=\(password)".data(using: .utf8)

    xhr.urlSession(method: "POST", file: "https://kida.al/login_register/", data: dataThing) { (result: XHR.Result<User>) in
        //...
    }
}

The example above is a little bit too simplified, that you may need to escape username and/or password before embedding it in a string, when they can contain some special characters. You can find many articles on the web about it.

Solution 2

I used below code in swift 4

  guard let url = URL(string: "http://192.168.88.129:81/authenticate") else {
        return
    }


    let user1 = username.text!
    let pass = passwordfield.text!
    print(user1)
    print(pass)
    let data : Data = "username=\(user1)&password=\(pass)&grant_type=password".data(using: .utf8)!
    var request : URLRequest = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type");
    request.setValue(NSLocalizedString("lang", comment: ""), forHTTPHeaderField:"Accept-Language");
    request.httpBody = data

    print("one called")

    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)
    // vs let session = URLSession.shared
      // make the request
    let task = session.dataTask(with: request, completionHandler: {
        (data, response, error) in

         if let error = error
        {
            print(error)
        }
         else if let response = response {
            print("her in resposne")

        }else if let data = data
         {
            print("here in data")
            print(data)
        }

        DispatchQueue.main.async { // Correct

            guard let responseData = data else {
                print("Error: did not receive data")
                return
            }

            let decoder = JSONDecoder()
            print(String(data: responseData, encoding: .utf8))
            do {
              //  let todo = try decoder.decode(T.self, from: responseData)
              //  NSAssertionHandler(.success(todo))
            } catch {
                print("error trying to convert data to JSON")
                //print(error)
              //  NSAssertionHandler(.failure(error))
            }
        }
    })
    task.resume()


}

Solution 3

Quoting from this post

In PHP, a variable or array element which has never been set is different from one whose value is null; attempting to access such an unset value is a runtime error.

The Undefined index error occurs when you try to access an unset variable or an array element. You should use function isset inorder to safely access the username param from the POST body. Try the below code in your PHP file.

if (isset($_POST["username"]))
{
  $user= $_POST["username"];
  echo 'Your Username is ' . $user;
} 
else 
{
  $user = null;
  echo "No user name found";
}

Solution 4

Another way of doing this is as follows:

  1. Add the URLEncodedFormEncoder.swift into your project. This is a custom URLEncodedFormEncoder from Alamofire / Vapor.

  2. Conform your model to native Swift Encodable protocol, just as you do with JSON coding.

  3. Encode the model just as you do during json encoding

// example
let requstModel = OpenIDCTokenRequest(
                             clientId: clientId,
                             clientSecret: clientSecret,
                             username: username,
                             password: password
                  )

guard let requestData: Data = try? URLEncodedFormEncoder().encode(requstModel) else {
    return // handle encoding error
}
Share:
14,462
M1X
Author by

M1X

Experienced Software Engineer with a demonstrated history of working in the web development industry. Strong engineering professional skilles in Angular, TypeScript, ECMAScript 6 and Node.Js.

Updated on June 15, 2022

Comments

  • M1X
    M1X almost 2 years

    I want to send a POST request to my php 7 server which accepts data as application/x-www-form-urlencoded. The data I have is inside a Struct and I want to get every property of this struct as a parameter when I submit it.

    This is the struct which handles my urlSession requests both GET and POST XHR.swift

    struct XHR {
    
        enum Result<T> {
            case success(T)
            case failure(Error)
        }
    
        func urlSession<T>(method: String? = nil, file: String, data: Data? = nil, completionHandler: @escaping (Result<T>) -> Void) where T: Codable {
    
            let file = file.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
    
            // Set up the URL request
            guard let url = URL.init(string: file) else {
                print("Error: cannot create URL")
                return
            }
    
            var urlRequest = URLRequest(url: url)
    
            if method == "POST" {
                urlRequest.httpMethod = "POST";
                urlRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
                urlRequest.httpBody = data
                print(urlRequest.httpBody)
            }
    
            // set up the session
            let config = URLSessionConfiguration.default
            let session = URLSession(configuration: config)
            // vs let session = URLSession.shared
    
            // make the request
            let task = session.dataTask(with: urlRequest, completionHandler: {
                (data, response, error) in
    
                DispatchQueue.main.async { // Correct
    
                    guard let responseData = data else {
                        print("Error: did not receive data")
                        return
                    }
    
                    let decoder = JSONDecoder()
                    print(String(data: responseData, encoding: .utf8))
                    do {
                        let todo = try decoder.decode(T.self, from: responseData)
                        completionHandler(.success(todo))
                    } catch {
                        print("error trying to convert data to JSON")
                        //print(error)
                        completionHandler(.failure(error))
                    }
                }
            })
            task.resume()
        }
    
    }
    

    This is the functions which sends a POST request to the server: VideoViewModel.swift

    struct User: Codable {
        let username: String
        let password: String
    
        static func archive(w:User) -> Data {
            var fw = w
            return Data(bytes: &fw, count: MemoryLayout<User>.stride)
        }
    
        static func unarchive(d:Data) -> User {
            guard d.count == MemoryLayout<User>.stride else {
                fatalError("BOOM!")
            }
    
            var w:User?
            d.withUnsafeBytes({(bytes: UnsafePointer<User>)->Void in
                w = UnsafePointer<User>(bytes).pointee
            })
            return w!
        }
    }
    
    enum Login {
        case success(User)
        case failure(Error)
    }
    
    func login(username: String, password: String, completionHandler: @escaping (Login) -> Void) {
        let thing = User(username: username, password: password)
        let dataThing = User.archive(w: thing)
    
        xhr.urlSession(method: "POST", file: "https://kida.al/login_register/", data: dataThing) { (result: XHR.Result<User>) in
            switch result {
            case .failure(let error):
                completionHandler(.failure(error))
            case .success(let user):
                //let convertedThing = User.unarchive(d: user)
                completionHandler(.success(user))
            }
        }
    }
    

    And I call it like this:

    videoViewModel.login(username: "rexhin", password: "bonbon") { (result: VideoViewModel.Login) in
        switch result {
        case .failure(let error):
            print("error")
    
        case .success(let user):
            print(user)
        }
    }
    

    From PHP I can see that a POST request is submitted successfully but when I try to get the username field by doing $_POST["username"] I get Undefined index:

    Full code of the app can be seen here https://gitlab.com/rexhin/ios-kida.git

  • M1X
    M1X over 6 years
    I know that but the variable is not SET
  • Rizwan Ahmed
    Rizwan Ahmed over 6 years
    Are you passing your data properly from your app?
  • M1X
    M1X over 6 years
    That's the problem, I think no
  • Rizwan Ahmed
    Rizwan Ahmed over 6 years
    Are you encoding your string data before sending it to the server? Example: [yourString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
  • M1X
    M1X over 6 years
    Yes but I need to do this manually every time I send a POST request. Is it a way to convert a struct to "username=\(username)&password=\(password)" ?
  • OOPer
    OOPer over 6 years
    @RexhinHoxha, yes, do it manually. If you want to ask something about any structs, that's another question. In this question, you have written this struct.
  • Krika
    Krika over 5 years
    This might not work if the values of username or password need to be URL-encoded.
  • OOPer
    OOPer over 5 years
    @JeremyWhite, thanks for clarifying. The same thing is noted in the part The example above is ..., but your comment would be better.
  • Dilip Saket
    Dilip Saket almost 4 years
    I was having issue with form data since last 1 year and I was using alamofire temporary . But, finally found this code useful which pass parameters using pure coding. Many Thanks!