博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS 网络请求 NSURLSession 的上传文件方法
阅读量:6462 次
发布时间:2019-06-23

本文共 4132 字,大约阅读时间需要 13 分钟。

 

 

此篇文章的理论基础主要是与HTTP网络通信协议相关。为集中精力,可以先把TCP/IP协议这些置之不理,也就是先只关注HTTP的请求和响应的结构。HTTP完整的原理内容就此略过。在此只略提相关内容。文中涉及的设计源码可以通过这里获取 https://github.com/wuqingjian2015/uploadHelper,有意者可以去看看。

 

HTTP是干什么用的呢?

先考虑一下以下应用过程:

  1. 从客户端向服务器端发起一个请求。
  2. 服务器端处理请求
  3. 服务器端发送一个响应。

那么,如果应用上面过程来实现上传文件这个功能,需要做到几方面:

  1. 要上传的文件需要捆绑在这个请求里面。
  2. 服务器端能够理解该请求,作出相关处理:如能提取出文件内容,存放在某一个文件目录下;如能提取当中某些指令,调用相关的指令处理程序。
  3. 服务器端发送一个响应,客户端应该能够理解该响应内容。

HTTP协议就是解决以上这些问题的。它定义了请求体结构和响应体结构。只要客户端或服务端遵守这个标准,它就能与任何遵守这一标准的应用程序通信。

如果再想实地观察一下符合HTTP标准的请求体和响应体“长”什么样,可以用一些抓包工具。我用了Wireshark和Charles。如果你的是网页应用,可以在IE上按F12键调出开发工具窗口的网络Tab。

在这里,我们只关注请求,了解响应StatusCode是200表示正常。

对于请求,因为iOS会自动设置其他内容,如果咱们不设置的话。下面只讨论其中的

  1. 上传到的目标地址
  2. 请求标头中的Content-Type
  3. 以及请求正文的内容

如何设置目标地址?在创建NSURLRequest时,指定URL即可。如,

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.targetURL];

接下来,我们需要设置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!

转载于:https://www.cnblogs.com/tmf-4838/p/5380454.html

你可能感兴趣的文章
rsync快速部署记录
查看>>
easyUI解析
查看>>
__defineGetter__ && __defineSetter__
查看>>
http-fs
查看>>
CF708A Letters Cyclic Shift 模拟
查看>>
Spring+MyBatis整合
查看>>
JAVA的起源和基础
查看>>
Android深度探索(卷1)HAL与驱动开发第六章总结
查看>>
lua序列化(支持循环引用)
查看>>
【转载】Java - Wait & Notify
查看>>
通过回调在fragment中获取activity的触摸事件
查看>>
安卓 内容提供者 sql 区别
查看>>
CSRF
查看>>
B-tree&B+tree
查看>>
jquery禁止点击事件,css禁止按钮
查看>>
C#Windows Form简易计算器实现(上)
查看>>
使用Matplotlab画图
查看>>
瑞星:周末拦截挂马网站数减少 新闻类网站占主打
查看>>
REST服务开发实战,互联网营销
查看>>
一起谈.NET技术,验证.NET强命称的思路和实例
查看>>