iOS-网络-NSURLProtocol
2019-11-19 本文已影响0人
Imkata
通过NSURLSession发起的网络,先走的是NSURLProtocol中间层,通过NSURLProtocol中间层处理,最后回到URLSession代理回调层,所以我们可以使用NSURLProtocol中间层篡改服务器返回给我们的数据。
一共分为四步:
- 是否重新定向request
- 修改request
- 重新启动
- 结束
实现这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: canInitWithRequest 返回YES,表示重定向
- 步骤2: canonicalRequestForRequest 不修改,还用原来的request
- 步骤3重新启动的代理方法里面,拼接数据

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初始效果图:

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所有图片,如下:

这里有个实际用途,就是我们可以按照上面的写法给webView写我们自己的缓存,没必要每次都从服务端拿。
3. 如何修改服务器请求地址
如果地址错了在本地如何修改,就不说了,可看上面代码步骤1和步骤2
4. NSURLProtocol可以截获封装好的库的网络请求吗?
其实使用NSURLProtocol还能截获封装好的库的网络请求。
我们封装了如下.a文件,里面就一个简单的网络请求,如下:
调用之前注册:
- (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