Requête HTTP en Swift avec la méthode POST

197

J'essaie d'exécuter une requête HTTP dans Swift, pour POST 2 paramètres à une URL.

Exemple:

Lien: www.thisismylink.com/postName.php

Paramètres:

id = 13
name = Jack

Quelle est la manière la plus simple de faire cela?

Je ne veux même pas lire la réponse. Je veux juste envoyer cela pour effectuer des modifications sur ma base de données via un fichier PHP.

en colère
la source

Réponses:

423

Dans Swift 3 et versions ultérieures, vous pouvez:

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

Où:

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

Cela vérifie à la fois les erreurs de réseau fondamentales et les erreurs HTTP de haut niveau. Ce pourcentage échappe également correctement aux paramètres de la requête.

Notez que j'ai utilisé un namede Jack & Jill, pour illustrer le x-www-form-urlencodedrésultat correct de name=Jack%20%26%20Jill, qui est «encodé en pourcentage» (c'est-à-dire que l'espace est remplacé par %20et &la valeur est remplacée par %26).


Voir la révision précédente de cette réponse pour le rendu Swift 2.

Rob
la source
7
Pour info, si vous voulez faire de vraies requêtes (y compris le pourcentage d'échappements, la création de requêtes complexes, simplifier l'analyse des réponses), pensez à utiliser AlamoFire , de l'auteur d'AFNetworking. Mais si vous souhaitez simplement faire une POSTrequête triviale , vous pouvez utiliser ce qui précède.
Rob
3
Merci Rob, c'était exactement ce que je cherchais! Rien de plus qu'un simple POST. Très bonne réponse!
angeant
1
Après quelques heures de recherche de plusieurs solutions différentes, les lignes 3 et 4 me sauvent la vie car je n'ai pas pu faire fonctionner NSJSONSerialization.dataWithJSONObject pendant toute la vie!
Zork
1
@complexi - Plutôt que de dessiner des connexions entre les $_POSTnoms de fichiers, je réduirais cela à quelque chose de plus simple: le script PHP ne fonctionnera pas du tout si vous n'obtenez pas l'URL correcte. Mais ce n'est pas toujours le cas où vous devez inclure le nom de fichier (par exemple, le serveur peut effectuer un routage URL ou avoir des noms de fichiers par défaut). Dans ce cas, l'OP nous a donné une URL qui comprenait un nom de fichier, donc j'ai simplement utilisé la même URL que lui.
Rob
2
Alamofire n'est ni meilleur ni pire qu'à URLSessioncet égard. Toutes les API de mise en réseau sont intrinsèquement asynchrones, elles devraient également l'être. Désormais, si vous recherchez d'autres moyens élégants de traiter les requêtes asynchrones, vous pouvez envisager de les URLSessionencapsuler ( requêtes ou requêtes Alamofire) dans une Operationsous-classe personnalisée asynchrone . Ou vous pouvez utiliser une bibliothèque de promesses, comme PromiseKit.
Rob
78

Swift 4 et supérieur

@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
la source
7
J'obtiens le message d'erreur suivant avec votre code "Les données n'ont pas pu être lues car elles ne sont pas au bon format."
applecrusher
Je pense que vous obtenez une réponse au format String, pouvez-vous vérifier?
Suhit Patil
1
Je pense que le problème ici dans cette solution est que vous passez le paramètre en tant que sérialisation json et que le service Web prend en tant que paramètres de données de forme
Amr Angry
oui dans la solution les paramètres sont json, veuillez vérifier avec le serveur si cela nécessite des données de formulaire puis changer le type de contenu par exemple request.setValue ("application / x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
Suhit Patil
pour les paramètres en plusieurs parties, utilisez let boundaryConstant = "--V2ymHFg03ehbqgZCaKO6jy--"; request.addvalue ("multipart / form-data boundary = (boundaryConstant)", forHTTPHeaderField: "Content-Type")
Suhit Patil
24

Pour tous ceux qui recherchent un moyen propre d'encoder une requête POST dans Swift 5.

Vous n'avez pas besoin de gérer l'ajout manuel du codage en pourcentage. Utilisez URLComponentspour créer une URL de requête GET. Utilisez ensuite la querypropriété de cette URL pour obtenir correctement le pourcentage de chaîne de requête échappée.

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

Le querysera une chaîne correctement échappée:

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

Vous pouvez maintenant créer une requête et utiliser la requête comme HTTPBody:

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

Vous pouvez maintenant envoyer la demande.

pointum
la source
2
Après divers exemples, cela ne fonctionne que pour Swift 5.
Oleksandr
J'ai hébergé la demande GET mais je me demande que diriez-vous de la demande POST? Comment passer des paramètres dans httpBody ou en ai-je besoin?
Mertalp Tasdelen le
1
Solution intelligente! Merci d'avoir partagé @pointum. Je suis sûr que Martalp n'a plus besoin de la réponse, mais pour quiconque lit, ce qui précède fait une requête POST.
Vlad Spreys
12

Voici la méthode que j'ai utilisée dans ma bibliothèque de journalisation: https://github.com/goktugyil/QorumLogs

Cette méthode remplit les formulaires html dans 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
la source
1
qu'est-ce application/x-www-form-urlencodedque vous réglez?
Honey
Pour transmettre des données dans le corps de la requête @Honey
Achraf
6
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
la source
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
la source
1
Celui-ci pourrait nécessiter une mise à jour pour Swift 3/4 pour utiliser URLRequest
Adam Ware
3

Toutes les réponses ici utilisent des objets JSON. Cela nous a posé des problèmes avec les $this->input->post() méthodes de nos contrôleurs Codeigniter. Le CI_Controllerne peut pas lire directement JSON. Nous avons utilisé cette méthode pour le faire SANS 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()
}

Votre CI_Controller pourra désormais obtenir param1et param2utiliser $this->input->post('param1')et$this->input->post('param2')

CanonicalBear
la source