最早接触到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
更多技术干货文章可以扫描下方二维码: