1. 程式人生 > >ios form表單上傳圖片

ios form表單上傳圖片

1.使用微博開發的一個“傳送帶圖片微博”的介面來測試,這是介面地址,這裡面明確說明需要使用multipart/form-data格式提交圖片。關於使用微博開放平臺api、授權之類的就不說了。

2.multipart/from-data是一種進行表單提交時的訊息格式。表單提交資料的時候,預設型別是application/x-www-form-urlencoded,也就是key=value的鍵值對格式,提交檔案的時候使用multipart/from-data。因為是表單提交,所以http請求方式是POST。然後在請求頭裡設定Content-Type為multipart/from-data指定請求的格式。

  NSURL *URL = [[NSURL alloc]initWithString:urlString];
      request = [[NSMutableURLRequest alloc]initWithURL:URL cachePolicy:(NSURLRequestUseProtocolCachePolicy) timeoutInterval:30];
  request.HTTPMethod = @"POST";

    NSString *boundary = @"wfWiEWrgEFA9A78512weF7106A";
    request.allHTTPHeaderFields = @{
                                    @"Content-Type":[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary]
                                    };

注意到在Content-Type裡還有個boundary,顧名思義,這個東西是用來做分隔的字串。boundary本身沒有特殊要求,只要不會和其他內容混淆就好,所以儘量複雜些。

3.POST請求,引數都放在請求體裡面,而請求體是這裡的關鍵,multipart/form-data就是一種格式,約定請求體的資料如何存放。放個例子先:

--wfWiEWrgEFA9A78512weF7106A                    //部分1
Content-Disposition: form-data; name="status"   //部分2

哈哈哈                                           //部分3
--wfWiEWrgEFA9A78512weF7106A
Content-Disposition: form-data; name="source"

2582981980
--wfWiEWrgEFA9A78512weF7106A
Content-Disposition: form-data; name="access_token"

2.00nVEJoBgbvnoCc54e19c4c4NksmWC
--wfWiEWrgEFA9A78512weF7106A
Content-Disposition: form-data; name="pic"; filename="卡車.png"
Content-Type=image/png

...這裡是檔案的二進位制資料...                      //部分4
--wfWiEWrgEFA9A78512weF7106A--                //部分5

上面的“//部分X”是註釋哈。
(1)部分1是“--”+boundary,即雙減號加分隔符,然後換行,注意換行是使用“\r\n”,因為這些標準開始都是在html中使用的。測試了微博介面,換行也不能出錯。
(2)部分2的格式是:Content-Disposition: form-data; name="xxx",這裡的xxx是介面的引數,比如微博測試介面有一個引數是“status”,那status寫在這裡。然後是兩個換行,即“\r\n\r\n”。
(3)有key就有value,上面說了key的位置,這裡就是value的位置。“status”欄位代表的是微博的正文內容,所以就把微博正文內容放在部分3位置,即“哈哈哈”。然後換行。
(4)然後就是部分1、2、3這個結構重複,每一個重複結構對應著接口裡的一個欄位的資料。直到你要上傳的檔案,部分4。部分4這一節多出了“ilename="卡車.png";Content-Type=image/png”這些內容,其實這裡可以還有其他的內容可以設定,charset和content-transfer-encoding,都是用於描述這一部分資料。具體參考rfc標準。需要注意的是,name\filename是帶引號的,而Content-Type是沒有的,就這一個細節,廢掉了我一下午啊!
這裡的Content-Type值是這裡要上傳檔案的格式,也不能錯。
(5)部分4這裡是需要上傳的檔案的二進位制資料,當然其他部分也是同樣是要轉成NSData的。
(6)最後部分5是結束標識,--wfWiEWrgEFA9A78512weF7106A這部分是和前面的分割符一樣,但接下來不是換行,而是繼續“--”,整個請求體結束。這也是個坑啊,之前以為沒有“--”!

具體程式碼如下:

NSMutableData *postData = [[NSMutableData alloc]init];//請求體資料
    for (NSString *key in params) {
        //迴圈引數按照部分1、2、3那樣迴圈構建每部分資料
        NSString *pair = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n",boundary,key];
        [postData appendData:[pair dataUsingEncoding:NSUTF8StringEncoding]];
        
        id value = [params objectForKey:key];
        if ([value isKindOfClass:[NSString class]]) {
            [postData appendData:[value dataUsingEncoding:NSUTF8StringEncoding]];
        }else if ([value isKindOfClass:[NSData class]]){
            [postData appendData:value];
        }
        [postData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    }
    
    //檔案部分
    NSString *filename = [filePath lastPathComponent];
    NSString *contentType = AFContentTypeForPathExtension([filePath pathExtension]);
    
    NSString *filePair = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"; filename=\"%@\"
Content-Type=%@\r\n\r\n",boundary,fileKey,filename,contentType];
    [postData appendData:[filePair dataUsingEncoding:NSUTF8StringEncoding]];
    [postData appendData:fileData]; //加入檔案的資料
[postData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; 

如果有多個檔案,就重複設定檔案部分,使用不同的`name`標識。
    
    //設定結尾
    [postData appendData:[[NSString stringWithFormat:@"--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
        request.HTTPBody = postData;
    //設定請求頭總資料長度
    [request setValue:[NSString stringWithFormat:@"%lu",(unsigned long)postData.length] forHTTPHeaderField:@"Content-Length"];

然後根據檔案字尾名可以獲取對應的Content-Type,來源AFNetWorking:

static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
#ifdef __UTTYPE__
    NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
    NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
    if (!contentType) {
        return @"application/octet-stream";
    } else {
        return contentType;
    }
#else
#pragma unused (extension)
    return @"application/octet-stream";
#endif
}

需要注意的:
1、每個該換行的地方都不能少,也不能多,且換行為“\r\n”
2、Content-Type的值沒有引號
3、整個請求體是以“--”結束的。

然後因為postData裡面混進了圖片的二進位制資料,沒法從NSData轉成字串,在除錯的時候沒法直觀的檢視哪裡出了問題。我的做法是把新增檔案資料,改成一個字串的資料,比如:

[postData appendData:[@"測試檔案資料" dataUsingEncoding:NSUTF8StringEncoding]];
//[postData appendData:fileData]; //加入檔案的資料

最後是專案程式碼:github地址。微博授權功能已經在裡面了,先授權,在測試提交圖片。上傳後看控制檯輸入,成功後可以在授權的微博賬號裡看見剛發的這條微博。


這個很久沒更新了,相關知識可以直接參考AFNetWorking的封裝,上傳檔案部分就是這個原理。



作者:Find1991
連結:https://www.jianshu.com/p/a0e3c77d3164
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。