AFHTTPSessionManager
是AFURLSessionManager
的子类。咱们能够经过这个类作HTTP请求。其实整个AFHTTPSessionManager
逻辑很简单,只是用HTTP的方式拼接了请求,而且调用父类的方式作处理。我会经过AFHTTPSessionManager
api来说一下POST上传数据的几种基本格式,而后我再随便分析一下AFHTTPSessionManager
。php
HTTP/1.1协议规定的HTTP请求方法有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 这几种。其中POST通常用来向服务端提交数据,接下来要讨论POST提交数据的几种方式。协议规定POST提交的数据必须放在消息主体中,但协议并无规定数据必须使用什么编码方式。实际上,开发者彻底能够本身决定消息主体的格式,只要最后发送的 HTTP 请求知足上面的格式就能够。html
可是,数据发送出去,还要服务端解析成功才有意义。通常服务端语言如php、python等,以及它们的framework,都内置了自动解析常见数据格式的功能。服务端一般是根据请求头(headers)中的Content-Type字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。因此说到POST提交数据方案,包含了Content-Type和消息主体编码方式两部分。python
这应该是最多见的 POST 提交数据的方式了。浏览器的原生表单,若是不设置enctype属性,那么最终就会以application/x-www-form-urlencoded方式提交数据。Content-Type被指定为application/x-www-form-urlencoded,提交的数据按照 key1=val1&key2=val2的方式进行编码,key和val都进行了URL转码。git
下面这个请求是简书进入一篇文章页面的时候,会自动往服务器POST一个请求,估计是统计文章被阅读的次数等功能。具体看下面:github
//发送的请求,删除了cookie相关的部分 POST /notes/e15592ce40ae/mark_viewed.json HTTP/1.1 Host: www.jianshu.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:52.0) Gecko/20100101 Firefox/52.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate X-CSRF-Token: vJvptva4Tqou/V3dd3nFCrcvRsb78FReHuIYZke5PVAnfR/tIAAMCfuaB2Z2/gaEohIZAsiEksUYyPqzg3DpSA== Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Referer: http://www.jianshu.com/p/e15592ce40ae Content-Length: 98 Connection: keep-alive Cache-Control: max-age=0 //请求体 uuid=4e3abc0f-1824-4a5d-982f-7d9dee92d9cd&referrer=http%3A%2F%2Fwww.jianshu.com%2Fu%2Fad726ba6935d
用AFHTTPSessionManager
实现上面这个application/x-www-form-urlencoded
请求。json
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSDictionary *params = @{ @"uuid":@"4e3abc0f-1824-4a5d-982f-7d9dee92d9cd", @"referrer":@"http://www.jianshu.com/p/e15592ce40ae" }; NSURLSessionDataTask *task = [manager POST:@"http://www.jianshu.com//notes/e15592ce40ae/mark_viewed.json" parameters:params progress:^(NSProgress * _Nonnull uploadProgress) { NSLog(@"进度更新"); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSLog(@"返回数据:%@",responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"返回错误:%@",error); }]; [task resume];
Multipart/form-data的基础方法是POST , 也就是说是由POST方法来组合实现的.
Multipart/form-data与POST方法的不一样之处在于请求头和请求体.
Multipart/form-data的请求头必须包含一个特殊的头信息 : Content-Type , 且其值也必须规定为multipart/form-data , 同时还须要规定一个内容分割符用于分割请求体中的多个POST的内容 , 如文件内容和文本内容天然须要分割开来 , 否则接收方就没法正常解析和还原这个文件了.
Multipart/form-data的请求体也是一个字符串 , 不过和post的请求体不一样的是它的构造方式 , post是简单的name=value值链接 , 而Multipart/form-data则是添加了分隔符等内容的构造体.api
请求的头部信息以下:浏览器
//其中xxxxx是我自定义的分隔符,每一个人均可以选择本身的分隔符 Content-Type: multipart/form-data; boundary=xxxxx
下面咱们来看一下一个个人Multipart/form-data请求体:服务器
POST /uploadFile HTTP/1.1 Host: 这里是url,就不暴露了^_^ Content-Type: multipart/form-data; boundary=xxxxx Connection: keep-alive Accept: */* User-Agent: AFNetWorking3.X%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/1 CFNetwork/808.2.16 Darwin/15.6.0 Content-Length: 32175 Accept-Language: en-us Accept-Encoding: gzip, deflate --xxxxx Content-Disposition: form-data;name="file" img.jpeg --xxxxx Content-Disposition: form-data;name="businessType" CC_USER_CENTER --xxxxx Content-Disposition: form-data;name="fileType" image --xxxxx Content-Disposition:form-data;name="file";filename="img1.jpeg" Content-Type:image/png 这里是图片数据,太长了.我就删了 --xxxxx--
这个请求有三个参数file
,businessType
,fileType
。好比file
参数和他的值就经过以下格式传输:cookie
--xxxxx Content-Disposition: form-data;name="file" img.jpeg
上面这种就是一个参数与之对应的值。协议规定的就是这个格式,没有为何。咱们能够看看图片数据部分:
--xxxxx Content-Disposition:form-data;name="file";filename="img1.jpeg" Content-Type:image/png 这里是图片数据,太长了.我就删了 --xxxxx--
其中name="参数名" filename="文件名" 其中参数名这个要和接收方那边相对应 正常开发中能够去问服务器那边 , 文件名是说在服务器端保存成文件的名字 , 这个参数然并卵 , 由于通常服务端会按照他们本身的要求去处理文件的存储.
下一行是指定类型 , 我这里示例中写的是PNG图片类型 , 这个能够根据你的实际需求的写。若是咱们要上传多分图片或者文件,则只须要按照指定格式就能够了,好比下面就是上传两张图片的请求:
POST /uploadFile HTTP/1.1 Host: 这里是url,就不暴露了^_^ Content-Type: multipart/form-data; boundary=xxxxx Connection: keep-alive Accept: */* User-Agent: AFNetWorking3.X%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/1 CFNetwork/808.2.16 Darwin/15.6.0 Content-Length: 32175 Accept-Language: en-us Accept-Encoding: gzip, deflate --xxxxx Content-Disposition: form-data;name="file" img.jpeg --xxxxx Content-Disposition: form-data;name="businessType" CC_USER_CENTER --xxxxx Content-Disposition: form-data;name="fileType" image --xxxxx Content-Disposition:form-data;name="file";filename="img1.jpeg" Content-Type:image/png 这里是图片1数据,太长了.我就删了 --xxxxx Content-Disposition:form-data;name="file";filename="img2.jpeg" Content-Type:image/png 这里是图片1数据,太长了.我就删了 --xxxxx--
下面是我Demo中一个multipart/form-data
请求的实现代码,分别用NSRULDataTask
和AFHTTPSessionManager
实现,咱们能够发现用第二种方法简便了不少,由于AFN已经帮咱们作好了拼接工做:
//方法一 - (IBAction)updatePic:(id)sender { //请求头参数 NSDictionary *dic = @{ @"businessType":@"CC_USER_CENTER", @"fileType":@"image", @"file":@"img.jpeg" }; //请求体图片数据 NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]); //建立request NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]]; //post方法 [request setHTTPMethod:@"POST"]; // 设置请求头格式为Content-Type:multipart/form-data; boundary=xxxxx //[request setValue:@"multipart/form-data; boundary=xxxxx" forHTTPHeaderField:@"Content-Type"]; AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSURLSessionDataTask *task = [manager POST:url parameters:dic constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { //请求体里面的参数 NSDictionary *bodyDic = @{ @"Content-Disposition":@"form-data;name=\"file\";filename=\"img.jpeg\"", @"Content-Type":@"image/png", }; [formData appendPartWithHeaders:bodyDic body:imageData]; } progress:^(NSProgress * _Nonnull uploadProgress) { NSLog(@"下载进度"); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSLog(@"下载成功:%@",responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"下载失败%@",error); }]; [task resume]; } //方法二 - (IBAction)multipartformPost2:(id)sender { //参数 NSDictionary *dic = @{ @"businessType":@"CC_USER_CENTER", @"fileType":@"image", @"file":@"img.jpeg" }; NSString *boundaryString = @"xxxxx"; NSMutableString *str = [NSMutableString string]; [dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { [str appendFormat:@"--%@\r\n",boundaryString]; [str appendFormat:@"%@name=\"%@\"\r\n\r\n",@"Content-Disposition: form-data;",key]; [str appendFormat:@"%@\r\n",obj]; }]; NSMutableData *requestMutableData=[NSMutableData data]; [str appendFormat:@"--%@\r\n",boundaryString]; [str appendFormat:@"%@:%@",@"Content-Disposition",@"form-data;"]; [str appendFormat:@"%@=\"%@\";",@"name",@"file"]; [str appendFormat:@"%@=\"%@\"\r\n",@"filename",@"img1.jpeg"]; [str appendFormat:@"%@:%@\r\n\r\n",@"Content-Type",@"image/png"]; //转换成为二进制数据 [requestMutableData appendData:[str dataUsingEncoding:NSUTF8StringEncoding]]; NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]); //文件数据部分 [requestMutableData appendData:imageData]; //添加结尾boundary [requestMutableData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundaryString] dataUsingEncoding:NSUTF8StringEncoding]]; //建立一个请求对象 NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]]; //post方法 [request setHTTPMethod:@"POST"]; // 设置请求头格式为Content-Type:multipart/form-data; boundary=xxxxx [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundaryString] forHTTPHeaderField:@"Content-Type"]; //session NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSURLSessionDataTask *task = [session uploadTaskWithRequest:request fromData:requestMutableData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@",result); }]; [task resume]; }
Multipart/form-data
格式的POST请求总结:
文件类型参数中name="参数名"
必定要和服务端对应, 开发的时候 , 能够问服务端人员,我这里是file
。
上传文件的数据部分使用二进制数据(NSData)
拼接。
上边界部分和下边界部分的字符串 , 最后都要转换成二进制数据(NSData) , 和文件部分的二进制数据拼接在一块儿 , 做为请求体发送给服务器。
每一行末尾须要有必定的`rn·。
接下来我将常使用NSURLSessionDataTask
作一个application/json
的POST请求。而且请求体数据我存储在一个test.txt
文件中,从文件中读取出来而后上传。
//test.txt文件内容 {"name":"huang","phone":"124"}
经过抓包软件个人请求以下,和其余POST请求原理同样,只是拼接请求体的方式不同,而且更具不一样格式的请求体,设置不一样的Content-Type
:
POST /posts HTTP/1.1 Host: jsonplaceholder.typicode.com Content-Type: application/json Connection: keep-alive Accept: application/json User-Agent: AFNetWorking3.X%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/1 CFNetwork/808.2.16 Darwin/15.6.0 Content-Length: 31 Accept-Language: en-us Accept-Encoding: gzip, deflate {"name":"huang","phone":"124"}
下面是我Demo的具体实现
- (IBAction)applicationjsonPOST2:(id)sender { NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://jsonplaceholder.typicode.com/posts"]]; //指请求体的类型。因为咱们test.txt里面的文件是json格式的字符串。因此我这里指定为`application/json` [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; [request addValue:@"application/json" forHTTPHeaderField:@"Accept"]; [request setHTTPMethod:@"POST"]; [request setCachePolicy:NSURLRequestReloadIgnoringCacheData]; [request setTimeoutInterval:20]; NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"txt"]; NSURL *url = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; //使用Block来处理返回数据 NSURLSessionDataTask *task = [session uploadTaskWithRequest:request fromFile:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@",result); }]; [task resume]; }
上面主要讲了对POST请求的分析,主要是AFHTTPSessionManager
并无多少逻辑,他主要是调用AFURLSessionManager
的实现。另外就是经过baseURL
改变了url的拼接过程。下面我就抽出他们的不一样点分析一下:
1 首先多了一个属性
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
这个属性的主要做用就是帮咱们拼接请求头和请求体,从上面的Demo咱们发现不少请求的拼接工做都经过requestSerializer
处理了。若是咱们不手动设置,默认是一个AFHTTPRequestSerializer
对象。具体能够去初始化方法里面看到。
2 重写了securityPolicy
这个属性的setter
方法,增长对于SSLPinningMode
的异常处理。
- (void)setSecurityPolicy:(AFSecurityPolicy *)securityPolicy { //增长对于SSLPinningMode的异常处理。 if (securityPolicy.SSLPinningMode != AFSSLPinningModeNone && ![self.baseURL.scheme isEqualToString:@"https"]) { NSString *pinningMode = @"Unknown Pinning Mode"; switch (securityPolicy.SSLPinningMode) { case AFSSLPinningModeNone: pinningMode = @"AFSSLPinningModeNone"; break; case AFSSLPinningModeCertificate: pinningMode = @"AFSSLPinningModeCertificate"; break; case AFSSLPinningModePublicKey: pinningMode = @"AFSSLPinningModePublicKey"; break; } NSString *reason = [NSString stringWithFormat:@"A security policy configured with `%@` can only be applied on a manager with a secure base URL (i.e. https)", pinningMode]; @throw [NSException exceptionWithName:@"Invalid Security Policy" reason:reason userInfo:nil]; } //调用`AFURLSessionManager`的`securityPolicy`属性的setter方法。 [super setSecurityPolicy:securityPolicy]; }
3 NSCopying和NSSecureCoding协议的实现过程
NSCopying
和NSSecureCoding
协议的实现过程添加了对requestSerializer
,responseSerializer
,securityPolicy
这三个属性的复制。也就是说,用copy方法复制的manager,这三个属性的配置跟着一块儿复制。而父类AFURSSessionManager
只实现了对configuration
的复制。
+ (BOOL)supportsSecureCoding { return YES; } - (instancetype)initWithCoder:(NSCoder *)decoder { NSURL *baseURL = [decoder decodeObjectOfClass:[NSURL class] forKey:NSStringFromSelector(@selector(baseURL))]; //获取当前manager的NSURLSessionConfiguration NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"]; if (!configuration) { NSString *configurationIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"identifier"]; if (configurationIdentifier) { //iOS7和iOS8初始化NSURLSessionConfiguration方法不同。因此要分开处理 #if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1100) configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:configurationIdentifier]; #else configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:configurationIdentifier]; #endif } } //初始化一个新的manager self = [self initWithBaseURL:baseURL sessionConfiguration:configuration]; if (!self) { return nil; } //添加了对`requestSerializer`,`responseSerializer`,`securityPolicy`这三个属性的接档。 self.requestSerializer = [decoder decodeObjectOfClass:[AFHTTPRequestSerializer class] forKey:NSStringFromSelector(@selector(requestSerializer))]; self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))]; AFSecurityPolicy *decodedPolicy = [decoder decodeObjectOfClass:[AFSecurityPolicy class] forKey:NSStringFromSelector(@selector(securityPolicy))]; if (decodedPolicy) { self.securityPolicy = decodedPolicy; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; //添加对baseURL属性的归档 [coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))]; if ([self.session.configuration conformsToProtocol:@protocol(NSCoding)]) { [coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"]; } else { [coder encodeObject:self.session.configuration.identifier forKey:@"identifier"]; } //添加了对`requestSerializer`,`responseSerializer`,`securityPolicy`这三个属性的归档。 [coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))]; [coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))]; [coder encodeObject:self.securityPolicy forKey:NSStringFromSelector(@selector(securityPolicy))]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration]; //添加了对`requestSerializer`,`responseSerializer`,`securityPolicy`这三个属性的复制。 HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone]; HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone]; HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone]; return HTTPClient; }
4 HEAD和PUT等方法的实现
我在这里不许备深刻讲这两个方法是如何实现的,由于AFHTTPSessionManager
主要经过他的requestSerializer
属性来实现对HEAD
和PUT
等请求的拼接。我准备分析AFHTTPRequestSerializer
的时候再看这一块是如何实现的。
//经过requestSerializer属性来拼接request对象。 NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];