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格式並放入請求體中。

相關文章
相關標籤/搜索