使用 Objective-C 发布多部分/表单数据

Posted

技术标签:

【中文标题】使用 Objective-C 发布多部分/表单数据【英文标题】:POST multipart/form-data with Objective-C 【发布时间】:2014-08-06 16:17:10 【问题描述】:

所以这个 html 代码为我提交了正确格式的数据。

<form action="https://www.example.com/register.php" method="post" enctype="multipart/form-data">
    Name: <input type="text" name="userName"><BR />
    Email: <input type="text" name="userEmail"><BR />
    Password: <input type="text" name="userPassword"><BR />
    Avatar: <input type="file" name="avatar"><BR />
    <input type="submit">
</form>

我查看了很多关于如何在 ios 上进行多部分/表单数据 POST 的文章,但没有一个真正解释如果有正常参数以及文件上传该怎么做。

能否请您帮我提供在 Obj-C 中发布此内容的代码?

谢谢!

【问题讨论】:

【参考方案1】:

流程如下:

    使用userNameuserEmailuserPassword 参数创建字典。

    NSDictionary *params = @@"userName"     : @"rob",
                             @"userEmail"    : @"rob@email.com",
                             @"userPassword" : @"password";
    

    确定图片的路径:

    NSString *path = [[NSBundle mainBundle] pathForResource:@"avatar" ofType:@"png"];
    

    创建请求:

    NSString *boundary = [self generateBoundaryString];
    
    // configure the request
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:@"POST"];
    
    // set content type
    
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request setValue:contentType forHTTPHeaderField: @"Content-Type"];
    
    // create body
    
    NSData *httpBody = [self createBodyWithBoundary:boundary parameters:params paths:@[path] fieldName:fieldName];
    

    这是上面用来构建请求正文的方法:

    - (NSData *)createBodyWithBoundary:(NSString *)boundary
                            parameters:(NSDictionary *)parameters
                                 paths:(NSArray *)paths
                             fieldName:(NSString *)fieldName 
        NSMutableData *httpBody = [NSMutableData data];
    
        // add params (all params are strings)
    
        [parameters enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) 
            [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", parameterKey] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"%@\r\n", parameterValue] dataUsingEncoding:NSUTF8StringEncoding]];
        ];
    
        // add image data
    
        for (NSString *path in paths) 
            NSString *filename  = [path lastPathComponent];
            NSData   *data      = [NSData dataWithContentsOfFile:path];
            NSString *mimetype  = [self mimeTypeForPath:path];
    
            [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, filename] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimetype] dataUsingEncoding:NSUTF8StringEncoding]];
            [httpBody appendData:data];
            [httpBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
        
    
        [httpBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
        return httpBody;
    
    

    以上使用了以下实用方法:

    @import MobileCoreServices;    // only needed in iOS
    
    - (NSString *)mimeTypeForPath:(NSString *)path 
        // get a mime type for an extension using MobileCoreServices.framework
    
        CFStringRef extension = (__bridge CFStringRef)[path pathExtension];
        CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extension, NULL);
        assert(UTI != NULL);
    
        NSString *mimetype = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType));
        assert(mimetype != NULL);
    
        CFRelease(UTI);
    
        return mimetype;
    
    
    - (NSString *)generateBoundaryString 
        return [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
    
    

    然后提交请求。这里有很多很多选择。

    例如,如果使用NSURLSession,您可以创建NSURLSessionUploadTask

    NSURLSession *session = [NSURLSession sharedSession];  // use sharedSession or create your own
    
    NSURLSessionTask *task = [session uploadTaskWithRequest:request fromData:httpBody completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
        if (error) 
            NSLog(@"error = %@", error);
            return;
        
    
        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"result = %@", result);
    ];
    [task resume];
    

    或者你可以创建一个NSURLSessionDataTask:

    request.HTTPBody = httpBody;
    
    NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
        if (error) 
            NSLog(@"error = %@", error);
            return;
        
    
        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"result = %@", result);
    ];
    [task resume];
    

    以上假设服务器只是返回文本响应。如果服务器返回 JSON 会更好,在这种情况下你会使用 NSJSONSerialization 而不是 NSString 方法 initWithData

    同样,我正在使用上面NSURLSession 的完成块再现,但也可以随意使用更丰富的基于委托的再现。但这似乎超出了这个问题的范围,所以我把它留给你。

