這兩天遇到一個問題是,當我用AFNetWorking進行POST請求,并且POST參數(shù)需要GBK編碼時,請求會有問題。
跟蹤代碼后發(fā)現(xiàn)問題出在 AFPercentEscapedStringFromString
函數(shù)里:
NSString * AFPercentEscapedStringFromString(NSString *string) {
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
NSUInteger length = MIN(string.length - index, batchSize);
NSRange range = NSMakeRange(index, length);
// To avoid breaking up character sequences such as ????????
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
這個函數(shù)其實是做URL編碼的,但是用的 stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet
編出來都是UTF8 格式的,有心的你會發(fā)現(xiàn)AF不是可以自定義請求編碼格式的么,就是AFHTTPRequestSerializer
的 stringEncoding
屬性,這里稍微理解下AF請求的參數(shù)轉變流程為如下:
- 你發(fā)送請求的入參 paramDic 字典
- 你的入參paramDic會被解析為 key1=value1&key2=value2 格式的字符串
- 被解析好的 key1=value1&key2=value2 字符串被轉換為data格式傳入請求的 httpbody 里
在這個過程中:
- 1到2的解析過程用到的是上面說的有問題的方法
AFPercentEscapedStringFromString
,所以任何請求這部都用的UTF8編碼 - 而2到3的時候的編碼類型才用到
AFHTTPRequestSerializer
的stringEncoding
屬性
所以很明顯,如果你需要GBK編碼格式編碼請求參數(shù),并且也設定了AFHTTPRequestSerializer
的 stringEncoding
為GBK ,在這個過程中你的參數(shù)從字典變?yōu)槠唇幼址臅r候,會先進行UTF編碼,然后字符串又轉成了GBK格式的data類型,很顯然不對。
所以最終導致,如果你的POSTBody需要GBK或者別的編碼格式編碼,當用AFNetworking時,會編碼錯誤,導致你的服務器解碼是也解不對,導致請求出問題。
這個問題再AFNetworking的Github官方主頁的issue下也有記錄,目前還是open狀態(tài),即未解決。
那怎么解決這個問題呢?
這有兩種解決方法,一種不侵入AF源碼,一種是改AF源碼。
1.通過AFHTTPRequestSerializer 的 setQueryStringSerializationWithBlock: 方法自定義,那么整個請求流程代碼就類似如下:
NSStringEncoding gbkEncoding = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
[manager.requestSerializer setQueryStringSerializationWithBlock:^NSString * _Nonnull(NSURLRequest * _Nonnull request, id _Nonnull parameters, NSError * _Nullable __autoreleasing * _Nullable error) {
NSDictionary *dic = parameters;
return [NSString stringWithFormat:@"%@=%@",[dic.allKeys[0] stringByAddingPercentEscapesUsingEncoding:gbkEncoding],[dic.allValues[0] stringByAddingPercentEscapesUsingEncoding:gbkEncoding]];
}];
manager.requestSerializer.stringEncoding = gbkEncoding;
[manager POST:searchHomeUrlStr parameters:@{@"key":@"GBK編碼值"} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
上面我是舉例寫,代碼不很嚴瑾,你能明白我主要要表達什么意思就行。
每個需要 非UTF8 的請求都需要定義下 setQueryStringSerializationWithBlock:
方法,在這個方法中你需要把你的參數(shù)字典解析成 key1=value1&key2=value2 格式,并且用你需要的編碼方式編碼。
2.直接改源碼
改源碼要改3處地方,都在AFHTTPRequestSerializer
里,第1處和第2處新增兩個方法需要能接收編碼方式,第3處改動,需要 requestBySerializingRequest: withParameters: error:
方法里判斷編碼方式是不是UTF8,如果不是,走你新增的那兩個方法來達到用你自己編碼方式編碼。
新增1:舊AFQueryStringFromParametersWithEncoding
下新增一個下面這個函數(shù),多一個編碼方式入參
NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding encode) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue:encode]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
新增2: 舊URLEncodedStringValue
方法下新增一個下面這個方法,也是多一個編碼方式入參
- (NSString *)URLEncodedStringValue:(NSStringEncoding)encode {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
if (encode == NSUTF8StringEncoding) {
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
} else {
return [NSString stringWithFormat:@"%@=%@", [self.field stringByAddingPercentEscapesUsingEncoding:encode], [self.value stringByAddingPercentEscapesUsingEncoding:encode]];
}
}
}
改動3,下面這個方法里,改動地方看下面代碼塊下面的圖:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
if (self.stringEncoding == NSUTF8StringEncoding) {
query = AFQueryStringFromParameters(parameters);
} else {
query = AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding);
}
break;
}
}
}
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
改動地方:這里做一個是否UTF8的判斷,不是UTF8類型走你新增1里的那個方法,把編碼方式作為入參傳入即可:
做個記錄,如果有更好的改進方法,請留言。謝謝。
主要還是要等官方修這個bug啊~