NSURLSession上傳文件
(一)上傳單個文件的過程及原理總結(jié)
1.上傳文件需要注意兩點
第一點 : 請求頭里面的Content-Type
請求頭里面
Content-Type
的作用 : 告訴服務(wù)器我的客戶端的請求是干什么的,是做普通請求還是做文件上傳普通請求的
Content-Type
:Content-Type: application/x-www-form-urlencoded
文件上傳的
Content-Type
:Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryf3vccsnJe8EBoxHY
boundary
: 表示文件上傳時的文件分隔符;可以自定義;自定義分隔符時,前面四個中劃線可以省略,分隔符不能有中文;自定義
boundary
:boundary=zxc
自定義
boundary
之后的Content-Type
:Content-Type:multipart/form-data; boundary=zxc
第二點 : 請求體
------WebKitFormBoundaryf3vccsnJe8EBoxHY
Content-Disposition: form-data; name="userfile"; filename="car.jpg"
Content-Type: image/jpeg
------WebKitFormBoundaryf3vccsnJe8EBoxHY--
請求體解釋 :
- 第一行 : 文件的起始分割符,分割文件用的.以"--"開頭+分隔符(
----WebKitFormBoundaryf3vccsnJe8EBoxHY
),分隔符前面的"----"可以省略,比如--zxc
- 第二行 :
Content-Disposition
,文件上傳時需要處理的表單數(shù)據(jù).-
name="userfile"
: 服務(wù)器接收文件的字段名,由服務(wù)器提供,并告知前端開發(fā)者. -
filename="car.jpg"
: 文件保存到服務(wù)器上的文件名.可以自定義也可以直接使用文件的原始文件名
-
- 第三行 :
Content-Type: image/jpeg
,告訴服務(wù)器上傳的文件類型.如果不想告訴服務(wù)器文件的類型,可以使用Content-Type: application/octet-stream
- 第四行 : 單純的回車換行
\r\n
- 第五行 : 上傳的文件的二進制數(shù)據(jù)data
- 第六行 : 整個文件結(jié)束的分隔符,以"--"開頭,以"--"結(jié)尾,兩個"--"都不能少.比如
--zxc--
代碼實現(xiàn)文件上傳需要做的事情 :
1.設(shè)置請求頭
Content-Type: multipart/form-data; boundary=zxc
-
2.設(shè)置請求體,需要拼接請求體中的字符串和二進制數(shù)據(jù)
- 請求體數(shù)據(jù)結(jié)構(gòu)
--zxc\r\n Content-Disposition: form-data; name="userfile"; filename="car.jpg"\r\n Content-Type: image/jpeg\r\n \r\n data \r\n--zxc--
2.說明
- boundary : 表示文件的分隔符,分界線.非中文的字符組成.
- userfile:服務(wù)器字段名,開發(fā)的時候,可以咨詢后服務(wù)器端程序員.
- filename:將文件保存在服務(wù)器上的文件名稱.
- Content-Type:客戶端告訴服務(wù)器上傳文件的文件類型.
- text/plain
- image/jpg
- image/png
- image/gif
- text/html
- application/json
- application/octet-stream(8進制流)贼涩,如果不想告訴服務(wù)器具體的文件類型菲驴,可以使用這個 Content-Type.
- 注意:每一行末尾一定需要有
\r\n
.
(二)單個文件上傳實現(xiàn)
1.準備要上傳的數(shù)據(jù),調(diào)用文件上傳主方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 參數(shù)1
NSString *URLString = @"http://localhost/php/upload/upload.php";
// 參數(shù)2
NSString *serverFileName = @"userfile";
// 參數(shù)3
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"mm01.jpg" ofType:nil];
// 調(diào)用文件上傳主方法
[self uploadFileWithURLString:URLString serverFileName:serverFileName filePath:filePath];
}
2.文件上傳主方法
/**
* 文件上傳主方法
*
* @param URLString 文件上傳地址
* @param serverFileName 服務(wù)器接收文件的字段名,開發(fā)中由服務(wù)器那邊提供
* @param filePath 文件路徑,有了路徑就可以獲取到文件二進制數(shù)據(jù)和文件名
*/
- (void)uploadFileWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePath:(NSString *)filePath
{
// URL
NSURL *URL= [NSURL URLWithString:URLString];
// 可變請求
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL];
// 設(shè)置請求頭信息
[requestM setValue:@"multipart/form-data; boundary=itcast" forHTTPHeaderField:@"Content-Type"];
// 設(shè)置請求方法
requestM.HTTPMethod = @"POST";
// 設(shè)置請求
requestM.HTTPBody = [self getHTTPBodyWithServerFileName:serverFileName filePath:filePath];
// 發(fā)送請求實現(xiàn)圖片上傳
[[[NSURLSession sharedSession] dataTaskWithRequest:requestM completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 處理響應(yīng)
if (error == nil && data != nil) {
// 反序列化
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@",result);
} else {
NSLog(@"%@",error);
}
}] resume];
}
3.獲取請求體信息
/**
* 獲取請求體信息
*
* @param serverFileName 服務(wù)器接收文件的字段名
* @param filePath 文件路徑
*
* @return 返回請求體二進制信息
*/
- (NSData *)getHTTPBodyWithServerFileName:(NSString *)serverFileName filePath:(NSString *)filePath
{
// 定義dataM,拼接請求體的二進制信息
NSMutableData *dataM = [NSMutableData data];
// 拼接文件二進制前面的字符串
NSMutableString *stringM = [NSMutableString string];
// 拼接文件開始的分隔符
[stringM appendString:@"--itcast\r\n"];
// 拼接表單數(shù)據(jù)
[stringM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",serverFileName,[filePath lastPathComponent]];
// 拼接文件類型
[stringM appendString:@"Content-Type: image/jpeg\r\n"];
// 拼接單純的換行
[stringM appendString:@"\r\n"];
// 把這部分的字符串轉(zhuǎn)成二進制,拼接到dataM里面
[dataM appendData:[stringM dataUsingEncoding:NSUTF8StringEncoding]];
// 拼接文件的二進制數(shù)據(jù)
[dataM appendData:[NSData dataWithContentsOfFile:filePath]];
// 拼接文件結(jié)束的分隔符
NSString *end = @"\r\n--itcast--";
[dataM appendData:[end dataUsingEncoding:NSUTF8StringEncoding]];
return dataM.copy;
}
(三)多個文件上傳原理分析
- 原理 : 循環(huán)的將多個文件拼接起來,并用上傳文件的分隔符分割開.
1.上傳文件需要注意兩點
第一點 : Content-Type
- 第一點 :
Content-Type
,告訴服務(wù)器是發(fā)送文件.Content-Type: multipart/form-data; boundary=----WebKitFormBoundary0YVF2TB5DWTxe
第二點 : 請求體
- 請求體數(shù)據(jù)結(jié)構(gòu)
--itcast\r\n
Content-Disposition: form-data; name="userfile[]"; filename="mm01.jpg"\r\n
Content-Type: image/jpeg\r\n
\r\n
data\r\n
--itcast\r\n
Content-Disposition: form-data; name="userfile[]"; filename="mm02.jpg"\r\n
Content-Type: image/jpeg\r\n
\r\n
data\r\n
--itcast\r\n
Content-Disposition: form-data; name="status"\r\n
\r\n
今天和女神的男神在一起!好開心!\r\n
--itcast--
2.說明
- 第一個文件開始分隔符前有無
\r\n
是沒有影響的. - 上傳多個文件時的請求體需要把多個文件循環(huán)的拼接起來.
- 有些服務(wù)器可以在上傳文件的同時,提交一些文本內(nèi)容給服務(wù)器.典型應(yīng)用 : 新浪微博,上傳圖片的同時,發(fā)送一個微博.
-
name="status"
中的"status"
是腳本文件接收上傳時文字信息的參數(shù)的名稱
(四)多個文件上傳實現(xiàn)
1.準備上傳數(shù)據(jù),調(diào)用多文件上傳的主方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 參數(shù)1
NSString *URLString = @"http://localhost/php/upload/upload-m.php";
// 參數(shù)2
NSString *serverFileName = @"userfile[]";
// 參數(shù)3
NSString *filePath1 = [[NSBundle mainBundle] pathForResource:@"mm01.jpg" ofType:nil];
NSString *filePath2 = [[NSBundle mainBundle] pathForResource:@"mm02.jpg" ofType:nil];
NSArray *filePaths = @[filePath1,filePath2];
// 參數(shù)4
NSDictionary *textDict = @{@"status":@"今天和女神的男神在一起!好開心!"};
// 調(diào)用文件上傳的主方法
[self uploadFilesWithURLString:URLString serverFileName:serverFileName filePaths:filePaths textDict:textDict];
}
2.多文件上傳的主方法
/**
* 多文件上傳的主方法
*
* @param URLString 文件上傳的地址
* @param serverFileName 服務(wù)器接收文件的字段名
* @param filePaths 文件路徑的數(shù)組
* @param textDict 文件上傳的附帶信息
*/
- (void)uploadFilesWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths textDict:(NSDictionary *)textDict
{
// URL
NSURL *URL = [NSURL URLWithString:URLString];
// 可變請求
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL];
// 設(shè)置請求頭
[requestM setValue:@"multipart/form-data; boundary=itcast" forHTTPHeaderField:@"Content-Type"];
// 設(shè)置請求方法
requestM.HTTPMethod = @"POST";
// 設(shè)置請求體
requestM.HTTPBody = [self getHTTPBodyWithServerFileName:serverFileName filePaths:filePaths textDict:textDict];
// 發(fā)送請求實現(xiàn)文件上傳
[[[NSURLSession sharedSession] dataTaskWithRequest:requestM completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 處理響應(yīng)
if (error == nil && data != nil) {
// 反序列化
id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@",result);
} else {
NSLog(@"%@",error);
}
}] resume];
}
3.獲取請求體信息
/**
* 獲取請求體信息
*
* @param serverFileName 服務(wù)器接收文件的字段名
* @param filePaths 文件的路徑數(shù)組
* @param textDict 文件上傳時的文本信息
*
* @return 文件的二進制數(shù)據(jù)
*/
- (NSData *)getHTTPBodyWithServerFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths textDict:(NSDictionary *)textDict
{
// 定義dataM拼接請求體二進制數(shù)據(jù)
NSMutableData *dataM = [NSMutableData data];
// 循環(huán)拼接文件二進制信息
[filePaths enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 用于字符串信息
NSMutableString *stringM = [NSMutableString string];
// 拼接文件開始的分隔符
[stringM appendString:@"--itcast\r\n"];
// 拼接表單數(shù)據(jù)
[stringM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",serverFileName,[obj lastPathComponent]];
// 拼接文件類型
[stringM appendString:@"Content-Type: image/jpeg\r\n"];
// 拼接單純的換行
[stringM appendString:@"\r\n"];
// 把前面的字符串信息拼接到請求體里面
[dataM appendData:[stringM dataUsingEncoding:NSUTF8StringEncoding]];
// 拼接文件的二進制數(shù)據(jù)到dataM
[dataM appendData:[NSData dataWithContentsOfFile:obj]];
// 拼接二進制數(shù)據(jù)后面的換行
[dataM appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 拼接文件的文本信息
[textDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 用于拼接文本信息
NSMutableString *stringM = [NSMutableString string];
// 拼接文本信息的開始分割符
[stringM appendString:@"--itcast\r\n"];
// 拼接表單數(shù)據(jù)
[stringM appendFormat:@"Content-Disposition: form-data; name=%@\r\n",key];
// 拼接單純的換行
[stringM appendString:@"\r\n"];
// 拼接文本信息
[stringM appendFormat:@"%@\r\n",obj];
// 把文本信息拼接到請求體
[dataM appendData:[stringM dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 拼接文件上傳的結(jié)束分隔符
[dataM appendData:[@"--itcast--" dataUsingEncoding:NSUTF8StringEncoding]];
return dataM.copy;
}