Objective C 实现 http post multipart/form-data 类型的请求

Objective C 2020-03-11 阅读 237 评论 0

下面介绍使用 Objective C 实现 http post 方法,发送 Content-Type 为 multipart/form-data 格式的数据,支持文件、图片的上传,模拟了 html 的表单数据提交。在 IOS 项目下获取文件的 mime type,需在 Xcode 的项目: Build Phases -> Link Binary With Libraries,添加MobileCoreServices.framework框架。可以参考 Objective C 根据文件的扩展名获取 MIME Type。添加文件 mime type,主要是为了告知后端服务器关于此文件的类型。

示例代码

文件 HttpPostMultipart.h

#import <Foundation/Foundation.h>

/// 实现Http Multipart 工具类
@interface HttpPostMultipart:NSObject
-(id)initWithUrl:(NSString *)urlString;

@property(nonatomic) NSMutableURLRequest *request;
@property(nonatomic) NSString *urlString;
@property(nonatomic) NSMutableDictionary *headers;
@property(nonatomic) NSMutableDictionary *fields;
@property(nonatomic) NSMutableDictionary *files;

/// 添加请求头
/// @param key 请求头key
/// @param value 请求头值
-(void)addHeader:(NSString *)key value:(NSString *)value;

/// 添加参数字段
/// @param key 请求字段 key
/// @param value 请求字段 value
-(void)addFormField:(NSString *)key value:(NSString *)value;

/// 添加文件
/// @param key 传输文件的key
/// @param path 文件路径
-(void)addFormFile:(NSString *)key path:(NSString *)path;

/// 执行请求
/// @param callback 回调块
- (void)finish:(void(^)(NSData *, NSURLResponse *, NSError *))callback;

@end

文件 HttpPostMultipart.m

#import "HttpPostMultipart.h"
#import <MobileCoreServices/MobileCoreServices.h>

@implementation HttpPostMultipart

-(id)initWithUrl:(NSString *)urlString{
  self.request = [[NSMutableURLRequest alloc]init];
  self.urlString = urlString;
  self.headers = [[NSMutableDictionary alloc]init];
  self.fields = [[NSMutableDictionary alloc]init];
  self.files = [[NSMutableDictionary alloc]init];
  return self;
}

-(void)addHeader:(NSString *)key value:(NSString *)value{
  [self.headers setObject:value forKey:key];
}

-(void)addFormField:(NSString *)key value:(NSString *)value{
  [self.fields setObject:value forKey:key];
}

-(void)addFormFile:(NSString *)key path:(NSString *)path{
  [self.files setObject:path forKey:key];
}

- (void)finish:(void (^)(NSData *, NSURLResponse *, NSError *))callback{
  NSURL *url = [[NSURL alloc]initWithString:self.urlString];
  [self.request setURL:url];
  [self.request setHTTPMethod:@"POST"];
  // 增加请求头
  for (NSString *key in self.headers){
    NSString *value = self.headers[key];
    [self.request setValue:value forHTTPHeaderField:key];
  }
  // 增加 Content-Type 头
  NSString *boundary = @"unique-consistent-string";
  NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
  [self.request setValue:contentType forHTTPHeaderField:@"Content-Type"];
  // 增加 Post 参数字段
  NSMutableData *body = [NSMutableData data];
  for (NSString *key in self.fields){
    NSString *value = self.fields[key];
    // add params (all params are strings)
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"%@\r\n", value] dataUsingEncoding:NSUTF8StringEncoding]];
  }
  // 添加文件
  for (NSString *key in self.files){
    NSString *path = self.files[key];
    NSString *fileName = [path lastPathComponent];
    
    NSString *fileExtension = [path pathExtension];
    NSString *contentType = [self getMimeType:fileExtension];
    
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n", key, fileName] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[NSData dataWithContentsOfFile:path]];
    [body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  }
  
  if ([body length] > 0) {
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    // setting the body of the post to the reqeust
    [self.request setHTTPBody:body];
    
    // set the content-length
    NSString *postLength = [NSString stringWithFormat:@"%ld", [body length]];
    [self.request setValue:postLength forHTTPHeaderField:@"Content-Length"];
  }
  
  void (^completionHandler)(NSData *, NSURLResponse *, NSError *) = ^(NSData *data, NSURLResponse *response, NSError *error) {
    if (callback) {
      callback(data, response, error);
    }
  };
  
  [[[NSURLSession sharedSession] dataTaskWithRequest:self.request completionHandler:completionHandler] resume];
}

/// 获取文件的 mime type
/// @param fileExtension 文件扩展名
- (NSString *)getMimeType:(NSString *)fileExtension{
  NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
  NSString *mimeType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
  return mimeType;
}

@end

调用示例

NSString *url = @"http://localhost";
HttpPostMultipart *request = [[HttpPostMultipart alloc]initWithUrl:url];
// 添加 user-agent
[request addHeader:@"User-Agent" value:@"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"];
// 添加 请求参数
[request addFormField:@"username" value:@"test"];
// 添加 图片
[request addFormFile:@"pictures" path:@"/Users/apple/Downloads/201228bli.bmp"];

void (^completionHandler)(NSData *, NSURLResponse *, NSError *) = ^(NSData *data, NSURLResponse *response, NSError *error) {
    if (data == nil || data.length == 0 || response == nil || [(NSHTTPURLResponse *)response statusCode] != 200) {
        NSLog(@"error");
        return;
    }
    NSLog(@"success: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
};
[request finish:completionHandler];
最后更新 2020-03-18
MIP.watch('startSearch', function (newVal, oldVal) { if(newVal) { var keyword = MIP.getData('keyword'); console.log(keyword); // 替换当前历史记录,新增 MIP.viewer.open('/s/' + keyword, {replace: true}); setTimeout(function () { MIP.setData({startSearch: false}) }, 1000); } }); MIP.watch('goHome', function (newVal, oldVal) { MIP.viewer.open('/', {replace: false}); });