Multipart/form-data

前言

在iOS开发中很多时候需要会涉及到上传图片等服务器交互的操作 , 这基本上全部都会使用Multipart/form-data的请求方式来完成上传 , 这需要我们去严格按照规范的格式来组装请求体 , 每一个换行每一个空格都是不可忽略的 , 虽然这些操作很繁琐 但平时我们使用的第三方网络库都会自动帮助我们完成这些处理 , 那这里为什么还要去讲如何去做呢? 答案很简单 , 因为我们不可能在任何时候都有第三方网络库去使用 , 比如你需要写一个静态库.

简介

Multipart/form-data的基础方法是POST , 也就是说是由POST方法来组合实现的.
Multipart/form-data与POST方法的不同之处在于请求头和请求体.
Multipart/form-data的请求头必须包含一个特殊的头信息 : Content-Type , 且其值也必须规定为multipart/form-data , 同时还需要规定一个内容分割符用于分割请求体中的多个POST的内容 , 如文件内容和文本内容自然需要分割开来 , 不然接收方就无法正常解析和还原这个文件了.
Multipart/form-data的请求体也是一个字符串 , 不过和post的请求体不同的是它的构造方式 , post是简单的name=value值连接 , 而Multipart/form-data则是添加了分隔符等内容的构造体.

请求的头部信息如下:

Content-Type: multipart/form-data; boundary=你的自定义boundary

下面我们来大致看一下Multipart/form-data请求体的格式:

--LEE你好帅
Content-Disposition: form-data; name="UserID"  
  
lee1994
--LEE你好帅
Content-Disposition: form-data; name="imageName"; filename="imageName.png"  
Content-Type: image/png  

...contents of image.png...  
--LEE你好帅--  

其主要格式就是这样子的 , 我来讲一下每一部分都是什么含义.

我们先说一下请求头部的信息 boundary这个参数是分界线的意思 , 也就是说你在请求头中指定分界线为: LEE你好帅 , 那么请求体中凡是LEE你好帅这样的字段都会被视为分界线 , 这个分界线参数具体是什么你可以随意自定义 , 我这里只是举个例子 ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄ 你如果要用也可以 , 建议设置的复杂一些 避免和请求体中其他字段发生重复的现象.

--+分隔符(boundary) = --LEE你好帅 :分界线 (分界线后要有换行)

接着往下看

Content-Disposition: form-data; name="UserID"  

lee1994

其中name="你的参数名" 参数名设置好后下面就是参数的值了 , 也就是lee1994 , 这里有一点非常要注意 参数名这行后面一定要有2个换行 然后才是参数的值 , 这点非常重要 一定要严格按照这个格式去写 , 别问我为什么 , 官方规定.

上面所设置的参数为文本类型 , 下面我们讲一下文件类型如何去写 这里我用图片文件举例.

分界线不用说了 每个参数前面都要有分界线

Content-Disposition: form-data; name="imageName"; filename="imageName.png"  
Content-Type: image/png  

...contents of image.png...

其中name="参数名" filename="文件名" 其中参数名这个要和接收方那边相对应 正常开发中可以去问服务器那边 , 文件名是说在服务器端保存成文件的名字 , 这个参数然并卵 , 因为一般服务端会按照他们自己的要求去处理文件的存储.

下一行是指定类型 , 我这里示例中写的是PNG图片类型 , 这个可以根据你的实际需求的写.

再往下就是设置参数值了 , 和文本类型一样 这里同样要有2个换行

...contents of image.png... 这里是image.png的二进制内容 , 如:

<ffd8ffe0 00104a46 49460001 01000048 00480000 ffe1004c 45786966 00004d4d 002a0000 00080002 01120003 00000001 00010000 87690004 00000001 00000026 00000000 0002a002 00040000 00010000 0280a003......

整个请求体拼装完成后 , 最后会以--分隔符--结尾 , 表示请求体结束 , 也就是--LEE你好帅--.

请求体格式纯注释参照:

--分隔符(boundary)[换行]
Content-Disposition: form-data; name="参数名"[换行] 
[换行]
参数值[换行]
--分隔符(boundary)[换行]
Content-Disposition: form-data; name="图片名"; filename="图片文件名"[换行]
Content-Type: 类型[换行]
[换行]
图片文件的二进制内容[换行]
--分隔符(boundary)--

多参数多文件参照:

--分隔符(boundary)
Content-Disposition: form-data; name="参数名1"  

参数值1
--分隔符(boundary)
Content-Disposition: form-data; name="参数名2"  

参数值2
--分隔符(boundary)
Content-Disposition: form-data; name="参数名3"  

参数值3
--分隔符(boundary)
Content-Disposition: form-data; name="图片名1"; filename="图片文件名1"  
Content-Type: 类型  

图片文件的二进制内容1
--分隔符(boundary)
Content-Disposition: form-data; name="图片名2"; filename="图片文件名2"  
Content-Type: 类型  

