NSURLConnection筆記-上傳文件

使用NSURLConnection上傳文件
上傳文件,使用POST還是PUT請求?根據(jù)原來HTTP的定義使用PUT來做上傳,但是現(xiàn)在開發(fā)中,用的是POST。

1.單文件上傳

發(fā)送請求的步驟
1.設(shè)置url
2.設(shè)置request,設(shè)置請求頭、請求體
3.發(fā)送請求

設(shè)置請求頭
Content-Type multipart/form-data; boundary=一個字符串
這一行必須手動告訴服務(wù)器:本次上傳的是文件信息。如果上傳的是一個普通的字符串,則不需要寫這行代碼

設(shè)置請求體
上傳的格式需要自己寫。POST上傳文件的格式遵循W3C制定的標(biāo)準(zhǔn),但是OC沒有做封裝,自己寫的時候必須按照這個格式來寫。
格式如下:(這里上傳的是一個文件名為“JSON”的json本地文件)

--boundary //上邊界 //“boundary”是一個邊界,沒有實際的意義,可以用任意字符串來替代
Content-Disposition: form-data; name=xxx; filename=xxx
Content-Type: application/octet-stream
(空一行)
文件內(nèi)容的二進(jìn)制數(shù)據(jù)
--boundary-- //下邊界
  • 請求體內(nèi)容分為三個部分:
    1.上邊界部分,告訴服務(wù)器要做數(shù)據(jù)上傳,包含:
    a. 服務(wù)器的接收字段name=xxx。xxx是負(fù)責(zé)上傳文件腳本中的 字段名,開發(fā)的時候,可以咨詢后端程序員,不需要自己設(shè)定。
    b. 文件在服務(wù)器中保存的名稱filename=xxx。xxx可以自己指定,不一定和本地原本的文件名相同
    c. 上傳文件的數(shù)據(jù)類型 application/octet-stream
    2.上傳文件的數(shù)據(jù)部分(二進(jìn)制數(shù)據(jù))
    3.下邊界部分,嚴(yán)格按照字符串格式來設(shè)置.

上邊界部分和下邊界部分的字符串,最后都要轉(zhuǎn)換成二進(jìn)制數(shù)據(jù),和文件部分的二進(jìn)制數(shù)據(jù)拼接在一起,作為請求體發(fā)送給服務(wù)器.

實現(xiàn)代碼如下:

#define bound @"boundary"

- (void)test{
    NSURL *url = [NSURL URLWithString:@"xxxxxxx"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    request.HTTPMethod = @"POST";
    //設(shè)置請求頭
    NSString *headerStr = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",bound];
    [request setValue:headerStr forHTTPHeaderField:@"Content-Type"];
    //設(shè)置請求體
    request.HTTPBody = [self setHttpBody];
 
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        
    }];
}

//設(shè)置請求體
//必須手動設(shè)置換行,\r\n:保證一定會換行,所有服務(wù)器都識別(不是/r/n)
- (NSData *)setHttpBody{
    NSMutableString *bodyHeaderStr = [NSMutableString stringWithFormat:@"--%@\r\n",bound];
    //"userfile":服務(wù)器接收文件參數(shù)的key值,服務(wù)器端定義,不需要自己指定
    //filename:文件上傳到服務(wù)器之后保存的名稱??梢宰约褐付?不一定和本地原本的文件名相同
    [bodyHeaderStr appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",@"userfile",@"JSON"];
    //Content-Type:上傳文件的文件類型 application/octet-stream :數(shù)據(jù)流格式,如果不知道文件類型,可以直接設(shè)置為這個格式
    [bodyHeaderStr appendString:@"Content-Type: application/octet-stream\r\n\r\n"];//兩個換行

    //文件內(nèi)容:二進(jìn)制
    NSData *fileData = [NSData dataWithContentsOfFile:@"本地文件路徑"];
    
    NSMutableString *bodyFooterStr = [NSMutableString stringWithFormat:@"\r\n--%@--",bound];
    
    //拼接成二進(jìn)制數(shù)據(jù)
    NSMutableData *bodyData = [NSMutableData data];
    [bodyData appendData:[bodyHeaderStr dataUsingEncoding:NSUTF8StringEncoding]];
    [bodyData appendData:fileData];
    [bodyData appendData:[bodyFooterStr dataUsingEncoding:NSUTF8StringEncoding]];
    return bodyData;
}

封裝設(shè)置請求體的方法

如果上傳的不再是json數(shù)據(jù)文件而是一張圖片,以上設(shè)置請求體方法的代碼需要作出改變。因此需要對此進(jìn)行封裝。
封裝的方法需要提供 文件路徑、服務(wù)器的接收字段name、文件上傳到服務(wù)器后保存的名稱filename 這些傳入?yún)?shù)。
上傳文件的時候,需要告訴服務(wù)器文件類型(即Content-Type),這時,需要獲取文件的 MIMEType.獲取文件的 MIMEType 方法:發(fā)送一個同步請求,通過 response 獲得。如果不想告訴服務(wù)器具體的文件類型,可以使用這個 Content-Type : application/octet-stream(8進(jìn)制流)