但希望这能说明这个想法。


如果我没有指出这一点,我会失职,比上面容易得多,你可以使用AFNetworking,重复上面的步骤 1 和 2,然后只需调用:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer]; // only needed if the server is not returning JSON; if web service returns JSON, remove this line
NSURLSessionTask *task = [manager POST:urlString parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) 
    NSError *error;
    if (![formData appendPartWithFileURL:[NSURL fileURLWithPath:path] name:@"avatar" fileName:[path lastPathComponent] mimeType:@"image/png" error:&error]) 
        NSLog(@"error appending part: %@", error);
    
  progress:nil success:^(NSURLSessionTask *task, id responseObject) 
    NSLog(@"responseObject = %@", responseObject);
 failure:^(NSURLSessionTask *task, NSError *error) 
    NSLog(@"error = %@", error);
];

if (!task) 
    NSLog(@"Creation of task failed.");

【讨论】:

请注意,您也可以使用 NSUUID 生成 UUID:NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]]; 是的,如果您不需要支持 6.0 之前的 iOS 版本,这是一个非常好的方法。 嗨,你的回答很有用,但我只是想问一下 fieldName NSString 到底是什么,因为我没有看到你在代码的任何地方创建它,我不确定我是什么'我应该在那里使用 fieldName 是您的服务器代码在识别上传时要查找的任何字段名称。例如,如果您的服务器代码是用 PHP 编写的,那么这就是服务器代码在其 $_FILES 引用中使用的任何字段名称。在原始问题中包含的示例中,此字段名称为 avatar 非常完整的答案。【参考方案2】:

在 Objective-C 中使用 multipart 或 form-data 发布多个图像

-(void)multipleimageandstring

    NSString *urlString=@"URL NAME";

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init] ;
    [request setURL:[NSURL URLWithString:urlString]];
    [request setHTTPMethod:@"POST"];

    NSMutableData *body = [NSMutableData data];

    NSString *boundary = @"---------------------------14737809831466499882746641449";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request addValue:contentType forHTTPHeaderField:@"Content-Type"];

    // file
    float low_bound = 0;
    float high_bound =5000;
    float rndValue = (((float)arc4random()/0x100000000)*(high_bound-low_bound)+low_bound);//image1
    int intRndValue = (int)(rndValue + 0.5);
   NSString *str_image1 = [@(intRndValue) stringValue];

    UIImage *chosenImage1=[UIImage imageNamed:@"Purchase_GUI_curves-12 copy.png"];

    NSData *imageData = UIImageJPEGRepresentation(chosenImage1, 90);
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"files\"; filename=\"%@.png\"\r\n",str_image1] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[NSData dataWithData:imageData]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];





    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"name\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Nilesh" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"apipassword\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithString:app.password] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"adminId\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithString:app.adminId] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];

    // close form
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    // set request body
    [request setHTTPBody:body];

    //return and test
    NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];

    NSLog(@"%@", returnString);


【讨论】:

【参考方案3】:

尝试将其用于具有不同 mime 类型的视频和图像数据。

NSDictionary *param;

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

// 1. Create `AFHTTPRequestSerializer` which will create your request.
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
NSMutableURLRequest *request;

NSData *fileData;
if ([objDoc.url containsString:@".mp4"]) 
    manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:@"application/json"];
    [serializer setValue:@"video/mp4" forHTTPHeaderField:@"Content-Type"];
    manager.requestSerializer = serializer;


// 2. Create an `NSMutableURLRequest`.