图片文件的二进制内容2
--分隔符(boundary)
Content-Disposition: form-data; name="图片名3"; filename="图片文件名3"  
Content-Type: 类型  

图片文件的二进制内容3
--分隔符(boundary)--

看到这里你大概已经了解了我们需要组装一个怎样的请求参数结构体 , 最后我附上一段Objective - C 的上传图片代码段:

//初始化请求

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30.0f];

//================组装Body=================

//分界线的标识符

NSString *Boundary = [NSString stringWithFormat:@"LEE%u%u" , arc4random() , arc4random()];

//分界线 --LEE

NSString *LEEboundary = [[NSString alloc]initWithFormat:@"--%@" , Boundary];

//结束符 LEE--

NSString *endLEEboundary = [[NSString alloc]initWithFormat:@"%@--" , LEEboundary];

//得到图片的data

NSData* imagData = [NSData dataWithData:image];

//HTTP body的字符串

NSMutableString *body=[[NSMutableString alloc]init];

//参数的集合的所有key的集合

NSArray *keys= [parameters allKeys];

//遍历keys 组装文本类型参数

for (NSString *key in keys) {
    
    //添加分界线,换行
    
    [body appendFormat:@"%@\r\n",LEEboundary];
    
    //添加字段名称,换2行
    
    [body appendFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key];
    
    //添加字段的值
    
    [body appendFormat:@"%@\r\n",[parameters objectForKey:key]];

}

//添加分界线,换行

[body appendFormat:@"%@\r\n",LEEboundary];

//组装文件类型参数

[body appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n" , name , fileName];

//声明上传文件的格式 换2行

[body appendFormat:@"Content-Type: image/jpeg\r\n\r\n"];

//声明结束符:--LEE--

NSString *end=[[NSString alloc]initWithFormat:@"\r\n%@",endLEEboundary];

//声明requestData,用来放入http body

NSMutableData *requestData=[NSMutableData data];

//将body字符串转化为UTF8格式的二进制

[requestData appendData:[body dataUsingEncoding:NSUTF8StringEncoding]];

//将image的data加入

[requestData appendData:imagData];

//加入结束符--LEE--

[requestData appendData:[end dataUsingEncoding:NSUTF8StringEncoding]];

//设置HTTP body

[request setHTTPBody:requestData];

//================END组装Body=================

//设置HTTPHeader中Content-Type的值

NSString *content=[[NSString alloc]initWithFormat:@"multipart/form-data; boundary=%@" , Boundary];

//设置HTTPHeader

[request setValue:content forHTTPHeaderField:@"Content-Type"];

//设置Content-Length

[request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[requestData length]] forHTTPHeaderField:@"Content-Length"];

//设置请求方式

[request setHTTPMethod:@"POST"];

/**
 NSURLSessionConfiguration(会话配置)
 
 defaultSessionConfiguration;       // 磁盘缓存,适用于大的文件上传下载
 ephemeralSessionConfiguration;     // 内存缓存,以用于小的文件交互,GET一个头像
 backgroundSessionConfiguration:(NSString *)identifier; // 后台上传和下载
 */

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];

NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:self.upLoadQueue];

[[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

     //请求完成的处理巴拉巴拉..
    
}] resume];

注意:

  • 文件类型参数中 name="参数名" 一定要和服务端对应 , 开发的时候 , 可以问服务端人员.

  • 上传文件的数据部分使用二进制数据 (NSData)拼接.

  • 上边界部分和下边界部分的字符串 , 最后都要转换成二进制数据(NSData) , 和文件部分的二进制数据拼接在一起 , 作为请求体发送给服务器(完整的请求体格式上面说过).

  • 每一行末尾需要有一定的 \r\n

  • 有些服务器可以直接使用 \n , 但是新浪微博如果使用 \n 上传文件 , 服务器会返回"没有权限"的错误~ 所以还是建议使用 \r\n.

  • NSURLSession做上传请求时可以通过代码方法去获取上传的进度.

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                                    didSendBodyData:(int64_t)bytesSent
                                     totalBytesSent:(int64_t)totalBytesSent
                           totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
    
    -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
    
        // bytesSent                 totalBytesSent                totalBytesExpectedToSend
    
        // 发送字节(本次发送的字节数)    总发送字节数(已经上传的字节数)     总希望要发送的字节(文件大小)
    
        NSLog(@"上传进度: \n发送字节:%lld - 总发送字节数:%lld - 总大小:%lld ", bytesSent, totalBytesSent, totalBytesExpectedToSend);
    
    }
    

总结:

上面所讲的请求方式虽然不常用 但我还是要说 , 第三方的库往往会给我带来方便 , 但不要过渡依赖于第三方 , 往往了解一些原始的东西 会给你的开发生涯带来意向不到的效果 .

我是LEE , 一枚有信仰的果粉Coder , 如果喜欢记得关注哦 亲 ~ 么了个哒 

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容