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