NSLog(@"filename =%@",objDoc.url);
request= [serializer multipartFormRequestWithMethod:@"POST" URLString:strUrl parameters:param constructingBodyWithBlock:^(id<AFMultipartFormData> formData) 

    if ([objDoc.url containsString:@".mp4"]) 
        [formData appendPartWithFileData:fileData
                                    name:@"File"
                                fileName:@"video.mp4"
                                mimeType:@"video/mp4"];

    else
        [formData appendPartWithFileData:fileData
                                    name:@"File"
                                fileName:@"image.jpeg"
                                mimeType:@"image/jpeg"];
    

 error:nil];

// 3. Create and use `AFHTTPRequestOperationManager` to create an `AFHTTPRequestOperation` from the `NSMutableURLRequest` that we just created.

self.objeDocument.isUploading = [NSNumber numberWithInt:1];

self.operation = [manager HTTPRequestOperationWithRequest:request
                                                  success:^(AFHTTPRequestOperation *operation, id responseObject) 

                                                      NSLog(@"Success %@", responseObject);
                                                   failure:^(AFHTTPRequestOperation *operation, NSError *error) 
                                                      UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Error!" message:@"The document attached has failed to upload." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
                                                      [alert show];
                                                      [self.operation cancel];
                                                      NSLog(@"Failure %@", error.description);
                                                  ];


// 4. Set the progress block of the operation.
[self.operation setUploadProgressBlock:^(NSUInteger __unused bytesWritten,
                                         long long totalBytesWritten,
                                         long long totalBytesExpectedToWrite) 
    NSLog(@"Wrote %lld/%lld", totalBytesWritten, totalBytesExpectedToWrite);
    float progress = (float)totalBytesWritten/(float)totalBytesExpectedToWrite;


];

// 5. Begin!
[self.operation start];

【讨论】:

【参考方案4】:

我为此苦苦挣扎了一段时间,如果您正在寻找上传多张图片或任何其他文件类型,您可以使用 AFNetworking 3.0 执行以下操作

NSDictionary *params = @key        : value,
                            ..... etc
                         ;

 NSString *urlString = @"http://..... your endpoint url";

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer]; // only needed if the server is not returning JSON;  

NSURLSessionTask *task = [manager POST:urlString parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) 
    for (int x = 0 ; x< contentArray.count; x++) 
        AttachmentsModel *model = contentArray[x];

        if(model.type == ImageAttachmentType)
            [formData appendPartWithFileData:model.data name:model.name fileName:model.fileName mimeType:model.mimeType];

        else if(model.type == AudioAttachmentType)
            NSURL *urlVideoFile = [NSURL fileURLWithPath:model.path];
            [formData appendPartWithFileURL:urlVideoFile name:model.name fileName:model.fileName mimeType:model.mimeType error:nil];
        else
            [formData appendPartWithFileURL:model.url name:model.name fileName:model.fileName mimeType:model.mimeType error:nil];

        

    
  progress:nil success:^(NSURLSessionTask *task, id responseObject) 
    [Utility stopLoading];

     NSString *result = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
    NSLog(@"result = %@", result);

    id json = [NSJSONSerialization JSONObjectWithData:responseObject options:0 error:nil];


    if (block) 

        //your response comes here
    

 failure:^(NSURLSessionTask *task, NSError *error) 
    NSLog(@"error = %@", error);

];

if (!task) 
    NSLog(@"Creation of task failed.");

这是我的 AttachmentsModel 的样子:

//  AttachmentsModel.h

typedef enum AttachmnetType
    ImageAttachmentType,
    AudioAttachmentType,
    VideoAttachmentType
 AttachmnetType;

@interface AttachmentsModel : NSObject

@property (strong, nonatomic) NSString *path;
@property (strong, nonatomic) NSData *data;
@property (strong, nonatomic) NSString *mimeType;
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *fileName;
@property (strong, nonatomic) NSURL *url;

【讨论】:

以上是关于使用 Objective-C 发布多部分/表单数据的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 fetch 发布多部分表单数据?

发布多部分表单数据问题

如何使用 Power Query 的 Web.Contents 发布多部分/表单数据

使用 Retrofit 2.0 发布多部分表单数据,包括图像

如何在 Jetty HttpClient 中进行多部分/表单数据发布

API Gateway - 发布多部分/表单数据