此篇文章的理论基础主要是与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!