Solicitud HTTP en Swift con método POST

190

Estoy tratando de ejecutar una solicitud HTTP en Swift, para enviar los parámetros a una URL.

Ejemplo:

Enlace: www.thisismylink.com/postName.php

Parámetros:

id = 13
name = Jack

¿Cuál es la forma más sencilla de hacer eso?

Ni siquiera quiero leer la respuesta. Solo quiero enviar eso para realizar cambios en mi base de datos a través de un archivo PHP.

angeant
fuente

Respuestas:

411

En Swift 3 y versiones posteriores puedes:

let url = URL(string: "http://www.thisismylink.com/postName.php")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let parameters: [String: Any] = [
    "id": 13,
    "name": "Jack & Jill"
]
request.httpBody = parameters.percentEncoded()

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    guard let data = data, 
        let response = response as? HTTPURLResponse, 
        error == nil else {                                              // check for fundamental networking error
        print("error", error ?? "Unknown error")
        return
    }

    guard (200 ... 299) ~= response.statusCode else {                    // check for http errors
        print("statusCode should be 2xx, but is \(response.statusCode)")
        print("response = \(response)")
        return
    }

    let responseString = String(data: data, encoding: .utf8)
    print("responseString = \(responseString)")
}

task.resume()

Dónde:

extension Dictionary {
    func percentEncoded() -> Data? {
        return map { key, value in
            let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
            let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
            return escapedKey + "=" + escapedValue
        }
        .joined(separator: "&")
        .data(using: .utf8)
    }
}

extension CharacterSet { 
    static let urlQueryValueAllowed: CharacterSet = {
        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
        let subDelimitersToEncode = "!$&'()*+,;="

        var allowed = CharacterSet.urlQueryAllowed
        allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
        return allowed
    }()
}

Esto verifica tanto los errores de red fundamentales como los errores HTTP de alto nivel. Esto también se escapa correctamente por ciento de los parámetros de la consulta.

Tenga en cuenta que usé una namede Jack & Jill, para ilustrar el x-www-form-urlencodedresultado adecuado de name=Jack%20%26%20Jill, que está "codificado en porcentaje" (es decir, el espacio se reemplaza por %20y &el valor en el valor se reemplaza por %26).


Consulte la revisión anterior de esta respuesta para la interpretación de Swift 2.

Robar
fuente
77
Para su información, si desea hacer solicitudes reales (incluido el porcentaje de escape, crear solicitudes complejas, simplificar el análisis de las respuestas), considere utilizar AlamoFire , del autor de AFNetworking. Pero si solo desea hacer una POSTsolicitud trivial , puede usar lo anterior.
Rob
2
Gracias Rob, ¡eso era justo lo que estaba buscando! Nada más que un simple POST. ¡Gran respuesta!
angeant
1
¡Después de algunas horas de buscar varias soluciones diferentes, las líneas 3 y 4 me están salvando la vida ya que no podría por mi vida conseguir que NSJSONSerialization.dataWithJSONObject funcione!
Zork
1
@complexi: en lugar de dibujar conexiones entre $_POSTy nombres de archivo, reduciría esto a algo más simple: el script PHP no se ejecutará en absoluto si no obtiene la URL correcta. Pero no siempre se debe incluir el nombre de archivo (por ejemplo, el servidor puede estar haciendo enrutamiento de URL o tener nombres de archivo predeterminados). En este caso, el OP nos dio una URL que incluía un nombre de archivo, así que simplemente usé la misma URL que él.
Rob
1
Alamofire no es mejor ni peor que URLSessionen este sentido. Todas las API de red son inherentemente asíncronas, como deberían ser. Ahora, si está buscando otras formas elegantes de tratar solicitudes asincrónicas, puede considerar envolverlas (ya sea URLSessionsolicitudes o solicitudes de Alamofire) en una Operationsubclase asincrónica personalizada . O puede usar algunas bibliotecas de promesas, como PromiseKit.
Rob
71

Swift 4 y superior

@IBAction func submitAction(sender: UIButton) {

    //declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid

    let parameters = ["id": 13, "name": "jack"]

    //create the url with URL
    let url = URL(string: "www.thisismylink.com/postName.php")! //change the url

    //create the session object
    let session = URLSession.shared

    //now create the URLRequest object using the url object
    var request = URLRequest(url: url)
    request.httpMethod = "POST" //set http method as POST

    do {
        request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
    } catch let error {
        print(error.localizedDescription)
    }

    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")

    //create dataTask using the session object to send data to the server
    let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in

        guard error == nil else {
            return
        }

        guard let data = data else {
            return
        }

        do {
            //create json object from data
            if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
                print(json)
                // handle json...
            }
        } catch let error {
            print(error.localizedDescription)
        }
    })
    task.resume()
}
Suhit Patil
fuente
66
Recibo el siguiente error con su código "No se pudieron leer los datos porque no están en el formato correcto".
applecrusher
Creo que está obteniendo respuesta en formato de cadena, ¿puede verificar?
Suhit Patil
1
Creo que el problema aquí en esta solución es que pasa el parámetro como serialización json y el servicio web está tomando como parámetros de formdata
Amr Angry
sí, en solución, los parámetros son json, verifique con el servidor si requiere datos de formulario y luego cambie el tipo de contenido, por ejemplo, request.setValue ("application / x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
Suhit Patil
para los parámetros de varias partes, use let boundaryConstant = "--V2ymHFg03ehbqgZCaKO6jy--"; request.addvalue ("multipart / form-data boundary = (boundaryConstant)", forHTTPHeaderField: "Content-Type")
Suhit Patil
19

Para cualquiera que busque una forma limpia de codificar una solicitud POST en Swift 5.

No necesita lidiar con la adición manual de codificación porcentual. Use URLComponentspara crear una URL de solicitud GET. Luego use la querypropiedad de esa URL para obtener la cadena de consulta con el porcentaje de escape correcto.

let url = URL(string: "https://example.com")!
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)!

