2014-01-06 4 views
4

Как this issue.AFNetworking 2.0 multipart request body blank

Использование AFNetworking 2.0.3 и попытка загрузить изображение с помощью POST + конструкции AFHTTPSessionManagerBodyWithBlock. По неизвестным причинам кажется, что тело сообщения HTTP всегда пустое, когда запрос отправляется на сервер.

I подкласс AFHTTPSessionManager ниже (отсюда и использование [self POST ...]

Я попытался построения данной просьбе два способа

Метод 1:.. Я просто попытался пройти Params, а затем добавить только изображение данные должны существовать это

- (void) createNewAccount:(NSString *)nickname accountType:(NSInteger)accountType primaryPhoto:(UIImage *)primaryPhoto 
{ 
    NSString *accessToken = self.accessToken; 

    // Ensure none of the params are nil, otherwise it'll mess up our dictionary 
    if (!nickname) nickname = @""; 
    if (!accessToken) accessToken = @""; 

    NSDictionary *params = @{@"nickname": nickname, 
          @"type": [[NSNumber alloc] initWithInteger:accountType], 
          @"access_token": accessToken}; 
    NSLog(@"Creating new account %@", params); 

    [self POST:@"accounts" parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { 
     if (primaryPhoto) { 
      [formData appendPartWithFileData:UIImageJPEGRepresentation(primaryPhoto, 1.0) 
             name:@"primary_photo" 
            fileName:@"image.jpg" 
            mimeType:@"image/jpeg"]; 
     } 
    } success:^(NSURLSessionDataTask *task, id responseObject) { 
     NSLog(@"Created new account successfully"); 
    } failure:^(NSURLSessionDataTask *task, NSError *error) { 
     NSLog(@"Error: couldn't create new account: %@", error); 
    }]; 
} 

Метод 2:. пытались построить данные формы в самом блоке:

- (void) createNewAccount:(NSString *)nickname accountType:(NSInteger)accountType primaryPhoto:(UIImage *)primaryPhoto 
{ 
    // Ensure none of the params are nil, otherwise it'll mess up our dictionary 
    if (!nickname) nickname = @""; 
    NSLog(@"Creating new account %@", params); 

    [self POST:@"accounts" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { 
     [formData appendPartWithFormData:[nickname dataUsingEncoding:NSUTF8StringEncoding] name:@"nickname"]; 
     [formData appendPartWithFormData:[NSData dataWithBytes:&accountType length:sizeof(accountType)] name:@"type"]; 
     if (self.accessToken) 
      [formData appendPartWithFormData:[self.accessToken dataUsingEncoding:NSUTF8StringEncoding] name:@"access_token"]; 
     if (primaryPhoto) { 
      [formData appendPartWithFileData:UIImageJPEGRepresentation(primaryPhoto, 1.0) 
             name:@"primary_photo" 
            fileName:@"image.jpg" 
            mimeType:@"image/jpeg"]; 
     } 
    } success:^(NSURLSessionDataTask *task, id responseObject) { 
     NSLog(@"Created new account successfully"); 
    } failure:^(NSURLSessionDataTask *task, NSError *error) { 
     NSLog(@"Error: couldn't create new account: %@", error); 
    }]; 
} 

Использование любого из методов, когда HTTP-запрос попадает на сервер, нет данных POST или параметров строки запроса, а только заголовков HTTP.

Transfer-Encoding: Chunked 
Content-Length: 
User-Agent: MyApp/1.0 (iPhone Simulator; iOS 7.0.3; Scale/2.00) 
Connection: keep-alive 
Host: 127.0.0.1:5000 
Accept: */* 
Accept-Language: en;q=1, fr;q=0.9, de;q=0.8, zh-Hans;q=0.7, zh-Hant;q=0.6, ja;q=0.5 
Content-Type: multipart/form-data; boundary=Boundary+0xAbCdEfGbOuNdArY 
Accept-Encoding: gzip, deflate 

Любые мысли? Также размещена ошибка в AFNetworking's github repo.

+1

Я создал множественный запрос с 'AFHTTPRequestOperationManager', и он работал нормально, но когда я использовал почти идентичный' AFHTTPSessionManager', он терпит неудачу. Похоже, внутри 'AFHTTPSManManager' что-то не так. – Rob

+0

Это сделало трюк @Rob, оставив вопрос открытым, но это определенно пахнет ошибкой. –

+0

Вы пробовали смотреть на 'task.response'? Mine возвращает «statusCode» из 406 (но тот же запрос отлично от 'AFHTTPRequestOperationManager'). – Rob

ответ

4

Порывшись в это еще, оказывается, что при использовании NSURLSession в сочетании с setHTTPBodyStream, даже если запрос устанавливает Content-Length (который AFURLRequestSerialization делает в requestByFinalizingMultipartFormData), что заголовок не послан. Вы можете это подтвердить, сравнив allHTTPHeaderFields задания originalRequest и currentRequest. Я также подтвердил это Чарльзом.

Что интересно, Transfer-Encoding получает значение chunked (что является правильным, когда длина неизвестна).

Нижняя линия, это кажется проявлением выбора AFNetworking для использования setHTTPBodyStream, а не setHTTPBody (который не страдает от такого поведения), которые, в сочетании с NSURLSession результатов в этом поведении некорректных запросов.

Я думаю, что это связано с AFNetworking issue 1398.

8

Роб абсолютно прав, проблема, которую вы видите, связана с (теперь закрытой) issue 1398. Тем не менее, я хотел предоставить быстрый tl; dr на случай, если кто-то еще посмотрит.

Во-первых, вот фрагмент кода предоставляется gberginc on github, что вы можете моделировать загрузку файлов после того, как:

NSString* apiUrl = @"http://example.com/upload"; 

// Prepare a temporary file to store the multipart request prior to sending it to the server due to an alleged 
// bug in NSURLSessionTask. 
NSString* tmpFilename = [NSString stringWithFormat:@"%f", [NSDate timeIntervalSinceReferenceDate]]; 
NSURL* tmpFileUrl = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tmpFilename]]; 

// Create a multipart form request. 
NSMutableURLRequest *multipartRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" 
                            URLString:apiUrl 
                            parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) 
             { 
              [formData appendPartWithFileURL:[NSURL fileURLWithPath:filePath] 
                     name:@"file" 
                    fileName:fileName 
                    mimeType:@"image/jpeg" error:nil]; 
             } error:nil]; 

// Dump multipart request into the temporary file. 
[[AFHTTPRequestSerializer serializer] requestWithMultipartFormRequest:multipartRequest 
              writingStreamContentsToFile:tmpFileUrl 
                completionHandler:^(NSError *error) { 
                 // Once the multipart form is serialized into a temporary file, we can initialize 
                 // the actual HTTP request using session manager. 

                 // Create default session manager. 
                 AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; 

                 // Show progress. 
                 NSProgress *progress = nil; 
                 // Here note that we are submitting the initial multipart request. We are, however, 
                 // forcing the body stream to be read from the temporary file. 
                 NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:multipartRequest 
                                fromFile:tmpFileUrl 
                                progress:&progress 
                              completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) 
                           { 
                            // Cleanup: remove temporary file. 
                            [[NSFileManager defaultManager] removeItemAtURL:tmpFileUrl error:nil]; 

                            // Do something with the result. 
                            if (error) { 
                             NSLog(@"Error: %@", error); 
                            } else { 
                             NSLog(@"Success: %@", responseObject); 
                            } 
                           }]; 

                 // Add the observer monitoring the upload progress. 
                 [progress addObserver:self 
                    forKeyPath:@"fractionCompleted" 
                     options:NSKeyValueObservingOptionNew 
                     context:NULL]; 

                 // Start the file upload. 
                 [uploadTask resume]; 
                }]; 

а во-вторых, обобщить эту проблему (и почему вы должны использовать временный файл в качестве работы вокруг), это действительно два раза.

  1. Apple, считает заголовок длиной содержимого, чтобы находиться под его контролем, и когда поток HTTP, тело набора для библиотек NSURLRequest от Apple будет установить кодировку фрагментированного, а затем отказаться от этого заголовка (и, таким образом, очистки любого content- значение длины множество AFNetworking)
  2. сервера загрузка бьет не поддерживает Transfer-Encoding: Chunked (например, S3)

Но получается, если вы загружаете запрос из файла (из-за общий запрос размер известен заранее), библиотеки Apple правильно настроят заголовок длины содержимого. Сумасшедший?

0

я бег в этой проблему сам, и пытаюсь как методы и предложенный метод здесь ...

Оказывается, это было так же просто, как изменение прилагаемых данных «имя» для «файла» вместо переменной имени файла.

Убедитесь, что ваш ключ данных совпадает, или вы увидите тот же самый признак пустого набора данных.