AFNetworking 2.0多部分请求正文空白

 手机用户2502903715 发布于 2023-02-06 11:17

与此问题类似.

使用AFNetworking 2.0.3并尝试使用AFHTTPSessionManager的POST + constructBodyWithBlock上传图像.由于原因未知,似乎HTTP邮件正文在向服务器发出请求时始终为空.

我下面是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 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 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的github回购中发布了一个错误.

2 个回答
  • 罗布是绝对正确的,你看到的问题与(现已结束)问题1398有关.但是,我想提供一个快速的tl; dr,以防其他人在寻找.

    首先,这是githgin在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];
                                                        }];
    

    其次,要总结问题(以及为什么必须使用临时文件作为解决方法),它实际上是双重的.

      Apple认为内容长度标头在其控制之下,并且当为NSURLRequestApple的库设置HTTP正文流时,将编码设置为Chunked然后放弃该标头(从而清除AFNetworking设置的任何内容长度值)

      上传的服务器不支持Transfer-Encoding: Chunked(例如S3)

    但事实证明,如果您从文件上传请求(因为提前知道总请求大小),Apple的库将正确设置内容长度标头.疯了吧?

    2023-02-06 11:18 回答
  • 挖掘到这个进一步,看来,当您使用NSURLSession与配合setHTTPBodyStream,即使请求集Content-Length(这AFURLRequestSerialization确实在requestByFinalizingMultipartFormData),这个头是没有得到发送.您可以通过比较allHTTPHeaderFields任务originalRequest和来确认这一点currentRequest.我也向查尔斯证实了这一点.

    有趣的是Transfer-Encoding被设置为chunked(当长度未知时,这是正确的).

    最重要的是,这似乎是AFNetworking选择使用setHTTPBodyStream而不是setHTTPBody(不受此行为影响)的一种表现形式,当与NSURLSession这种不正确的请求行为相结合时.

    我认为这与AFNetworking 问题1398有关.

    2023-02-06 11:18 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有