components.queryItems = [
    URLQueryItem(name: "key1", value: "NeedToEscape=And&"),
    URLQueryItem(name: "key2", value: "vålüé")
]

let query = components.url!.query

La queryhabrá una cadena de escape adecuado:

key1 = NeedToEscape% 3DAnd% 26 & key2 = v% C3% A5l% C3% BC% C3% A9

Ahora puede crear una solicitud y usar la consulta como HTTPBody:

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = Data(query.utf8)

Ahora puedes enviar la solicitud.

punto
fuente
1
Después de varios ejemplos, solo esto funciona para Swift 5.
Oleksandr
Realicé una solicitud GET pero me pregunto qué tal la solicitud POST. ¿Cómo pasar parámetros a httpBody o lo necesito?
Mertalp Tasdelen
Solución inteligente! Gracias por compartir @pointum. Estoy seguro de que Martalp ya no necesita la respuesta, pero para cualquiera que lea, lo anterior hace una solicitud POST.
Vlad Spreys
12

Aquí está el método que utilicé en mi biblioteca de registro: https://github.com/goktugyil/QorumLogs

Este método llena formularios html dentro de Google Forms.

    var url = NSURL(string: urlstring)

    var request = NSMutableURLRequest(URL: url!)
    request.HTTPMethod = "POST"
    request.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
    request.HTTPBody = postData.dataUsingEncoding(NSUTF8StringEncoding)
    var connection = NSURLConnection(request: request, delegate: nil, startImmediately: true)
Esqarrouth
fuente
1
¿ application/x-www-form-urlencodedQué estás configurando?
Honey
Para pasar datos en el cuerpo de solicitud @Honey
Achraf
4
let session = URLSession.shared
        let url = "http://...."
        let request = NSMutableURLRequest(url: NSURL(string: url)! as URL)
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        var params :[String: Any]?
        params = ["Some_ID" : "111", "REQUEST" : "SOME_API_NAME"]
        do{
            request.httpBody = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions())
            let task = session.dataTask(with: request as URLRequest as URLRequest, completionHandler: {(data, response, error) in
                if let response = response {
                    let nsHTTPResponse = response as! HTTPURLResponse
                    let statusCode = nsHTTPResponse.statusCode
                    print ("status code = \(statusCode)")
                }
                if let error = error {
                    print ("\(error)")
                }
                if let data = data {
                    do{
                        let jsonResponse = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions())
                        print ("data = \(jsonResponse)")
                    }catch _ {
                        print ("OOps not good JSON formatted response")
                    }
                }
            })
            task.resume()
        }catch _ {
            print ("Oops something happened buddy")
        }
Osman
fuente
3
@IBAction func btn_LogIn(sender: AnyObject) {

    let request = NSMutableURLRequest(URL: NSURL(string: "http://demo.hackerkernel.com/ios_api/login.php")!)
    request.HTTPMethod = "POST"
    let postString = "email: [email protected] & password: testtest"
    request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
    let task = NSURLSession.sharedSession().dataTaskWithRequest(request){data, response, error in
        guard error == nil && data != nil else{
            print("error")
            return
        }
        if let httpStatus = response as? NSHTTPURLResponse where httpStatus.statusCode != 200{
            print("statusCode should be 200, but is \(httpStatus.statusCode)")
            print("response = \(response)")
        }
        let responseString = String(data: data!, encoding: NSUTF8StringEncoding)
        print("responseString = \(responseString)")
    }
    task.resume()
}
Raish Khan
fuente
1
Este podría necesitar una actualización para Swift 3/4 para usar URLRequest
Adam Ware
2

Todas las respuestas aquí usan objetos JSON. Esto nos dio problemas con los $this->input->post() métodos de nuestros controladores Codeigniter. El CI_Controllerno puede leer JSON directamente. Utilizamos este método para hacerlo SIN JSON

func postRequest() {
    // Create url object
    guard let url = URL(string: yourURL) else {return}

    // Create the session object
    let session = URLSession.shared

    // Create the URLRequest object using the url object
    var request = URLRequest(url: url)

    // Set the request method. Important Do not set any other headers, like Content-Type
    request.httpMethod = "POST" //set http method as POST

    // Set parameters here. Replace with your own.
    let postData = "param1_id=param1_value&param2_id=param2_value".data(using: .utf8)
    request.httpBody = postData

    // Create a task using the session object, to run and return completion handler
    let webTask = session.dataTask(with: request, completionHandler: {data, response, error in
    guard error == nil else {
        print(error?.localizedDescription ?? "Response Error")
        return
    }
    guard let serverData = data else {
        print("server data error")
        return
    }
    do {
        if let requestJson = try JSONSerialization.jsonObject(with: serverData, options: .mutableContainers) as? [String: Any]{
            print("Response: \(requestJson)")
        }
    } catch let responseError {
        print("Serialisation in error in creating response body: \(responseError.localizedDescription)")
        let message = String(bytes: serverData, encoding: .ascii)
        print(message as Any)
    }

    // Run the task
    webTask.resume()
}

Ahora su CI_Controller podrá obtener param1y param2usar $this->input->post('param1')y$this->input->post('param2')

CanonicalBear
fuente