通過發(fā)送一個同步請求來獲得上傳的文件類型:

//動態(tài)獲得文件類型,通過發(fā)送一個同步請求
-(NSURLResponse *)getFileTypeWithPath:(NSString *)path{
    //根據(jù)本地文件路徑,設(shè)置一個本地的url
    //file 資源是本地計算機(jī)上的文件。格式file:///,注意后邊應(yīng)是三個斜杠(最后一個杠屬于傳入的路徑的一部分,所以下面只有兩個杠)。
    NSString *urlstr = [NSString stringWithFormat:@"file://%@",path];
    //發(fā)送一個同步請求來獲得文件類型
    NSURL *url = [NSURL URLWithString:urlstr];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    //(NSURLResponse *__autoreleasing  _Nullable * _Nullable) 有兩個**,先指定一塊地址,內(nèi)容為空。等方法執(zhí)行完畢之后,會將返回的內(nèi)容存儲到這塊地址中。
    NSURLResponse *response = nil;
    // 同步請求: 阻塞當(dāng)前線程
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
    //MIMEType就是需要的文件類型
    //expectedContentLength文件的長度。一般在文件下載的時候使用,類型是lld
    //suggestedFilename建議的文件名稱
    NSLog(@"response: %@ %@ %lld",response.MIMEType, response.suggestedFilename, response.expectedContentLength);
    return response;
}

封裝請求體設(shè)置:

/*
 filePath:需要上傳的文件路徑。(手機(jī)訪問相冊!選擇一張圖片.是拿到圖片的二進(jìn)制數(shù)據(jù)?還是拿到圖片的路徑? -- 路徑)
 key : 服務(wù)器接受文件的 key 值
 name: 文件上傳到服務(wù)器之后保存的名稱
 上傳文件的文件類型:根據(jù)文件路徑,自動獲得文件類型
*/
- (NSData *)packageWithPath:(NSString *)filePath fileKey:(NSString *)key fileName:(NSString *)name{
    //根據(jù)文件路徑,發(fā)送同步請求,獲得文件信息
    NSURLResponse *response = [self getFileTypeWithPath:filePath];
    
    if (!name) {//如果沒有傳入name值,默認(rèn)使用建議的文件名
        name = response.suggestedFilename;
    }
    
    NSMutableString *bodyHeaderStr = [NSMutableString stringWithFormat:@"--%@\r\n",bound];
    [bodyHeaderStr appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",key,name];
    [bodyHeaderStr appendFormat:@"Content-Type: %@\r\n\r\n",response.MIMEType];//兩個換行
    
    //文件內(nèi)容:二進(jìn)制
    NSData *fileData = [NSData dataWithContentsOfFile:filePath];
    
    NSMutableString *bodyFooterStr = [NSMutableString stringWithFormat:@"\r\n--%@--",bound];
    
    //拼接成二進(jìn)制數(shù)據(jù)
    NSMutableData *bodyData = [NSMutableData data];
    [bodyData appendData:[bodyHeaderStr dataUsingEncoding:NSUTF8StringEncoding]];
    [bodyData appendData:fileData];
    [bodyData appendData:[bodyFooterStr dataUsingEncoding:NSUTF8StringEncoding]];
    return bodyData;
}

使用封裝后的方法設(shè)置請求體:
request.HTTPBody = [self packageWithPath:@"/Users/apple/Desktop/bd_logo1.png" fileKey:@"userfile" fileName:nil];

對文件上傳進(jìn)行整體封裝

POST請求的設(shè)置、發(fā)送以及請求體的封裝等全部放到一個工具類中進(jìn)行整體封裝,當(dāng)需要進(jìn)行文件上傳時,直接調(diào)用該工具類提供的接口即可

NetworkTool.h
// 定義兩個 Block : 1. 成功Block回調(diào) 2.失敗的 Block 回調(diào)!
typedef void(^SuccessBlock)(NSData *data, NSURLResponse *response);
typedef void(^failBlock)(NSError *error);

NetworkTool.m
#define bound @"boundary"

- (void)POSTFileWithUrlString:(NSString *)urlString FilePath:(NSString *)filePath FileKey:(NSString *)key FileName:(NSString *)name SuccessBlock:(SuccessBlock)Success FailBlock:(failBlock)fail
{
    // 1. 創(chuàng)建請求
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    request.HTTPMethod = @"POST";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",bound];
    [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
    // 設(shè)置請求體
    request.HTTPBody = [self getHttpBodyWithFilePath:filePath FileKey:key FileName:name];
      
    // 2. 發(fā)送請求
    // 在系統(tǒng)內(nèi)的Block 中調(diào)用自己的 成功或者失敗的回調(diào)
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        // 成功或者失敗:根據(jù)服務(wù)器返回的參數(shù)判定
        //簡單舉例
        if (data && !connectionError) {  // 成功            
            // 調(diào)用 成功的回調(diào)
            if (Success) {
                Success(data,response);
            }
        }else
        {
            if (fail) {
                // 失敗之后的回調(diào)!
                fail(connectionError);
            }
        }
    }];
}

