说一说NSURLProtocol

最早接触到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
更多技术干货文章可以扫描下方二维码:
在这里插入图片描述

发布了231 篇原创文章 · 获赞 110 · 访问量 60万+

猜你喜欢

转载自blog.csdn.net/HHL110120/article/details/103116512