NSURLSession/NSURLConnection的上传文件方法(已做了更新)
最好的学习方法就是 领悟 + 证悟。
此篇文章的理论基础主要是与HTTP网络通信协议相关。为集中精力,可以先把TCP/IP协议这些置之不理,也就是先只关注HTTP的请求和响应的结构。HTTP完整的原理内容就此略过。在此只略提相关内容。文中涉及的设计源码可以通过这里获取 https://github.com/wuqingjian2015/uploadHelper,有意者可以去看看。
HTTP是干什么用的呢?
先考虑一下以下应用过程:
- 从客户端向服务器端发起一个请求。
- 服务器端处理请求。
- 服务器端发送一个响应。
那么,如果应用上面过程来实现上传文件这个功能,需要做到几方面:
- 要上传的文件需要捆绑在这个请求里面。
- 服务器端能够理解该请求,作出相关处理:如能提取出文件内容,存放在某一个文件目录下;如能提取当中某些指令,调用相关的指令处理程序。
- 服务器端发送一个响应,客户端应该能够理解该响应内容。
HTTP协议就是解决以上这些问题的。它定义了请求体结构和响应体结构。只要客户端或服务端遵守这个标准,它就能与任何遵守这一标准的应用程序通信。
如果再想实地观察一下符合HTTP标准的请求体和响应体“长”什么样,可以用一些抓包工具。我用了Wireshark和Charles。如果你的是网页应用,可以在IE上按F12键调出开发工具窗口的网络Tab。
在这里,我们只关注请求,了解响应StatusCode是200表示正常。
对于请求,因为iOS会自动设置其他内容,如果咱们不设置的话。下面只讨论其中的
- 上传到的目标地址
- 请求标头中的Content-Type
- 以及请求正文的内容
如何设置目标地址?在创建NSURLRequest时,指定URL即可。如,
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.targetURL];
设置目标地址URL
接下来,我们需要设置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中获取文件,并保存到本地目录下。这是我在软件架构时用到的工具服务器,在此基础上作的临时上传文件功能。(我已经上传一个版本到https://github.com/wuqingjian2015/uploadHelper,感兴趣的朋友可以在此基础上摆弄一下。)
//功能测试:
在服务器启动的过程中,执行以上客户端代码,可以看到文件被拷贝到目标目录下。
注意事项:
boundary的格式值得加倍注意,在请求标头中指明的boundary,必须用到请求正文中。
剩下的就是耐心调试了。Good Luck!
NSURLSession/NSURLConnection的上传文件方法(已做了更新)的更多相关文章
- Java ftp上传文件方法效率对比
Java ftp上传文件方法效率对比 一.功能简介: txt文件采用ftp方式从windows传输到Linux系统: 二.ftp实现方法 (1)方法一:采用二进制流传输,设置缓冲区,速度快,50M的t ...
- django上课笔记7-jQuery Ajax 和 原生Ajax-伪造的Ajax-三种Ajax上传文件方法-JSONP和CORS跨域资源共享
一.jQuery Ajax 和 原生Ajax from django.conf.urls import url from django.contrib import admin from app01 ...
- web 表单方式上传文件方法(不用flash插件)
原理:使用表单的input type="file"标签,通过ajax提交表单请求,后台获取请求中的文件信息,进行文件保存操作 由于我测试用的做了一个上传文件和上传图片方法,所以我有 ...
- springmvc上传文件方法及注意事项
本文基于注解的配置,敬请留意 基于注解整合 一.springmvc为我们提供两种上传方式配置: org.springframework.web.multipart.commons.CommonsMu ...
- Selenium上传文件方法总结
Web上本地上传图片,弹出的框Selenium是无法识别的,也就是说,selenium本身没有直接的方法去实现上传本地文件,这里总结了两种上传文件的方式. 一.利用Robot类处理文件上传. 其大致流 ...
- selenium 上传文件方法补充——SendKeys、win32gui
之前和大家说了input标签的上传文件的方式: <selenium2 python 自动化测试实战>(13)——上传文件 现在好多网站上传的标签并不是input,而是div之类的比如: 全 ...
- Ui自动化测试上传文件方法都在这里了
前言 实施UI自动化测试的时候,经常会遇见上传文件的操作,那么对于上传文件你知道几种方法呢?今天我们就总结一下几种常用的上传文件的方法,并分析一下每个方法的优点和缺点以及哪种方法效率,稳定性更高 被测 ...
- java的几种上传文件方法
这时:commonsmultipartresolver 的源码,可以研究一下 http://www.verysource.com/code/2337329_1/commonsmultipartreso ...
- .net ftp上传文件方法
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI ...
随机推荐
- JS基础回顾,小练习(判断数组,以及函数)
追梦子博客版权所有. // 判断arr是否为一个数组,返回一个bool值 方法1: function isArray(arr) { var str = arr.__proto__.constructo ...
- Maven进价:Maven的安装和目录结构
一.在windows上安装Maven 1.下载 下载地址:http://maven.apache.org/download.html 下载最新版本 maven3.2.5 2.解压 解压地址:F:\Ja ...
- DDD:建模原语 之 四象图(转载的神文)
“模型.状态和行为特征.场景”和“四象图”,建模观的命名与立象. 建模原语:四象图 作者:achieveidea@gmail.com 命名:模型.结构特征.行为特征.场景(及其规约). 释义:模型,描 ...
- MySQL中的锁(表锁、行锁)
锁是计算机协调多个进程或纯线程并发访问某一资源的机制.在数据库中,除传统的计算资源(CPU.RAM.I/O)的争用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性.有效性是所在有数 ...
- 利用 ELK系统分析Nginx日志并对数据进行可视化展示
一.写在前面 结合之前写的一篇文章:Centos7 之安装Logstash ELK stack 日志管理系统,上篇文章主要讲了监控软件的作用以及部署方法.而这篇文章介绍的是单独监控nginx 日志分析 ...
- jquery获取url参数
js/jquery 获取url参数 2010年04月27日 星期二 13:45 js代码: function GetQueryString(name) { var reg = new RegExp(& ...
- .NET指定程序集的位置
有两种方法用来指定程序集的位置: 使用 <codeBase> 元素. 使用 <probing> 元素. 还可以使用 .NET Framework 配置工具 (Mscorcfg. ...
- spring AspectJ的Execution表达式
Aspectj切入点语法定义 在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点" 例如定义切入点表达式 execu ...
- CentOS6.5菜鸟之旅:安装ATI显卡驱动
一.前言 自从安装了CentOS,我的显卡就没消停过,一直在彪高温而且噪音特别大,于是决定上网搜索解决办法.下面记录下来以供日后查阅. 二.安装fglrx driver(ATI/AMD 显卡的linu ...
- HTTP Cookie/Session
一.HTTP协议 HTTP协议是一个无状态协议,服务器无法判断若干个请求是否来自同一个浏览器,无法与浏览器进行会话. 二.HTTP会话控制:Cookie Cookie技术是使用在浏览器端的一种缓存技术 ...