- (NSData *)packageWithPath:(NSString *)filePath fileKey:(NSString *)key fileName:(NSString *)name{
    ......
}

-(NSURLResponse *)getFileTypeWithPath:(NSString *)path{
    ......
}

// 獲得單例對象,只有通過這個方法獲得的才是單例對象
// 沒有把其他創(chuàng)建對象實例的方法也堵死了,從而可以讓別人自己選擇實例化對象的方法
+(instancetype)sharedNetworkTool{
    static id _instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    }); 
    return _instance;
}

在ViewController中,需要上傳文件時:

CZNetworkTool *tool = [CZNetworkTool sharedNetworkTool];    
[tool POSTFileWithUrlString:@"xxxurl" FilePath:@"xxx本地文件路徑" FileKey:@"userfile" FileName:nil SuccessBlock:^(NSData *data, NSURLResponse *response) {
        NSLog(@"請求成功");
    } FailBlock:^(NSError *error) {
        NSLog(@"網(wǎng)絡(luò)鏈接錯誤");
    }];

2.多文件上傳(帶普通文本參數(shù))

多文件上傳和單文件上傳的思路相似,區(qū)別在于設(shè)置請求體。
另外,有些服務(wù)器可以在上傳文件的同時,提交一些文本內(nèi)容給服務(wù)器,比如:
1.新浪微博: 上傳圖片的同時,發(fā)送一條微博信息
2.購物評論: 購買商品之后發(fā)表評論的時候圖片+評論內(nèi)容

多文件+普通文本 上傳的請求體格式如下:

--boundary\r\n           // 第一個文件參數(shù)//上邊界,不過也可以寫成這樣:\r\n--boundary\r\n 
Content-Disposition: form-data; name=xxx; filename=xxx\r\n
Content-Type:image/jpeg\r\n\r\n        
(空一行)        
上傳文件的二進(jìn)制數(shù)據(jù)部分    
\r\n--boundary\r\n    // 第二個文件參數(shù)//上邊界 //文件一的下邊界可略,在這句之前插入文件一的下邊界\r\n--boundary--也可以
Content-Disposition: form-data; name=xxx; filename=xxx\r\n
Content-Type:text/plain\r\n\r\n
(空一行)                
上傳文件的二進(jìn)制數(shù)據(jù)部分  
\r\n--boundary\r\n    //普通文本參數(shù) //上邊界
Content-Disposition: form-data; name="xxx"\r\n\r\n    //name是服務(wù)器的接收字段,不需要自己制定
(空一行)     
普通文本二進(jìn)制數(shù)據(jù)     
\r\n--boundary--       // 下邊界

