使用 AFNetworking 的多部分 PUT 请求

Posted

技术标签:

【中文标题】使用 AFNetworking 的多部分 PUT 请求【英文标题】:multipart PUT request using AFNetworking 【发布时间】:2015-05-22 03:40:32 【问题描述】:

ios 上使用 AFNetworking 对多部分 PUT 请求进行编码的正确方法是什么? (仍然是 Objective-C,不是 Swift)

我看起来好像 AFNetworking 可以做多部分 POST 但不能做 PUT,有什么解决方案?

谢谢

【问题讨论】:

【参考方案1】:

您可以使用 multipartFormRequestWithMethod 创建包含所需数据的多部分 PUT 请求。

例如,在 AFNetworking v3.x 中:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];

NSError *error;
NSURLRequest *request = [manager.requestSerializer multipartFormRequestWithMethod:@"PUT" URLString:@"http://example.com/rest/api/" parameters:@@"foo" : @"bar" constructingBodyWithBlock:^(id<AFMultipartFormData> formData) 
    NSString *value = @"qux";
    NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
    [formData appendPartWithFormData:data name:@"baz"];
 error:&error];

NSURLSessionDataTask *task = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) 
    if (error) 
        NSLog(@"%@", error);
        return;
    

    // if you want to know what the statusCode was

    if ([response isKindOfClass:[NSHTTPURLResponse class]]) 
        NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
        NSLog(@"statusCode: %ld", statusCode);
    

    NSLog(@"%@", responseObject);
];
[task resume];

如果是AFNetworking 2.x,可以使用AFHTTPRequestOperationManager

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

NSError *error;
NSURLRequest *request = [manager.requestSerializer multipartFormRequestWithMethod:@"PUT" URLString:@"http://example.com/rest/api/" parameters:@@"foo" : @"bar" constructingBodyWithBlock:^(id<AFMultipartFormData> formData) 
    NSString *value = @"qux";
    NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
    [formData appendPartWithFormData:data name:@"baz"];
 error:&error];

AFHTTPRequestOperation *operation = [manager HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) 
    NSLog(@"%@", responseObject);
 failure:^(AFHTTPRequestOperation *operation, NSError *error) 
    NSLog(@"%@", error);
];

[manager.operationQueue addOperation:operation];

已经说明了如何创建这样的请求,值得注意的是服务器可能无法解析它们。值得注意的是,php 解析多部分 POST 请求,但不解析多部分 PUT 请求。

【讨论】:

尝试了上面的代码,但出现以下错误“image”:[“提交的数据不是文件。检查表单上的编码类型。” ] 您使用的是appendPartWithFormData 还是appendPartWithFileData?听起来你想要后者。 @Rob 我已经实现了您的解决方案,但我将字节数据作为 responseObject。你能在这里解决这个问题吗:***.com/questions/40424323/… @Rob 请问如何从会话任务中获取返回的状态码? @Llg - 它是response 对象的一部分。所以,检查它是否是一个NSHTTPURLResponse 对象,如果是,抓住它的statusCode。请参阅上面的修改示例。【参考方案2】:

这是适用于我的 Afnetworking 3.0 和 Swift 的代码。我知道它的旧线程,但可能对某人很方便!

    let manager: AFHTTPSessionManager = AFHTTPSessionManager()

    let URL = "\(baseURL)\(url)"        

    let request: NSMutableURLRequest = manager.requestSerializer.multipartFormRequestWithMethod("PUT", URLString: URL, parameters: parameters as? [String : AnyObject], constructingBodyWithBlock: (formData: AFMultipartFormData!) -> Void in
        formData.appendPartWithFileData(image!, name: "Photo", fileName: "photo.jpg", mimeType: "image/jpeg")
        , error: nil)

    manager.dataTaskWithRequest(request)  (response, responseObject, error) -> Void in

        if((error == nil)) 
            print(responseObject)
            completionHandler(responseObject as! NSDictionary,nil)
        
        else 
            print(error)
            completionHandler(nil,error)
        

        print(responseObject)
        .resume()

【讨论】:

【参考方案3】:

你可以创建一个使用AFHTTPRequestSerialization的多部分表单请求方法构造的NSURLRequest

NSString *url = [[NSURL URLWithString:path relativeToURL:manager.baseURL] absoluteString];
id block = ^(id<AFMultipartFormData> formData) 
    [formData appendPartWithFileData:media
                                name:@"image"
                            fileName:@"image"
                            mimeType:@"image/jpeg"];
;

NSMutableURLRequest *request = [manager.requestSerializer
                                multipartFormRequestWithMethod:@"PUT"
                                URLString:url
                                parameters:nil
                                constructingBodyWithBlock:block
                                error:nil];

[manager HTTPRequestOperationWithRequest:request success:successBlock failure:failureBlock];

【讨论】:

【参考方案4】:

我想出了一个可以处理任何受支持方法的解决方案。这是 PUT 的解决方案,但您也可以将其替换为 POST。这是我在基础模型类上调用的类别中的一个方法。

    - (void)updateWithArrayOfFiles:(NSArray *)arrayOfFiles forKey:(NSString *)key params:(NSDictionary *)params success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure 

        id block = [self multipartFormConstructionBlockWithArayOfFiles:arrayOfFiles forKey:key failureBlock:failure];

        NSMutableURLRequest *request = [[self manager].requestSerializer
                                        multipartFormRequestWithMethod:@"PUT"
                                        URLString:self.defaultURL
                                        parameters:nil
                                        constructingBodyWithBlock:block
                                        error:nil];

       AFHTTPRequestOperation *operation = [[self manager] HTTPRequestOperationWithRequest:request success:success failure:failure];
       [operation start];
    

    #pragma mark multipartForm constructionBlock

    - (void (^)(id <AFMultipartFormData> formData))multipartFormConstructionBlockWithArayOfFiles:(NSArray *)arrayOfFiles forKey:(NSString *)key failureBlock:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure 
        id block = ^(id<AFMultipartFormData> formData) 
            int i = 0;
            // form mimeType
            for (FileWrapper *fileWrapper in arrayOfFiles) 
                NSString *mimeType = nil;
                switch (fileWrapper.fileType) 
                    case FileTypePhoto:
                        mimeType = @"image/jpeg";
                        break;
                    case FileTypeVideo:
                        mimeType = @"video/mp4";
                        break;
                    default:
                        break;
                
                // form imageKey
                NSString *imageName = key;
                if (arrayOfFiles.count > 1)
                    // add array specificator if more than one file
                    imageName = [imageName stringByAppendingString: [NSString stringWithFormat:@"[%d]",i++]];
                // specify file name if not presented
                if (!fileWrapper.fileName)
                    fileWrapper.fileName  = [NSString stringWithFormat:@"image_%d.jpg",i];
                NSError *error = nil;

                // Make the magic happen
                [formData appendPartWithFileURL:[NSURL fileURLWithPath:fileWrapper.filePath]
                                           name:imageName
                                       fileName:fileWrapper.fileName
                                       mimeType:mimeType
                                          error:&error];
                if (error) 
                    // Handle Error
                    [ErrorManager logError:error];
                    failure(nil, error);
                
            
        ;
        return block;
    

因为它使用 FileWrapper 接口

    typedef NS_ENUM(NSInteger, FileType) 
        FileTypePhoto,
        FileTypeVideo,
    ;


@interface FileWrapper : NSObject

@property (nonatomic, strong) NSString *filePath;
@property (nonatomic, strong) NSString *fileName;
@property (assign, nonatomic) FileType fileType;

@end

【讨论】:

【参考方案5】:

对于 RAW 正文:

NSData *data = someData; NSMutableURLRequest *requeust = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[self getURLWith:urlService]]];

[reqeust setHTTPMethod:@"PUT"];
[reqeust setHTTPBody:data];
[reqeust setValue:@"application/raw" forHTTPHeaderField:@"Content-Type"];

NSURLSessionDataTask *task = [manager uploadTaskWithRequest:requeust fromData:nil progress:^(NSProgress * _Nonnull uploadProgress) 

 completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) 

];
[task resume];

【讨论】:

【参考方案6】:

.h

+ (void)PUT:(NSString *)URLString
 parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
   progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
    success:(void (^)(NSURLResponse *response, id responseObject))success
    failure:(void (^)(NSURLResponse * response, NSError *error))failure
      error:(NSError *__autoreleasing *)requestError;

.m:

+ (void)PUT:(NSString *)URLString
 parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
    success:(void (^)(NSURLResponse * _Nonnull response, id responseObject))success
    failure:(void (^)(NSURLResponse * _Nonnull response, NSError *error))failure
error:(NSError *__autoreleasing *)requestError 

    NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer]
                                    multipartFormRequestWithMethod:@"PUT"
                                    URLString:(NSString *)URLString
                                    parameters:(NSDictionary *)parameters
                                    constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                    error:(NSError *__autoreleasing *)requestError];
    AFURLSessionManager *manager = [AFURLSessionManager sharedManager];//[AFURLSessionManager manager]
    NSURLSessionUploadTask *uploadTask;
    uploadTask = [manager uploadTaskWithStreamedRequest:(NSURLRequest *)request
                                               progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                      completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) 
                                          if (error) 
                                              failure(response, error);
                                           else 
                                              success(response, responseObject);
                                          
                                      ];

    [uploadTask resume];

就像经典的 afnetworking。 把它放到你的网络上 Util :)

【讨论】:

以上是关于使用 AFNetworking 的多部分 PUT 请求的主要内容,如果未能解决你的问题,请参考以下文章

AFNetworking 3.0 中带有图像和其他参数的多部分数据

AFNetworking 使用 Rails 进行 PUT 和删除

如何使用 AFNetworking 为“PUT”请求设置数据?

POST 和 PUT 请求 AFNetworking

AFNetworking:从文件发送图像

AFHttpSessionManager - 导致 org.codehaus.jackson.JsonParseException 的多部分 POST