AFNetworking 全解析之 AFURLRequestSerialization

概览

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"];

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

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. 客户端访问一个受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 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对象中。
这段代码注册了这些属性的变更通知。

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];
    }
}];

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

咱们使用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格式并放入请求体中。

相关文章
相关标签/搜索