当前位置:   article > 正文

AFNetworking 全解析之 AFURLRequestSerialization

af会对parameters encode吗

概览

RequestSerilization 是AFNetwroking中对网络请求中request这个概率的封装。它的原型其实是NSURLRequest,将NSURLRequest进行第二次封装,将许多诸如请求头,请求参数格式化, multipar/form data文件上传等进行了简化处理。
总结来说,使用AFURLRequestSerializer有以下几个优点:
1、自动处理的请求参数转义,以及对不同请求方式自动对请求参数进行格式化。
2、实现了multipart/form-data方式的请求。
3、自动处理了User-Agent,Language等请求头。

使用方法

AFURLRequestSerializtion在AF框架中是封装请求这一部分对象的,作为AFHTTPSessionManaager的一个属性被使用。
如:

  1. /// request data parse
  2. manager.requestSerializer = [AFHTTPRequestSerializer serializer];
  3. manager.requestSerializer.timeoutInterval = 30.f;

如果上传时使用的是json格式数据,那么使用AFJSONRequestSerializer:

 manager.requestSerializer = [AFJSONRequestSerializer serializer];

原来存在于NSURLRequest对象的属性,都可以该对象使用如:

[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"accept"];

AFURLRequestSerialization中所涉及的疑难知识点

URL Percent Escape (URL 百分比转议)

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参数中的各个组件分别进行转议,然后再进行拼接。
代码:

  1. - (NSString *)URLEncodedStringValue {
  2. if (!self.value || [self.value isEqual:[NSNull null]]) {
  3. return AFPercentEscapedStringFromString([self.field description]);
  4. } else {
  5. return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
  6. }
  7. }

函数AFPercentEscapedStringFromString 用于将一个字符串进行转义。static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@“;static NSString * const kAFCharactersSubDelimitersToEncode = @"!$ 定义了不需要被转义字符集,然后从URLQueryAllowedCharacterSet移除这些字符集。 实际在进行转义时,该函数并不是直接使用 string stringByAddingPercentEncodingWithAllowedCharacters 进行转义,而是使用rangeOfComposedCharacterSequencesForRange 来算出该自符串下每一个字的位置,然后对每一个字进行转义,再拼接到一起。

  1. range = [string rangeOfComposedCharacterSequencesForRange:range];
  2. NSString *substring = [string substringWithRange:range];
  3. NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
  4. [escaped appendString:encoded];

这样做的原因是,我们平常书写的字符, 并不全部都是用唯一的一个16位字符来表示, 而是有一部分用两个16位字符来表示, 这就是surrogate pairs的概念. 如果还是用上面的方法遍历字符串, 就会出现”断字”。

URL QueryStringFormat url请求的参数格式

我们发送一个请求,无论是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 Basic Authentication (http基本认证)

http 认证是基于质询/回应的,即我们在NSURLSession代理中常见到的challenge。
最简单的认证方式为基本认证,它的密码是通过明文来传递的。
基本认证步骤:

  1. 1. 客户端访问一个受http基本认证保护的资源。
  2. 2. 服务器返回401状态,要求客户端提供用户名和密码进行认证。
  3. 401 Unauthorized
  4. WWW-Authenticate: Basic realm="WallyWorld"
  5. 3. 客户端将输入的用户名密码用Base64进行编码后,采用非加密的明文方式传送给服务器。
  6. Authorization: Basic xxxxxxxxxx.
  7. 4. 如果认证成功,则返回相应的资源。如果认证失败,则仍返回401状态,要求重新进行认证。

摘要认证:服务器端以nonce进行质询,客户端以用户名,密码,nonce,HTTP方法,请求的URI等信息为基础产生的response信息进行认证的方式。
摘要认证步骤:

  1. 1. 客户端访问一个受http摘要认证保护的资源。
  2. 2. 服务器返回401状态以及nonce等信息,要求客户端进行认证。

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest
realm="testrealm@host.com",
qop="auth,auth-int",
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
opaque="5ccc069c403ebaf9f0171e9517f40e41"

  1. 3. 客户端将以用户名,密码,nonce值,HTTP方法, 和被请求的URI为校验值基础而加密(默认为MD5算法)的摘要信息返回给服务器。
  2. 认证必须的五个情报:

     ・ 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 Multipart Form Request

