美文网首页
iOS-网络-NSURLProtocol

iOS-网络-NSURLProtocol

作者: Imkata | 来源:发表于2019-11-19 14:40 被阅读0次

通过NSURLSession发起的网络,先走的是NSURLProtocol中间层,通过NSURLProtocol中间层处理,最后回到URLSession代理回调层,所以我们可以使用NSURLProtocol中间层篡改服务器返回给我们的数据。

一共分为四步:

  1. 是否重新定向request
  2. 修改request
  3. 重新启动
  4. 结束

实现这4个步骤,我们就能想给什么数据就给什么数据。

1. 假如我们有需求要在请求的数据的最前面添加一串"123456"字符

首先,一个网络请求如下,先在config里面注册一下NSURLProtocol

- (void)netLoadDelegateStyle{
    
    NSString *urlstr = [NSString stringWithFormat:@"%@?versions_id=1&system_type=1", URLPath];
    NSURL *url = [NSURL URLWithString:urlstr];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:@"GET"];
    [request setHTTPBody:nil];
    
    NSURLSessionConfiguration *config =  [NSURLSessionConfiguration defaultSessionConfiguration];
    //使用代理方式,设置在config里面
    config.protocolClasses = [NSArray arrayWithObject:[EOCURLProtocol class]];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
    
    NSURLSessionTask *task = [session dataTaskWithRequest:request];
    
    [task resume];
}

子类化NSURLProtocol,重写它的方法,如下:

/*
 分为四步:
 1 是否重新定向request
 2 修改request
 3 重新启动
 4 结束
 实现这四个步骤, 我们就能想给什么数据给什么数据
 */

/* 步骤1:是否重新定向 YES是重定向,NO 不修改 */
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
    NSLog(@"=========%@", request.URL);
    return YES;
}

//步骤2:修改request
//canonical 规范
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest *)request{
    NSMutableURLRequest *newRequest = request.mutableCopy;
    return newRequest;
}

//步骤3:重新启动
- (void)startLoading{
   // [self loadLocalData];
   [self reloadNet];
}

//重新加载 - 使用代理, 在相应的代理方法里面走上面的三步
- (void)reloadNet{
    
    NSMutableURLRequest *newRequest = [self.request mutableCopy];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
    
    NSURLSessionTask *task = [session dataTaskWithRequest:newRequest];
    
    [task resume];
}

//步骤4:结束
- (void)stopLoading{

}

#pragma mark - 代理方法 
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    completionHandler(NSURLSessionResponseAllow);
    //第一步
    [self.client URLProtocol:self didReceiveResponse:[NSURLResponse new] cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    //最前面加123456
    NSMutableData *eocdata = [@"123456" dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
    [eocdata appendData:data];
    [self.client URLProtocol:self didLoadData:eocdata];
    
    //第二步
   // [self.client URLProtocol:self didLoadData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    //第三步
    [self.client URLProtocolDidFinishLoading:self];
}

上面的代码:

  1. 步骤1: canInitWithRequest 返回YES,表示重定向
  2. 步骤2: canonicalRequestForRequest 不修改,还用原来的request
  3. 步骤3重新启动的代理方法里面,拼接数据
运行后,结果如下: 简单修改数据.png

2. 拦截weView的请求,替换图片

我们都知道UIWebView内部有好多网络请求的,我们也可以使用NSURLProtocol修改UIWebView里面的网络请求,比如:替换webView里面所有的图片

首先加载webView之前,先注册EOCURLProtocol

[NSURLProtocol registerClass:[EOCURLProtocol class]];
NSURL *url = [NSURL URLWithString:@"http://huaban.com"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
[_webView loadRequest:request];
webView初始效果图: 初始.png

EOCURLProtocol.m代码

/* 步骤1:是否重新定向 YES是重定向,NO 不修改 */
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
    
    NSLog(@"=========%@", request.URL);
    
    //如果url是图片, 就重定向
    NSString *urlstr = request.URL.path;
    if ([urlstr hasSuffix:@".png"]) {
        return YES;
    }

    //如果使用了错误的baidu.com地址, 就重定向
    NSString *domain = request.URL.host;
    if ([domain isEqualToString:@"www.baidu.com"]) {
        return YES;
    }

    return NO;
}

// 步骤2:修改request
//canonical 规范
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest *)request{
    
    NSMutableURLRequest *newRequest = request.mutableCopy;
    //将错误的baidu.com 改成 svr.tuliu.com
    NSString *domain = request.URL.host;
    if ([domain isEqualToString:@"www.baidu.com"]) {
        NSString *urlstr = request.URL.absoluteString;
        urlstr = [urlstr stringByReplacingOccurrencesOfString:@"www.baidu.com" withString:@"svr.tuliu.com"];
        newRequest.URL = [NSURL URLWithString:urlstr];
    }
    return newRequest;
}

//步骤3:重新启动
- (void)startLoading{
    [self loadLocalData];
//    [self reloadNet];
}

