此篇文章的理論基礎主要是與HTTP網絡通訊協議相關。爲集中精力,能夠先把TCP/IP協議這些置之不理,也就是先只關注HTTP的請求和響應的結構。HTTP完整的原理內容就此略過。在此只略提相關內容。文中涉及的設計源碼能夠經過這裏獲取 https://github.com/wuqingjian2015/uploadHelper,有意者能夠去看看。html
HTTP是幹什麼用的呢?git
先考慮一下如下應用過程:github
那麼,若是應用上面過程來實現上傳文件這個功能,須要作到幾方面:服務器
HTTP協議就是解決以上這些問題的。它定義了請求體結構和響應體結構。只要客戶端或服務端遵照這個標準,它就能與任何遵照這一標準的應用程序通訊。網絡
若是再想實地觀察一下符合HTTP標準的請求體和響應體「長」什麼樣,能夠用一些抓包工具。我用了Wireshark和Charles。若是你的是網頁應用,能夠在IE上按F12鍵調出開發工具窗口的網絡Tab。架構
在這裏,咱們只關注請求,瞭解響應StatusCode是200表示正常。app
對於請求,由於iOS會自動設置其餘內容,若是我們不設置的話。下面只討論其中的微服務
如何設置目標地址?在建立NSURLRequest時,指定URL便可。如,工具
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.targetURL];post
接下來,咱們須要設置Content-Type的值爲:multipart/form-data,同時制定boundary的值,該boundary會在設置請求正文時用到。到此爲止,咱們獲得了這樣的一些代碼:
-(NSURLRequest *)createRequestHeader
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.targetURL]; //指定目標地址
//建立http header請求標頭內容
// Content-Type := multipart/form-data; boundary=---------------827292(任意)
// Content-Length := (文件長度)
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"]; //設置Content-Type
[request setHTTPMethod:@"post"];//設置Method爲POST
return request;
}
下面再來看請求正文怎麼設置。在iOS中,由NSURLRequest.HTTPBody屬性來指定,其爲NSData類型。謹記:這個有固定的格式,該格式必須正確,不然服務器端沒法取得正確的內容。而這個問題沒法經過抓包工具中體現出來。以下:
格式:
beginBoundary
Content-Disposition: form-data; name="<服務器端須要知道的名字>"; filename="<服務器端這個傳上來的文件名>"
Content-Type: application/zip --根據不一樣的文件類型選擇不一樣的值
<空行>
<二進制數據>
endBoundary
範例:
----KenApp299912318
Content-Disposition: form-data; name="<服務器端須要知道的名字>"; filename="<服務器端這個傳上來的文件名>"
Content-Type: application/zip --根據不一樣的文件類型選擇不一樣的值
<空行>
<二進制數據>
----KenApp299912318--
有代碼有真相:
-(NSData*)createDataForRequestHTTPBodyForSource
{
NSMutableString *bodyHead = [[NSMutableString alloc] init];
NSMutableData *data = [[NSMutableData alloc] init];
NSString *fileName = [self.sourceURL lastPathComponent];
NSString *name=@"uploadFile";
NSData *fileContent = [NSData dataWithContentsOfURL:self.sourceURL];
//建立http body請求體內容
// 第一行: --827292
[bodyHead appendString:self.beginBoundary];
// [body appendFormat:@"--------------------"]
// Content-Disposition: form-data; name="uploadFile"; filename="xxxx.ext"
[bodyHead appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",name,fileName];
// Content-Type: application/x-zip-compressed
// (空行)
[bodyHead appendFormat:@"Content-Type: application/zip\r\n\r\n"];
// (二進制數據)
[data appendData:[bodyHead dataUsingEncoding:NSUTF8StringEncoding]];
[data appendData:fileContent];
// 最後一行:827292--
[data appendData:[self.endBoundary dataUsingEncoding:NSUTF8StringEncoding]];
return data;
}
到目前爲止,我們知道怎麼設置請求標頭和請求正文了。怎麼用上這些結果呢?
若是是用NSURLConnection的話, 咱們須要在同一個NSURLRequest中設置好這二者。
再調用factory method [NSURLConnection connectionWithRequest: delegate:];
若是是用NSURLSession中uploadTask的話,須要在NSURLRequest中設置請求標頭(以下requestWithHeader),同時在NSData中設置請求正文(以下requestHTTPBody)。代碼例子以下,其中SSUploadHelper封裝了以上提到的處理過程。
/////////////////////////////////範 例////////////////////////
SSUploadHelper *uploadHelper = [[SSUploadHelper alloc] initWithTarget:[NSURL URLWithString:@"http://192.168.31.172:5012/ArchFlow/upload"] forSource:self.downloadedLocation];
NSURLSessionUploadTask *uploadTask = [self.ephemeralSession uploadTaskWithRequest:[uploadHelper requestWithHeader] fromData:[uploadHelper requestHTTPBody] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"Got response %@ with error %@.\n", response, error);
NSLog(@"DATA:\n%@\nEND DATA\n",
[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
[uploadTask resume];
//////////////////////////////////
至此,客戶端的設計基本完成了。爲了在服務器端看到上傳到的文件,我們須要搭建一個服務器環境了。我我的實現了一個基於Python的REST的微服務器,在處理到ArchFlow/upload的POST請求中,從request中獲取文件,並保存到本地目錄下。這是我在軟件架構時用到的工具服務器,在此基礎上做的臨時上傳文件功能。
//功能測試:
在服務器啓動的過程當中,執行以上客戶端代碼,能夠看到文件被拷貝到目標目錄下。
注意事項:
boundary的格式值得加倍注意,在請求標頭中指明的boundary,必須用到請求正文中。
剩下的就是耐心調試了。Good Luck!