普通文本的上傳格式不需要Content-Type

封裝設(shè)置請求體的方法

思路:
1.由于文件內(nèi)容是可變的,因此創(chuàng)建一個文件參數(shù)字典以要上傳文件的本地路徑為key、文件在服務(wù)器中保存的名稱為value。
2.普通文本信息(字符串信息) 有可能有多個值。創(chuàng)建一個普通文本參數(shù)字典, 以服務(wù)器接受文本參數(shù)的key值為 key、以上傳的普通文本參數(shù)為 value

此處省略動態(tài)獲得上傳的文件類型的方法,直接設(shè)置上傳文件類型為application/octet-stream

#define bound @"boundary"
// 文件參數(shù)字典: fileName :key filePath:value
// fileDict 文件參數(shù)字典
// fileKey 服務(wù)器接受文件參數(shù)的key值
// paramaters 普通文本參數(shù)字典
- (NSData *)getHttpBodyWithFileDict:(NSDictionary *)fileDict fileKey:(NSString *)fileKey paramater:(NSDictionary *)paramaters
{
    NSMutableData *data = [NSMutableData data];
    // 遍歷文件參數(shù)字典,設(shè)置文件的格式
    [fileDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        // 取出每一條字典數(shù)據(jù): fileName : 服務(wù)器保存的名稱 , filePath: 文件路徑
        NSString *fileName = key;
        NSString *filePath = obj;    
    
        //文件的上邊界
        NSMutableString *headerStrM = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", bound];        
        [headerStrM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",fileKey,fileName];
        [headerStrM appendFormat:@"Content-Type: application/octet-stream\r\n\r\n"];
        
        // 將文件的上邊界添加到請求體中!
        [data appendData:[headerStrM dataUsingEncoding:NSUTF8StringEncoding]];        
        // 將文件內(nèi)容添加到請求體中
        [data appendData:[NSData dataWithContentsOfFile:filePath]];
        
    }];   
    // 遍歷普通文本參數(shù)字典
    [paramaters enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {        
        // msgKey :服務(wù)器接受參數(shù)的key值 msgValue:上傳的文本參數(shù)
        NSString *msgKey = key;
        NSString *msgValue = obj;
        
        // 普通文本信息上邊界
        NSMutableString *headerStrM = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", bound];       
        [headerStrM appendFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n",msgKey];
        
        [data appendData:[headerStrM dataUsingEncoding:NSUTF8StringEncoding]];        
        // 普通文本信息;
        [data appendData:[msgValue dataUsingEncoding:NSUTF8StringEncoding]];
    }]; 
    // 3. 下邊界 (只添加一次)
    NSMutableString *footerStrM = [NSMutableString stringWithFormat:@"\r\n--%@--",bound];
    [data appendData:[footerStrM dataUsingEncoding:NSUTF8StringEncoding]];    
    return data;
}

使用封裝后的方法設(shè)置請求體:

......略創(chuàng)建請求

NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",kBounary];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
// 設(shè)置請求體
NSString *fileName1 = @"文件1";
NSString *filePath1 = @"本地路徑1xxx";    
NSString *fileName2 = @"文件2";
NSString *filePath2 = @"本地路徑2xxx";

NSDictionary *fileDict = @{fileName1:filePath1,fileName2 :filePath2};
request.HTTPBody = [self getHttpBodyWithFileDict:fileDict fileKey:@"xxx" paramater:@{@"服務(wù)器key1":@"普通文本1",@"服務(wù)器key2":@"普通文本2"}];

//發(fā)送請求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);        
}];

多文件上傳整體封裝

與單文件上傳的整體封裝思路一樣。略

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,711評論 19 139
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,537評論 6 13
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,704評論 4 61
  • 自己去醫(yī)院,確實不是一種什么好的體驗,但也不是太糟糕的體驗。感覺自己沒什么不好,還是要多去愛別人,多去愛這...
    6a7bdcc57e6f閱讀 158評論 1 0
  • 今天的話題是如何通過整理來提升在婆家的地位? 作為初為人妻 初為兒媳 初為人母,同時要扮演三個角色的我,如何把這個...
    知恩藝生閱讀 688評論 0 0

友情鏈接更多精彩內(nèi)容