//用本地数据
- (void)loadLocalData{
    
    //分三步修改
    //1.didReceiveResponse
    [self.client URLProtocol:self didReceiveResponse:[NSURLResponse new] cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    UIImage *localImage = [UIImage imageNamed:@"1.png"];
    NSData *localImageData = UIImagePNGRepresentation(localImage);
    //2.didLoadData
    [self.client URLProtocol:self didLoadData:localImageData];
    //3.DidFinishLoading
    //这三个方法和URLSession代理方法里面的三个方法相对应
    [self.client URLProtocolDidFinishLoading:self];
}

// 步骤4:结束
- (void)stopLoading{
    
}

在上面的canInitWithRequest方法中,通过打印可以验证,webView的确有很多网络请求,包括下面的图片请求,我们要做的就是替换它们

2019-11-19 11:50:58.180000+0800 EOCURLProtocol[15154:1232892] =========https://s11.cnzz.com/z_stat.php?id=1256903590
2019-11-19 11:50:58.182316+0800 EOCURLProtocol[15154:1232892] =========https://huaban.com/img/mobile/bg.png
2019-11-19 11:51:01.145290+0800 EOCURLProtocol[15154:1232962] =========https://huaban.com/img/mobile/bg.png
2019-11-19 11:51:01.145452+0800 EOCURLProtocol[15154:1232962] =========https://huaban.com/img/mobile/bg.png
2019-11-19 11:51:01.145546+0800 EOCURLProtocol[15154:1232962] =========https://huaban.com/img/mobile/bg.png
2019-11-19 11:51:01.145650+0800 EOCURLProtocol[15154:1232962] =========https://huaban.com/img/mobile/bg.png
2019-11-19 11:51:01.145790+0800 EOCURLProtocol[15154:1232962] =========https://huaban.com/img/mobile/bg.png
上面的代码,在步骤1做判断是否重定向,在步骤3把图片使用本地图片替换,就实现了替换webView所有图片,如下: 替换后.png

这里有个实际用途,就是我们可以按照上面的写法给webView写我们自己的缓存,没必要每次都从服务端拿

3. 如何修改服务器请求地址

如果地址错了在本地如何修改,就不说了,可看上面代码步骤1和步骤2

4. NSURLProtocol可以截获封装好的库的网络请求吗?

其实使用NSURLProtocol还能截获封装好的库的网络请求。

我们封装了如下.a文件,里面就一个简单的网络请求,如下: .a.png

调用之前注册:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //截获封装好的库的网络
    [NSURLProtocol registerClass:[EOCURLProtocol class]];
    [NetTest netLoadBlock];
}

在canInitWithRequest方法里面打印,如下:

2019-11-19 14:02:24.403951+0800 EOCURLProtocol[16688:1386553] =========http://svr.tuliu.com/center/front/app/util/updateVersions?versions_id=1&system_type=1
2019-11-19 14:02:24.426204+0800 EOCURLProtocol[16688:1386553] =========http://svr.tuliu.com/center/front/app/util/updateVersions?versions_id=1&system_type=1

可以发现截获到了,所以封装给别人用的一些库最好不使用NSURLSession,因为可以被截获到,可以使用更底层的CFNetwork。

补充:如何使用CFNetwork写一个简单的网络请求?

/*
 ASIHttp就是用CF写的
 */
#import "CFNetworkVC.h"
#import <CFNetwork/CFNetwork.h>

void __CFReadStreamClientCallBack(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo){
    
    CFNetworkVC *tmpSelf = (__bridge CFNetworkVC*)clientCallBackInfo;
    if (type == kCFStreamEventOpenCompleted) {
        NSLog(@"开始了");
        
    }else if(type == kCFStreamEventHasBytesAvailable){
        
        NSLog(@"数据");
        
        UInt8 buff[4096];
        CFIndex lenght = CFReadStreamRead(stream, buff, 4096);
        //NSLog(@"%s", buff);
        [tmpSelf handleNetData:[NSData dataWithBytes:buff length:lenght]];
        
    }else if(type == kCFStreamEventEndEncountered || type == kCFStreamEventErrorOccurred){
        
        NSLog(@"结束了");
    }
}

@interface CFNetworkVC ()

@end

@implementation CFNetworkVC

- (void)handleNetData:(NSData*)data{
    NSLog(@"%s", [data bytes]);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self startCFNet];
}

- (void)startCFNet{
    
    // 1 url
    CFStringRef urlStr = CFSTR("http://svr.tuliu.com/center/front/app/util/updateVersions?versions_id=1&system_type=1");
    CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, NULL);
    
    // 2 request
    CFStringRef method = CFSTR("GET");
    CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, method, url, kCFHTTPVersion1_1);
    
   // CFHTTPMessageSetBody(request, <#CFDataRef  _Nonnull bodyData#>)
    // 3 发送
    
    CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
    
    //
    CFOptionFlags streamStatus = kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
    
    CFStreamClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
    CFReadStreamSetClient(readStream, streamStatus, __CFReadStreamClientCallBack, &context);
    
    CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    CFReadStreamOpen(readStream);
    
    // 4 接收数据  通过readStream来操作
}
@end

Demo地址:NSURLProtocol

相关文章

网友评论

      本文标题:iOS-网络-NSURLProtocol

      本文链接:https://www.haomeiwen.com/subject/ohxwictx.html