RequestSerilization 是AFNetwroking中对网络请求中request这个几率的封装。它的原型实际上是NSURLRequest,将NSURLRequest进行第二次封装,将许多诸如请求头,请求参数格式化, multipar/form data文件上传等进行了简化处理。
总结来讲,使用AFURLRequestSerializer有如下几个优势:
1、自动处理的请求参数转义,以及对不一样请求方式自动对请求参数进行格式化。
2、实现了multipart/form-data方式的请求。
3、自动处理了User-Agent,Language等请求头。html
AFURLRequestSerializtion在AF框架中是封装请求这一部分对象的,做为AFHTTPSessionManaager的一个属性被使用。
如:ios
/// request data parse manager.requestSerializer = [AFHTTPRequestSerializer serializer]; manager.requestSerializer.timeoutInterval = 30.f;
若是上传时使用的是json格式数据,那么使用AFJSONRequestSerializer:算法
manager.requestSerializer = [AFJSONRequestSerializer serializer];
原来存在于NSURLRequest对象的属性,均可以该对象使用如:json
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"accept"];
URL中的字符只能是ascii字符,非ascii字符若是须要出如今url中,则必须进行转义,每个非ascii字符都会被替换成”%hh”的形式,hh为两位16进制数,对应该字符在iso-8859-1字符集里面的编码。这个过程即url转议,或者叫百分比转义, Percent Escape数组
在咱们的ios中,一般在url中遇到有非ascii字符的状况时,通常是直接使用stringByAddingPercentEscapesUsingEncoding:
方法进行转义,这时会将每一个汉字都转换成相应的unicode编码对应的3个%形式。然而使用这种方法进行转义却有一些问题,由于咱们的url参数颇有可能包含&, ?这样的字符,而stringByAddingPercentEscapesUsingEncoding:
并不会对它们进行转义,这样会致使最终得到的编码后的url与预期不符。
举个栗子:
好比urlString = @“汉字&ss”;
转义后就会变为: @“%E6%B1%89%E5%AD%97&ss"
然而显然,咱们须要将&符号也进行转义掉。服务器
那么,如何解决这个问题呢?ios7之后,stringByAddingPercentEncodingWithAllowedCharacters:
方法,这个方法会对字符串进行更完全的转义,但须要传递一个参数:一个字符集,处于这个字符集中的字符不会被转义。
如: [queryWord stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet letterCharacterSet]]
固然,若是你将url 直接使用上述方法进行转义,那么问题有来了,原本做为分隔符的&以及?也都会被转义掉。网络
AFNetwork 的并不会将url进行转义,而是将parameters参数中的各个组件分别进行转议,而后再进行拼接。
代码:app
- (NSString *)URLEncodedStringValue { if (!self.value || [self.value isEqual:[NSNull null]]) { return AFPercentEscapedStringFromString([self.field description]); } else { return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])]; } }
函数AFPercentEscapedStringFromString 用于将一个字符串进行转义。static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@“;
和static NSString * const kAFCharactersSubDelimitersToEncode = @"!$
定义了不须要被转义字符集,而后从URLQueryAllowedCharacterSet
移除这些字符集。 实际在进行转义时,该函数并非直接使用 string stringByAddingPercentEncodingWithAllowedCharacters 进行转义,而是使用rangeOfComposedCharacterSequencesForRange
来算出该自符串下每个字的位置,而后对每个字进行转义,再拼接到一块儿。框架
range = [string rangeOfComposedCharacterSequencesForRange:range]; NSString *substring = [string substringWithRange:range]; NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; [escaped appendString:encoded];
这样作的缘由是,咱们日常书写的字符, 并不所有都是用惟一的一个16位字符来表示, 而是有一部分用两个16位字符来表示, 这就是surrogate pairs的概念. 若是仍是用上面的方法遍历字符串, 就会出现”断字”。tcp
咱们发送一个请求,不管是get,仍是post,发送出去的请求体都是一个字符串。那么这个字符串如何表示数组、字典等格式呢?
假设咱们发出去的请求参数为 {“name”: “jack”, “specialty”: [“Guitar”, “Coding”], “families”: {“wife”: “rose”, “son”: “jacky"}}
那么实际发出去的请求参数为: name=jack&specialty[]=Cuitar&specialty[]=Coding&families[wife]=rose&families[son]=jacky,
即 数组参数为: key[]=value1&key[]=value2的格式,
字典参数为: key[subkey] = value的方式
具体实现可参照 函数AFQueryStringPairsFromKeyAndValue
http 认证是基于质询/回应的,即咱们在NSURLSession代理中常见到的challenge。
最简单的认证方式为基本认证,它的密码是经过明文来传递的。
基本认证步骤:
1. 客户端访问一个受http基本认证保护的资源。 2. 服务器返回401状态,要求客户端提供用户名和密码进行认证。 401 Unauthorized WWW-Authenticate: Basic realm="WallyWorld" 3. 客户端将输入的用户名密码用Base64进行编码后,采用非加密的明文方式传送给服务器。 Authorization: Basic xxxxxxxxxx. 4. 若是认证成功,则返回相应的资源。若是认证失败,则仍返回401状态,要求从新进行认证。
摘要认证:服务器端以nonce进行质询,客户端以用户名,密码,nonce,HTTP方法,请求的URI等信息为基础产生的response信息进行认证的方式。
摘要认证步骤:
1. 客户端访问一个受http摘要认证保护的资源。 2. 服务器返回401状态以及nonce等信息,要求客户端进行认证。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest
realm="testrealm@host.com",
qop="auth,auth-int",
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
opaque="5ccc069c403ebaf9f0171e9517f40e41"
3. 客户端将以用户名,密码,nonce值,HTTP方法, 和被请求的URI为校验值基础而加密(默认为MD5算法)的摘要信息返回给服务器。 认证必须的五个情报:
・ realm : 响应中包含信息
・ nonce : 响应中包含信息
・ username : 用户名
・ digest-uri : 请求的URI
・ response : 以上面四个信息加上密码信息,使用MD5算法得出的字符串。
Authorization: Digest
username="Mufasa", ← 客户端已知信息
realm="testrealm@host.com", ← 服务器端质询响应信息
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ← 服务器端质询响应信息
uri="/dir/index.html", ← 客户端已知信息
qop=auth, ← 服务器端质询响应信息
nc=00000001, ← 客户端计算出的信息
cnonce="0a4f113b", ← 客户端计算出的客户端nonce
response="6629fae49393a05397450978507c4ef1", ← 最终的摘要信息 ha3
opaque="5ccc069c403ebaf9f0171e9517f40e41" ← 服务器端质询响应信息
4. 若是认证成功,则返回相应的资源。若是认证失败,则仍返回401状态,要求从新进行认证。
在进行http请求的时候,尤为文件上传的时候,咱们经常会使用mutlipart-form-data这个请求类型,那么,什么是multipart-form-data Request呢?
根据标准的http协议,咱们的请求只能是OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE这几种。http协议是以ASCII码传输,创建在tcp, ip协议之上的应用层规范,http请求被分为了三个部分:状态行、请求头、请求体。
实际上,原始的http请求是不支持什么multipart或者www-form-urlencoded的,而全部的这些类型,其实是对http请求体的一次封装。
multipart/form-data是这样的一种请求类型:它基于post方法,它的头信息中必须包含Content-Type=multipart/form-data。同时它的请求头中包含一个分隔符,将请求体分隔开来。
具体的请求头以下:Content-Type: multipart/form-data; boundary=${bound}
这里的${bound}是一个自定义的分隔符。
multipart/form-data的请求体也是一个字符串,不过与post方法不一样,post的请求体是简单的key=value值联接,而multipart/form-data则添加了分隔符来将请求体分隔成不一样的部分,每个部分均为一个请求域,如:
--${bound}
Content-Disposition: form-data; name="Filename"
HTTP.pdf
--${bound}
Content-Disposition: form-data; name="file000"; filename="HTTP协议详解.pdf"
Content-Type: application/octet-stream
%PDF-1.5
file content
%%EOF
--${bound}
Content-Disposition: form-data; name="Upload"
Submit Query
--${bound}--
其中,${bound}为以前头信息中的分隔符。每个域,均以—${bound}起头,最后以—${bound}—结尾。每一个域均包含如下信息: Content-Disposition:form-data;name=xxx;...
以及Content-Type:...
如下为AFURLRequestSerialization及相关类的UML图。
从图中能够看出,该组件中的主要类为AFURLRequestSerialization,该类在运行过程当中使用到的类则有AFQueryStringPair和AFStreamingMultipartFormData。
AFQueryStringPair类封装了请求过程当中所用到的键值对参数,通常的post方法、Get方法,均使用这个类来组建请求参数。
AFStreamingMultipartFormData继承于接口AFMultipartFormData,用于组件multipart/form-data类型请求的参数。而AFStreamingMultipartFormData类中的主要成员则是AFMultipartBodyStream,它表明用于生成multipart/form-data请求体的一个流。该对象又使用一个mutableArray来维护一系列的AFHTTPBodyPart对象,该对象表示一个multipart/form-data请求体中的每个请求域。
AFHTTPRequestSerializer对象实质上是对NSURLRequest对象的封装,然而实际上咱们最后使用的,仍然是NSURLRequest对象。AFHTTPRequestSerializer对象将大部分NSURLRequest对象所须要用到的属性导出,并使用KVO的方式统一进行处理,最终生成NSURLRequest时,这些更改的属性会被从新赋于NSURLRequest对象中。
这段代码注册了这些属性的变更通知。
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; } }
每当相应的属性发生变更,AFHTTPRequestSerilizer对角便宜该属性名保存起来:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(__unused id)object change:(NSDictionary *)change context:(void *)context { if (context == AFHTTPRequestSerializerObserverContext) { if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) { [self.mutableObservedChangedKeyPaths removeObject:keyPath]; } else { [self.mutableObservedChangedKeyPaths addObject:keyPath]; } } }
而后在生成NSURLRequest对象过程当中,将这些变动过的属性值进行赋值:
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } }
此外,请求的header值也会在生成NSURLRequest对象时被赋值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }];
咱们使用AFHTTPRequestSerializer对象时,请求参数均是经过requestWithMethod:URLString:parameters:方法传递过来,而后在requestBySerializingRequest:withParameters:error:方法中进行组建。
AFURLRequestSerializer对象首先提供了一个queryStringSerialization回调,用于将请求参数组合成一个查询字符串。
if (self.queryStringSerialization) { NSError *serializationError; query = self.queryStringSerialization(request, parameters, &serializationError); if (serializationError) { if (error) { *error = serializationError; } return nil; } }
若是没有这个回调,那么该对象会使用函数AFQueryStringFromParameters(NSDictionary *parameters)
来组件这个查询字符串。
NSString * AFQueryStringFromParameters(NSDictionary *parameters) { NSMutableArray *mutablePairs = [NSMutableArray array]; for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { [mutablePairs addObject:[pair URLEncodedStringValue]]; } return [mutablePairs componentsJoinedByString:@"&"]; }
最终这个查询字符串query会被传递给request对象,或者放入请求体中,或者拼接到url以后。
固然,若是是JSON请求格式,即便用的是AFJSONRequestSerializer,那么会直接将请求参数转为JSON格式并放入请求体中。