12

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
  • 4,971
  • 10
  • 61
  • 123

4 Answers4

17

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()


}
Mohammad Muddasir
  • 947
  • 10
  • 21
  • 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! – Dilip Saket Jul 02 '20 at 14:40
10

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.

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • 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)"` ? – M1X Feb 11 '18 at 12:10
  • @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_. – OOPer Feb 11 '18 at 14:28
  • This might not work if the values of username or password need to be URL-encoded. – Jeremy White Nov 01 '18 at 23:52
  • @JeremyWhite, thanks for clarifying. The same thing is noted in the part _The example above is ..._, but your comment would be better. – OOPer Nov 02 '18 at 16:03
3

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
}
Shengchalover
  • 614
  • 5
  • 10
1

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";
}
Rizwan Ahmed
  • 919
  • 13
  • 19