Solicitud GET rápida con parámetros

81

Soy muy nuevo en Swift, por lo que probablemente tendré muchas fallas en mi código, pero lo que intento lograr es enviar una GETsolicitud a un servidor localhost con parámetros. Más aún, estoy tratando de lograrlo dado que mi función toma dos parámetros baseURL:string,params:NSDictionary. No estoy seguro de cómo combinar esos dos en la URLRequest real. Esto es lo que he probado hasta ahora

    func sendRequest(url:String,params:NSDictionary){
       let urls: NSURL! = NSURL(string:url)
       var request = NSMutableURLRequest(URL:urls)
       request.HTTPMethod = "GET"
       var data:NSData! =  NSKeyedArchiver.archivedDataWithRootObject(params)
       request.HTTPBody = data
       println(request)
       var session = NSURLSession.sharedSession()
       var task = session.dataTaskWithRequest(request, completionHandler:loadedData)
       task.resume()

    }

}

func loadedData(data:NSData!,response:NSURLResponse!,err:NSError!){
    if(err != nil){
        println(err?.description)
    }else{
        var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
        println(jsonResult)

    }

}
MrSSS16
fuente

Respuestas:

156

Al crear una GETsolicitud, no hay cuerpo para la solicitud, sino que todo va en la URL. Para crear una URL (y el porcentaje adecuado de escape), también puede usar URLComponents.

var url = URLComponents(string: "https://www.google.com/search/")!

url.queryItems = [
    URLQueryItem(name: "q", value: "War & Peace")
]

El único truco es que la mayoría de los servicios web necesitan un +porcentaje de caracteres de escape (porque lo interpretarán como un carácter de espacio según lo dicta la application/x-www-form-urlencodedespecificación ). Pero URLComponentsno escapará por ciento. Apple sostiene que +es un carácter válido en una consulta y, por lo tanto, no debe escaparse. Técnicamente, tienen razón, que está permitido en una consulta de un URI, pero tiene un significado especial en las application/x-www-form-urlencodedsolicitudes y realmente no debe pasarse sin escape.

Apple reconoce que tenemos que escapar por ciento de los +personajes, pero aconseja que lo hagamos manualmente:

var url = URLComponents(string: "https://www.wolframalpha.com/input/")!

url.queryItems = [
    URLQueryItem(name: "i", value: "1+2")
]

url.percentEncodedQuery = url.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")

Esta es una solución temporal poco elegante, pero funciona, y es lo que Apple aconseja si sus consultas pueden incluir un +carácter y tiene un servidor que las interpreta como espacios.

Entonces, combinando eso con tu sendRequestrutina, terminas con algo como:

func sendRequest(_ url: String, parameters: [String: String], completion: @escaping ([String: Any]?, Error?) -> Void) {
    var components = URLComponents(string: url)!
    components.queryItems = parameters.map { (key, value) in 
        URLQueryItem(name: key, value: value) 
    }
    components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
    let request = URLRequest(url: components.url!)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data,                            // is there data
            let response = response as? HTTPURLResponse,  // is there HTTP response
            (200 ..< 300) ~= response.statusCode,         // is statusCode 2XX
            error == nil else {                           // was there no error, otherwise ...
                completion(nil, error)
                return
        }

        let responseObject = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any]
        completion(responseObject, nil)
    }
    task.resume()
}

Y lo llamarías así:

sendRequest("someurl", parameters: ["foo": "bar"]) { responseObject, error in
    guard let responseObject = responseObject, error == nil else {
        print(error ?? "Unknown error")
        return
    }

    // use `responseObject` here
}

Personalmente, usaría JSONDecoderhoy en día y devolvería structun diccionario personalizado en lugar de un diccionario, pero eso no es realmente relevante aquí. Con suerte, esto ilustra la idea básica de cómo codificar porcentualmente los parámetros en la URL de una solicitud GET.


Consulte la revisión anterior de esta respuesta para Swift 2 y versiones de escape de porcentaje manual.

Robar
fuente
Gracias por la fantástica respuesta. Solo tengo una pregunta, todavía estoy un poco confundido en cuanto a lo que extension stringestá haciendo con los valores. Además, ¿cuándo debería usarlo HttpBodyentonces?
MrSSS16
La extensión de la cadena es un porcentaje que se escapa de los valores según RFC 3986. Hay ciertos caracteres que tienen significados especiales en las URL (por ejemplo, &separa un parámetro del siguiente, por lo que si &aparece en valor, no puede dejarlo sin escape). Con respecto HTTPBody, debe no utilizarlo en GETla solicitud; Se usa en POSTpero no GET.
Rob
Esto parece tan simple, ¿cuáles son las ventajas de usar algo como github.com/xyyc/SwiftSocket en lugar de esto? Lo siento, soy nuevo en todo esto.
jigzat
Puede que esa no sea la comparación correcta, porque es una biblioteca de sockets y esto es HTTP. Más cercano es algo como Alamofire , pero eso es (a) más flexible (puede manejar solicitudes JSON, solicitudes x-www-form-urlencoded, multiparte, etc.); (b) más funcional (por ejemplo, maneja desafíos de autenticación); y (c) lo saca de las malas hierbas de la programación HTTP. En todo caso, mi respuesta anterior pretende ser una advertencia sobre los riesgos de crear la suya propia NSMutableURLRequest, señalando que hay más de lo que sugiere el OP.
Rob
Esta es una hermosa solución. Gracias @Rob. Por cierto, ¿hay alguna diferencia en el suministro de parámetros como este NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: nil)? parameterssiendo una variedad de diccionarios[String: String]
Isuru
94

Use NSURLComponents para construir su NSURL de esta manera

var urlComponents = NSURLComponents(string: "https://www.google.de/maps/")!

urlComponents.queryItems = [
  NSURLQueryItem(name: "q", value: String(51.500833)+","+String(-0.141944)),
  NSURLQueryItem(name: "z", value: String(6))
]
urlComponents.URL // returns https://www.google.de/maps/?q=51.500833,-0.141944&z=6

fuente: https://www.ralfebert.de/snippets/ios/encoding-nsurl-get-parameters/

Ben-Hur Batista
fuente
5
A pesar de la impresionante respuesta de Rob, la suya es más simple y funciona.
Ene ATAC
3
Esta debería ser la respuesta aceptada. Se recomienda utilizar NSURLComponents junto con elementos de consulta para construir URL. Mucho más seguro y menos propenso a errores.
John Rogers
4

Estoy usando esto, pruébalo en el patio de recreo. Definir las URL base como estructura en constantes

struct Constants {

    struct APIDetails {
        static let APIScheme = "https"
        static let APIHost = "restcountries.eu"
        static let APIPath = "/rest/v1/alpha/"
    }
}

private func createURLFromParameters(parameters: [String:Any], pathparam: String?) -> URL {

    var components = URLComponents()
    components.scheme = Constants.APIDetails.APIScheme
    components.host   = Constants.APIDetails.APIHost
    components.path   = Constants.APIDetails.APIPath
    if let paramPath = pathparam {
        components.path = Constants.APIDetails.APIPath + "\(paramPath)"
    }
    if !parameters.isEmpty {
        components.queryItems = [URLQueryItem]()
        for (key, value) in parameters {
            let queryItem = URLQueryItem(name: key, value: "\(value)")
            components.queryItems!.append(queryItem)
        }
    }

    return components.url!
}

let url = createURLFromParameters(parameters: ["fullText" : "true"], pathparam: "IN")

//Result url= https://restcountries.eu/rest/v1/alpha/IN?fullText=true
anoop4real
fuente
1

Swift 3 :

extension URL {
    func getQueryItemValueForKey(key: String) -> String? {
        guard let components = NSURLComponents(url: self, resolvingAgainstBaseURL: false) else {
              return nil
        }

        guard let queryItems = components.queryItems else { return nil }
     return queryItems.filter {
                 $0.name.lowercased() == key.lowercased()
                 }.first?.value
    }
}

Lo usé para obtener el nombre de la imagen UIImagePickerControlleren func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]):

var originalFilename = ""
if let url = info[UIImagePickerControllerReferenceURL] as? URL, let imageIdentifier = url.getQueryItemValueForKey(key: "id") {
    originalFilename = imageIdentifier + ".png"
    print("file name : \(originalFilename)")
}
Danut Pralea
fuente
su respuesta no responde la pregunta de OP
Ilias Karim
0

Puede extender su Dictionarypara proporcionar solo stringFromHttpParametersi tanto la clave como el valor se ajustan a CustomStringConvertableesto

extension Dictionary where Key : CustomStringConvertible, Value : CustomStringConvertible {
  func stringFromHttpParameters() -> String {
    var parametersString = ""
    for (key, value) in self {
      parametersString += key.description + "=" + value.description + "&"
    }
    return parametersString
  }
}

esto es mucho más limpio y evita llamadas accidentales a stringFromHttpParametersdiccionarios que no tienen negocios llamando a ese método

Reza Shirazian
fuente
-1

Esta extensión que sugirió @Rob funciona para Swift 3.0.1

No pude compilar la versión que incluyó en su publicación con Xcode 8.1 (8B62)

extension Dictionary {

    /// Build string representation of HTTP parameter dictionary of keys and objects
    ///
    /// :returns: String representation in the form of key1=value1&key2=value2 where the keys and values are percent escaped

    func stringFromHttpParameters() -> String {

        var parametersString = ""
        for (key, value) in self {
            if let key = key as? String,
               let value = value as? String {
                parametersString = parametersString + key + "=" + value + "&"
            }
        }
        parametersString = parametersString.substring(to: parametersString.index(before: parametersString.endIndex))
        return parametersString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    }

}
etayluz
fuente
-3

Yo suelo:

let dictionary = ["method":"login_user",
                  "cel":mobile.text!
                  "password":password.text!] as  Dictionary<String,String>

for (key, value) in dictionary {
    data=data+"&"+key+"="+value
    }

request.HTTPBody = data.dataUsingEncoding(NSUTF8StringEncoding);
Fabio Guerra
fuente