最早接觸到NSURLProtocol應該是在三四年前,當時有了解到微信讀書好像出了一個框架,是可以實現單個接口的mock,自己研究了一下加了一點東西,通過實現匹配網絡請求,來達到,網絡請求內容讀取指定路徑的json文件,實現mock接口的操作。源碼地址:https://github.com/xindizhiyin2014/JKAPIMock
雖說知道了當時NSURLProtocol可以實現網絡請求的攔截。但其實瞭解並不是特別多。由於最近在推動並行開發,因此對全鏈路的mock操作都進行了梳理,其中使用charles進行mock可以參考下面這篇文章《使用Charles進行mock的三種方式》 但是有的時候mock需要擺脫局域網的限制,因此charles的使用就不能夠滿足我們的需求。
NSURLProtocol可以攔截UIwebView,NSURLConnection,NSURLSession發出的網絡請求。由於UIWebView目前已經廢棄,普通接口發送網絡請求也不再使用NSURLConnection。我這裏就不再一一多說。下面就重點說一下攔截使用NSURLSession發送網絡請求的情況。
使用NSURLSession發送網絡請求前需要進行額外的配置纔可以,具體配置如下:
[JKNetworkConfig sharedConfig].mockBaseUrl = @"https://123.com";
[JKNetworkConfig sharedConfig].isMock = YES;
NSDictionary *config = @{@"GET,/a1":@{}};
[JKMockManager initMockConfig:config];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.protocolClasses = @[[JKMockURLProtocol class]];
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
AFHTTPRequestSerializer *requestSerializer = [AFHTTPRequestSerializer serializer];
sessionManager.securityPolicy = [AFSecurityPolicy defaultPolicy];
sessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSMutableURLRequest *request = [requestSerializer requestWithMethod:@"GET" URLString:@"https://www.baidu.com/a1" parameters:nil error:nil];
NSURLSessionTask *dataTask = [sessionManager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
NSLog(@"AAA %@",responseObject);
}];
[dataTask resume];
由於AFNetworking網絡請求底層是使用NSURLSession來實現的,這裏就使用AFnetworking框架來進行說明。來發送網絡請求之前需要配置NSURLSession的sessionConfiguration,而這個sessionConfiguration有一個屬性protocolClasses用來保存NSURLProtocol相關的子類。我這邊用了我自己實現的子類。配置好以後,JKMockURLProtocol會被自動註冊。此時發送網絡請求可以發現網絡請求已經被攔截到了。下面是JKMockURLProtocol的源碼,大家可以看看
@interface JKMockURLProtocol()
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
@end
@implementation JKMockURLProtocol
static AFHTTPSessionManager *_jkSessionManager = nil;
- (AFHTTPSessionManager *)sessionManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_jkSessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
});
return _jkSessionManager;
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
if ([self matchMethod:request]
&& [self matchQueryKeyParams:request]
&& [self matchesHeaders:request]
&& [self matchBody:request]
) {
return YES;
}
return NO;
}
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *newRequest = [request mutableCopy];
NSString *host = newRequest.URL.host;
NSString *url = newRequest.URL.absoluteString;
NSNumber *port = newRequest.URL.port;
NSString *scheme = newRequest.URL.scheme;
NSString *baseUrl = nil;
if (port) {
baseUrl = [NSString stringWithFormat:@"%@://%@:%@",scheme,host,port];
} else {
baseUrl = [NSString stringWithFormat:@"%@://%@",scheme,host];
}
url = [url stringByReplacingOccurrencesOfString:baseUrl withString:[JKNetworkConfig sharedConfig].mockBaseUrl];
NSURL *mockUrl = [NSURL URLWithString:url];
[newRequest setURL:mockUrl];
return newRequest;
}
- (void)startLoading
{
NSURLSessionTask *dataTask = [[self sessionManager] dataTaskWithRequest:self.request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocolDidFinishLoading:self];
[self.client URLProtocol:self didFailWithError:error];
}];
[dataTask resume];
}
- (void)stopLoading
{
}
/**
判斷方法是否相同
*/
+ (BOOL)matchMethod:(NSURLRequest *)request
{
NSString *httpMethod = [self getMockHttpMethodWithRequest:request];
if (httpMethod && [httpMethod caseInsensitiveCompare:request.HTTPMethod] == NSOrderedSame) {
return YES;
}
return NO;
}
/**
匹配query參數:關鍵參數是否相同
*/
+ (BOOL)matchQueryKeyParams:(NSURLRequest *)request
{
NSDictionary *params = [JKMockManager paramsWithURL:request.URL.absoluteString];
NSDictionary *mockQueryParams = [self getMockQueryParamsWithRequest:request];
for (NSDictionary *dic in mockQueryParams) {
BOOL status = NO;
for (NSDictionary *tmpDic in params) {
if ([tmpDic isEqualToDictionary:dic]) {
status = YES;
break;
}
}
if (!status) {
return NO;
}
}
return YES;
}
/**
判斷頭關鍵參數是否相同
*/
+ (BOOL)matchesHeaders:(NSURLRequest *)request
{
NSDictionary *mockHeaders = [self getMockHeadersWithRequest:request];
NSDictionary *headers = request.allHTTPHeaderFields;
for (NSDictionary *dic in mockHeaders) {
BOOL status = NO;
for (NSDictionary *tmpDic in headers) {
if ([tmpDic isEqualToDictionary:dic]) {
status = YES;
break;
}
}
if (!status) {
return NO;
}
}
return YES;
}
/**
判斷body 只匹配關鍵參數
*/
+ (BOOL)matchBody:(NSURLRequest *)request
{
NSDictionary *mockParams = [self getMockBodyParamsWithRequest:request];
if (!mockParams) {
return YES;
}
NSData *reqBody = request.HTTPBody;
NSString *reqBodyString = [[NSString alloc] initWithData:reqBody encoding:NSUTF8StringEncoding];
NSDictionary *params = [JKMockManager convertDictionaryWithURLParams:reqBodyString];
for (NSDictionary *dic in mockParams) {
BOOL status = NO;
for (NSDictionary *tmpDic in params) {
if ([tmpDic isEqualToDictionary:dic]) {
status = YES;
break;
}
}
if (!status) {
return NO;
}
}
return YES;
}
/// 根據request獲取本地配置的需要mock的請求的請求方法
/// @param request request
+ (NSString *)getMockHttpMethodWithRequest:(NSURLRequest *)request
{
NSString *apiName = [self getAPINameWithRequest:request];
NSString *method = [request.HTTPMethod uppercaseString];
return [JKMockManager mockHttpMethodWithApiName:apiName method:method];
}
/// 根據request按照制定規則解析獲取APIName
/// @param request request
+ (NSString *)getAPINameWithRequest:(NSURLRequest *)request
{
NSString *path = [request.URL path];
NSString *apiName = path;
return apiName;
}
+ (NSDictionary *)getMockQueryParamsWithRequest:(NSURLRequest *)request
{
NSString *apiName = [self getAPINameWithRequest:request];
NSString *method = [request.HTTPMethod uppercaseString];
return [JKMockManager mockQueryParamsWithApiName:apiName method:method];
}
+ (NSDictionary *)getMockHeadersWithRequest:(NSURLRequest *)request
{
NSString *apiName = [self getAPINameWithRequest:request];
NSString *method = [request.HTTPMethod uppercaseString];
return [JKMockManager mockHeadersWithApiName:apiName method:method];
}
+ (NSDictionary *)getMockBodyParamsWithRequest:(NSURLRequest *)request
{
NSString *apiName = [self getAPINameWithRequest:request];
NSString *method = [request.HTTPMethod uppercaseString];
return [JKMockManager mockBodyParamsWithApiName:apiName method:method];
}
@end
由於我這邊主要是進行攔截然後重定向到指定域名,大家也可以用來進行接口的數據緩存操作。
源碼下載地址:https://github.com/xindizhiyin2014/JKNetworking.git
更多技術乾貨文章可以掃描下方二維碼: