Enviar request POST utilizando NSURLSession

Actualización: se encontró la solución. Puedes leerlo al final de la publicación.

Estoy intentando realizar una request POST a una API REST remota utilizando NSURLSession . La idea es hacer una request con dos parameters: deviceId y textContent .

El problema es que el server no reconoce esos parameters. La parte del server funciona correctamente porque he enviado un POST usando POSTMAN para Google Chrome y funcionó perfectamente.

Este es el código que estoy usando en este momento:

 NSString *deviceID = [[NSUserDefaults standardUserDefaults] objectForKey:@"deviceID"]; NSString *textContent = @"New note"; NSString *noteDataString = [NSString stringWithFormat:@"deviceId=%@&textContent=%@", deviceID, textContent]; NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; sessionConfiguration.HTTPAdditionalHeaders = @{ @"api-key" : @"API_KEY", @"Content-Type" : @"application/json" }; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; NSURL *url = [NSURL URLWithString:@"http://url_to_manage_post_requests"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPBody = [noteDataString dataUsingEncoding:NSUTF8StringEncoding]; request.HTTPMethod = @"POST"; NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { // The server answers with an error because it doesn't receive the params }]; [postDataTask resume]; 

He intentado el mismo procedimiento con un NSURLSessionUploadTask :

 // ... NSURL *url = [NSURL URLWithString:@"http://url_to_manage_post_requests"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:[noteDataString dataUsingEncoding:NSUTF8StringEncoding] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { // The server answers with an error because it doesn't receive the params }]; [uploadTask resume]; 

¿Algunas ideas?

Solución

El problema con mi enfoque fue que estaba enviando el encabezado de Content-Type incorrecto con todas mis requestes. Entonces, el único cambio necesario para que el código funcione correctamente es eliminar el encabezado HTTP Content-Type = application/json . Entonces el código correcto sería este:

 NSString *deviceID = [[NSUserDefaults standardUserDefaults] objectForKey:@"deviceID"]; NSString *textContent = @"New note"; NSString *noteDataString = [NSString stringWithFormat:@"deviceId=%@&textContent=%@", deviceID, textContent]; NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; sessionConfiguration.HTTPAdditionalHeaders = @{ @"api-key" : @"API_KEY" }; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; NSURL *url = [NSURL URLWithString:@"http://url_to_manage_post_requests"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPBody = [noteDataString dataUsingEncoding:NSUTF8StringEncoding]; request.HTTPMethod = @"POST"; NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { // The server answers with an error because it doesn't receive the params }]; [postDataTask resume]; 

Envío de imágenes junto con otros parameters

Si necesita publicar imágenes junto con otros parameters utilizando NSURLSession aquí tiene un ejemplo:

 NSString *deviceID = [[NSUserDefaults standardUserDefaults] objectForKey:@"deviceID"]; NSString *textContent = @"This is a new note"; // Build the request body NSString *boundary = @"SportuondoFormBoundary"; NSMutableData *body = [NSMutableData data]; // Body part for "deviceId" parameter. This is a string. [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", @"deviceId"] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[[NSString stringWithFormat:@"%@\r\n", deviceID] dataUsingEncoding:NSUTF8StringEncoding]]; // Body part for "textContent" parameter. This is a string. [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", @"textContent"] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[[NSString stringWithFormat:@"%@\r\n", textContent] dataUsingEncoding:NSUTF8StringEncoding]]; // Body part for the attachament. This is an image. NSData *imageData = UIImageJPEGRepresentation([UIImage imageNamed:@"ranking"], 0.6); if (imageData) { [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"image.jpg\"\r\n", @"image"] dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:[@"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; [body appendData:imageData]; [body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; } [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; // Setup the session NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; sessionConfiguration.HTTPAdditionalHeaders = @{ @"api-key" : @"55e76dc4bbae25b066cb", @"Accept" : @"application/json", @"Content-Type" : [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] }; // Create the session // We can use the delegate to track upload progress NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil]; // Data uploading task. We could use NSURLSessionUploadTask instead of NSURLSessionDataTask if we needed to support uploads in the background NSURL *url = [NSURL URLWithString:@"URL_TO_UPLOAD_TO"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; request.HTTPBody = body; NSURLSessionDataTask *uploadTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { // Process the response }]; [uploadTask resume]; 

Puedes intentar usar un NSDictionary para los params. Lo siguiente enviará los parameters correctamente a un server JSON.

 NSError *error; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; NSURL *url = [NSURL URLWithString:@"[JSON SERVER"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; [request addValue:@"application/json" forHTTPHeaderField:@"Accept"]; [request setHTTPMethod:@"POST"]; NSDictionary *mapData = [[NSDictionary alloc] initWithObjectsAndKeys: @"TEST IOS", @"name", @"IOS TYPE", @"typemap", nil]; NSData *postData = [NSJSONSerialization dataWithJSONObject:mapData options:0 error:&error]; [request setHTTPBody:postData]; NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { }]; [postDataTask resume]; 

Espero que esto ayude (estoy tratando de orderar un problema de autenticidad CSRF con lo anterior, pero envía los parameters en el dictionary NSDictionary).

Motivación

A veces he recibido algunos errores cuando desea pasar httpBody serializado a Data from Dictionary , que en la mayoría de los casos se debe a una encoding errónea o a datos mal formados debido a objects que no conforman NSCoding en el Dictionary .

Solución

Dependiendo de sus requisitos, una solución fácil sería crear una String lugar de un Dictionary y convertirla en Data . Usted tiene los ejemplos de código a continuación escritos en Objective-C y Swift 3.0 .

C objective

 // Create the URLSession on the default configuration NSURLSessionConfiguration *defaultSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultSessionConfiguration]; // Setup the request with URL NSURL *url = [NSURL URLWithString:@"yourURL"]; NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url]; // Convert POST string parameters to data using UTF8 Encoding NSString *postParams = @"api_key=APIKEY&email=example@example.com&password=password"; NSData *postData = [postParams dataUsingEncoding:NSUTF8StringEncoding]; // Convert POST string parameters to data using UTF8 Encoding [urlRequest setHTTPMethod:@"POST"]; [urlRequest setHTTPBody:postData]; // Create dataTask NSURLSessionDataTask *dataTask = [defaultSession dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { // Handle your response here }]; // Fire the request [dataTask resume]; 

Rápido

 // Create the URLSession on the default configuration let defaultSessionConfiguration = URLSessionConfiguration.default let defaultSession = URLSession(configuration: defaultSessionConfiguration) // Setup the request with URL let url = URL(string: "yourURL") var urlRequest = URLRequest(url: url!) // Note: This is a demo, that's why I use implicitly unwrapped optional // Convert POST string parameters to data using UTF8 Encoding let postParams = "api_key=APIKEY&email=example@example.com&password=password" let postData = postParams.data(using: .utf8) // Set the httpMethod and assign httpBody urlRequest.httpMethod = "POST" urlRequest.httpBody = postData // Create dataTask let dataTask = defaultSession.dataTask(with: urlRequest) { (data, response, error) in // Handle your response here } // Fire the request dataTask.resume() 

Puede usar https://github.com/mxcl/OMGHTTPURLRQ

 id config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:someID]; id session = [NSURLSession sessionWithConfiguration:config delegate:someObject delegateQueue:[NSOperationQueue new]]; OMGMultipartFormData *multipartFormData = [OMGMultipartFormData new]; [multipartFormData addFile:data1 parameterName:@"file1" filename:@"myimage1.png" contentType:@"image/png"]; NSURLRequest *rq = [OMGHTTPURLRQ POST:url:multipartFormData]; id path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"upload.NSData"]; [rq.HTTPBody writeToFile:path atomically:YES]; [[session uploadTaskWithRequest:rq fromFile:[NSURL fileURLWithPath:path]] resume]; 

Si está usando Swift, la biblioteca Just lo hace por usted. Ejemplo de su file readme:

 // talk to registration end point Just.post( "http://justiceleauge.org/member/register", data: ["username": "barryallen", "password":"ReverseF1ashSucks"], files: ["profile_photo": .URL(fileURLWithPath:"flash.jpeg", nil)] ) { (r) if (r.ok) { /* success! */ } } 

La solución Swift 2.0 está aquí:

  let urlStr = “http://url_to_manage_post_requests” let url = NSURL(string: urlStr) let request: NSMutableURLRequest = NSMutableURLRequest(URL: url!) request.HTTPMethod = "POST" request.setValue(“application/json” forHTTPHeaderField:”Content-Type”) request.timeoutInterval = 60.0 //additional headers request.setValue(“deviceIDValue”, forHTTPHeaderField:”DeviceId”) let bodyStr = “string or data to add to body of request” let bodyData = bodyStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true) request.HTTPBody = bodyData let session = NSURLSession.shanetworkingSession() let task = session.dataTaskWithRequest(request){ (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in if let httpResponse = response as? NSHTTPURLResponse { print("responseCode \(httpResponse.statusCode)") } if error != nil { // You can handle error response here print("\(error)") }else { //Converting response to collection formate (array or dictionary) do{ let jsonResult: AnyObject = (try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) //success code }catch{ //failure code } } } task.resume() 

El código de Teja Kumar Bethina cambió para Swift 3:

  let urlStr = "http://url_to_manage_post_requests" let url = URL(string: urlStr) var request: URLRequest = URLRequest(url: url!) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField:"Content-Type") request.timeoutInterval = 60.0 //additional headers request.setValue("deviceIDValue", forHTTPHeaderField:"DeviceId") let bodyStr = "string or data to add to body of request" let bodyData = bodyStr.data(using: String.Encoding.utf8, allowLossyConversion: true) request.httpBody = bodyData let task = URLSession.shanetworking.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) -> Void in if let httpResponse = response as? HTTPURLResponse { print("responseCode \(httpResponse.statusCode)") } if error != nil { // You can handle error response here print("\(error)") } else { //Converting response to collection formate (array or dictionary) do { let jsonResult = (try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)) //success code } catch { //failure code } } } task.resume()