在进行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类结构图

以下为AFURLRequestSerialization及相关类的UML图。

clipboard.png

从图中可以看出,该组件中的主要类为AFURLRequestSerialization,该类在运行过程中使用到的类则有AFQueryStringPair和AFStreamingMultipartFormData。
AFQueryStringPair类封装了请求过程中所用到的键值对参数,一般的post方法、Get方法,均使用这个类来组建请求参数。
AFStreamingMultipartFormData继承于接口AFMultipartFormData,用于组件multipart/form-data类型请求的参数。而AFStreamingMultipartFormData类中的主要成员则是AFMultipartBodyStream,它代表用于生成multipart/form-data请求体的一个流。该对象又使用一个mutableArray来维护一系列的AFHTTPBodyPart对象,该对象表示一个multipart/form-data请求体中的每一个请求域。

几个关键过程分析

NSURLRequest参数和HTTPHeader参数的传递

AFHTTPRequestSerializer对象实质上是对NSURLRequest对象的封装,然而实际上我们最后使用的,仍然是NSURLRequest对象。AFHTTPRequestSerializer对象将大部分NSURLRequest对象所需要用到的属性导出,并使用KVO的方式统一进行处理,最终生成NSURLRequest时,这些更改的属性会被重新赋于NSURLRequest对象中。
这段代码注册了这些属性的变动通知。

  1. for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
  2. if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
  3. [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
  4. }
  5. }

每当相应的属性发生变动,AFHTTPRequestSerilizer对角便宜该属性名保存起来:

  1. - (void)observeValueForKeyPath:(NSString *)keyPath
  2. ofObject:(__unused id)object
  3. change:(NSDictionary *)change
  4. context:(void *)context
  5. {
  6. if (context == AFHTTPRequestSerializerObserverContext) {
  7. if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
  8. [self.mutableObservedChangedKeyPaths removeObject:keyPath];
  9. } else {
  10. [self.mutableObservedChangedKeyPaths addObject:keyPath];
  11. }
  12. }
  13. }

然后在生成NSURLRequest对象过程中,将这些变更过的属性值进行赋值:

  1. for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
  2. if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
  3. [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
  4. }
  5. }

此外,请求的header值也会在生成NSURLRequest对象时被赋值

  1. [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
  2. if (![request valueForHTTPHeaderField:field]) {
  3. [mutableRequest setValue:value forHTTPHeaderField:field];
  4. }
  5. }];

GET、POST请求参数的过滤及组建

我们使用AFHTTPRequestSerializer对象时,请求参数均是通过requestWithMethod:URLString:parameters:方法传递过来,然后在requestBySerializingRequest:withParameters:error:方法中进行组建。
AFURLRequestSerializer对象首先提供了一个queryStringSerialization回调,用于将请求参数组合成一个查询字符串。

  1. if (self.queryStringSerialization) {
  2. NSError *serializationError;
  3. query = self.queryStringSerialization(request, parameters, &serializationError);
  4. if (serializationError) {
  5. if (error) {
  6. *error = serializationError;
  7. }
  8. return nil;
  9. }
  10. }

如果没有这个回调,那么该对象会使用函数AFQueryStringFromParameters(NSDictionary *parameters)来组件这个查询字符串。

  1. NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
  2. NSMutableArray *mutablePairs = [NSMutableArray array];
  3. for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
  4. [mutablePairs addObject:[pair URLEncodedStringValue]];
  5. }
  6. return [mutablePairs componentsJoinedByString:@"&"];
  7. }

最终这个查询字符串query会被传递给request对象,或者放入请求体中,或者拼接到url之后。

当然,如果是JSON请求格式,即使用的是AFJSONRequestSerializer,那么会直接将请求参数转为JSON格式并放入请求体中。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/168158?site
推荐阅读
相关标签
  

闽ICP备14008679号