使用 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】:流程如下:
使用userName
、userEmail
和userPassword
参数创建字典。
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 发布多部分/表单数据的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Power Query 的 Web.Contents 发布多部分/表单数据
使用 Retrofit 2.0 发布多部分表单数据,包括图像