美文网首页
通过URLProtocol拦截网络请求后进行ip替换或请求代理设

通过URLProtocol拦截网络请求后进行ip替换或请求代理设

作者: 枫叶情结 | 来源:发表于2018-12-28 18:33 被阅读23次

    一、关于 NSURLProtocol

    NSURLProtocol作为URL Loading System中的一个独立部分存在,能够拦截所有的URL Loading System发出的网络请求,拦截之后便可根据需要做各种自定义处理,是iOS网络层的一个终极利器。虽然名叫NSURLProtocol,但它却不是协议。它是一个抽象类。我们要使用它的时候需要创建它的一个子类。
    NSURLProtocol在iOS系统中大概处于这样一个位置:


    Snip20181229_3.png

    二、NSURLProtocol能拦截哪些网络请求

    NSURLProtocol能拦截所有基于URL Loading System的网络请求。
    URL Loading System的图如下:


    872807-b7f17b6fbaf25831.png

    所以,可以拦截的网络请求包括NSURLSession,NSURLConnection。现在主流的iOS网络库,例如AFNetworking,Alamofire等网络库都是基于NSURLSession或NSURLConnection的,所以这些网络库的网络请求都可以被NSURLProtocol所拦截。

    PS:基于CFNetwork的网络请求,以及WKWebView的请求是无法拦截的。例如ASIHTTPRequest,MKNetwokit等网路库都是基于CFNetwork的,所以这些网络库的网络请求无法被NSURLProtocol拦截。

    三、使用

    使用NSURLProtocol的主要可以分为5个步骤:


    Snip20181229_4.png

    1、创建NSURLProtocol子类

    由于NSURLProtocol是一个抽象类,要使用它的时候需要创建它的一个子类。.m文件如下:

    #import "HJQURLProtocol.h"
    
    static NSString * const URLProtocolHandledKey = @"URLProtocolHandledKey";
    
    @interface HJQURLProtocol()<NSURLSessionDelegate>
    @property(nonatomic,strong)NSURLSession * session;
    @end
    
    @implementation HJQURLProtocol
    
    +(BOOL)canInitWithRequest:(NSURLRequest *)request
    {
      //是http或https则拦截处理
      NSString * scheme = [[request.URL scheme] lowercaseString];//获取URL转小写
      if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"])
      {
        //有拦截处理过的则不再拦截,否则会在这死循环
        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request])
        {
          return NO;
        }
        else
        {
          return YES;
        }
      }
      else
      {
        return NO;
      }
    }
    
    //改变请求request
    +(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
    {
      return request;
    }
    
    //开始请求
    -(void)startLoading
    {
      NSMutableURLRequest * mutableRequest = [[self request] mutableCopy];
      //标识该request已经处理过了,防止无限循环
      [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableRequest];
      
      
      //设置代理
      NSString * proxyHost = @"127.0.0.1";
      NSNumber * proxyPort = [NSNumber numberWithInt:8888];
      
      //创建一个代理服务器,包括HTTP或HTTPS代理,当然还可以添加SOCKS,FTP,RTSP等
      NSDictionary * proxyDic = @{
                                  @"HTTPEnable": @YES,
                                  @"HTTPProxy": proxyHost,
                                  @"HTTPPort": proxyPort,
                                  @"HTTPSEnable": @YES,
                                  @"HTTPSProxy": proxyHost,
                                  @"HTTPSPort": proxyPort
                                  };
      
      NSURLSessionConfiguration * configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];//创建一个临时会话配置
      configuration.connectionProxyDictionary = proxyDic;
      
      //网络请求
      self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
      NSURLSessionTask * task = [self.session dataTaskWithRequest:self.request];
      [task  resume];//开始任务
      
    }
    
    //停止请求
    -(void)stopLoading
    {
      [self.session invalidateAndCancel];
      self.session = nil;
    }
    
    #pragma mark ---- NSURLSessionDelegate
    /*
      NSURLSessionDelegate接到数据后,通过URLProtocol传出去
     */
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
      if (error)
      {
        [self.client URLProtocol:self didFailWithError:error];
      }
      else
      {
        [self.client URLProtocolDidFinishLoading:self];
      }
    }
    
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
      
      [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
      completionHandler(NSURLSessionResponseAllow);
      
    }
    
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
    {
      [self.client URLProtocol:self didLoadData:data];
    }
    
    @end
    

    2、注册protocol

    基于NSURLConnection[NSURLSession sharedSession]创建的网络请求,在AppDelegate的didFinishLaunchingWithOptions方法中调用registerClass方法即可。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      // Override point for customization after application launch.
      
      //注册protocol
      [NSURLProtocol registerClass:[HJQURLProtocol class]];
      
      return YES;
    }
    

    3、拦截用户的网络请求

    首先,在拦截到网络请求后会先调用+(BOOL)canInitWithRequest:(NSURLRequest *)request方法。我们可以在该方法里进行是否处理这个拦截的逻辑。如设置只对拦截到的http或https请求进行处理。

    +(BOOL)canInitWithRequest:(NSURLRequest *)request
    {
      //是http或https则拦截处理
      NSString * scheme = [[request.URL scheme] lowercaseString];//获取URL转小写
      if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"])
      {
        //有拦截处理过的则不再拦截,否则会在这死循环
        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request])
        {
          return NO;
        }
        else
        {
          return YES;
        }
      }
      else
      {
        return NO;
      }
    }
    

    接着,会调用+(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request在该方法中,我们可以对request进行处理。例如修改头部信息等。最后返回一个处理后的request实例。也可以在该方法里面将用户的请求域名替换成别的域名:

    +(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
    {
      if ([request.URL host].length == 0)
      {
        return request;
      }
    
      NSString * originUrlStr = [request.URL absoluteString];
      NSString * originHostStr = [request.URL host];
      NSRange hostRange = [originUrlStr rangeOfString:originHostStr];
      
      if (hostRange.location == NSNotFound)
      {
        return request;
      }
    
      
      //定向到百度搜索
      NSString * ip = @"www.baidu.com";
      NSString * urlStr = [originUrlStr stringByReplacingCharactersInRange:hostRange withString:ip];
      NSURL * url = [NSURL URLWithString:urlStr];
      request.URL = url;
    
      return request;
    }
    

    4、转发

    -(void)startLoading将处理过的request重新发送出去。发送的形式,可以是基于NSURLConnection,NSURLSession甚至CFNetwork。我们也可以在该方法里面设置网络代理,如下我们设置一个代理后,重新创建一个NSURLSession将网络请求发送出去:

    //开始请求
    -(void)startLoading
    {
      NSMutableURLRequest * mutableRequest = [[self request] mutableCopy];
      //标识该request已经处理过了,防止无限循环
      [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableRequest];
      
      //设置代理
      NSString * proxyHost = @"127.0.0.1";
      NSNumber * proxyPort = [NSNumber numberWithInt:8888];
      
      //创建一个代理服务器,包括HTTP或HTTPS代理,当然还可以添加SOCKS,FTP,RTSP等
      NSDictionary * proxyDic = @{
                                  @"HTTPEnable": @YES,
                                  @"HTTPProxy": proxyHost,
                                  @"HTTPPort": proxyPort,
                                  @"HTTPSEnable": @YES,
                                  @"HTTPSProxy": proxyHost,
                                  @"HTTPSPort": proxyPort
                                  };
      
      NSURLSessionConfiguration * configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];//创建一个临时会话配置
      configuration.connectionProxyDictionary = proxyDic;
      
      //网络请求
      self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
      NSURLSessionTask * task = [self.session dataTaskWithRequest:self.request];
      [task  resume];//开始任务
    }
    

    5、回调

    上面使用的是NSURLSession请求,所以我们通过NSURLSessionDelegate来接收网络请求的数据(成功或失败等信息):

    #pragma mark ---- NSURLSessionDelegate
    /*
      NSURLSessionDelegate接到数据后,通过URLProtocol传出去
     */
    //失败
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
      if (error)
      {
        [self.client URLProtocol:self didFailWithError:error]; //请求错误
      }
      else
      {
        [self.client URLProtocolDidFinishLoading:self]; //完成加载
      }
    }
    //接收到响应
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
      
      [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; //创建一个响应(缓存策略:不缓存)
      completionHandler(NSURLSessionResponseAllow);
      
    }
    //接收到数据
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
    {
      [self.client URLProtocol:self didLoadData:data]; //接收到数据
    }
    

    然后再通过URLProtocol回调给外面用户发送网络请求的地方。不管外面用户是通过NSURLSession或NSURLConnection发起的网络请求,都可以收到URLProtocol的回调数据。URLProtocol回调的方法主要有以下几个:

    [self.client URLProtocol:self didFailWithError:error]; //请求错误
    [self.client URLProtocolDidFinishLoading:self]; //完成加载
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; //创建一个响应(缓存策略:不缓存)
    [self.client URLProtocol:self didLoadData:data]; //接收到数据
    

    6、结束

    -(void)stopLoading完成网络请求的操作

    //结束请求
    -(void)stopLoading
    {
      [self.session invalidateAndCancel];
      self.session = nil;
    }
    

    四、代码

    AppDelegate.m文件

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      // Override point for customization after application launch.
      
      //注册protocol
      [NSURLProtocol registerClass:[HJQURLProtocol class]];
      
      return YES;
    }
    

    ViewController.m文件

    #import "ViewController.h"
    
    @interface ViewController ()<NSURLSessionDelegate>
    @property(nonatomic,strong)NSMutableData *responseData;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
      [super viewDidLoad];
     
      UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
      btn.frame = CGRectMake(0, 300, [UIScreen mainScreen].bounds.size.width, 50);
      [btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
      [btn setTitle:@"网络请求" forState:UIControlStateNormal];
      [btn addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
      [self.view addSubview:btn];
    }
    
    #pragma mark ---- 按钮响应函数
    -(void)buttonAction:(UIButton *)button
    {
       [button setTitle:@"请求中..." forState:UIControlStateNormal];
      
      
    //     //NSURLSession的网络请求
    //    NSURLSession * session = [NSURLSession sharedSession];
    //    NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
    //    NSURLSessionTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    //        NSLog(@"NSURLSession got the response [%@]", response);
    //        NSLog(@"NSURLSession got the data [%@]", data);
    //    }];
    //    [task resume];
      
      
       //NSURLConnection的网络请求
      NSURL * url = [NSURL URLWithString:@"https://www.baidu.com"];
      NSURLRequest * request = [NSURLRequest requestWithURL:url];
      NSURLConnection * connection = [NSURLConnection connectionWithRequest:request delegate:self];
      [connection start];
    }
    
    #pragma mark ---- NSURLConnectionDataDelegate代理方法
    /*
     *当接收到服务器的响应(连通了服务器)时会调用
     */
    -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
    {
      NSLog(@"接收到服务器的响应");
      //初始化数据
      self.responseData = [NSMutableData data];
    }
    
    /*
     *当接收到服务器的数据时会调用(可能会被调用多次,每次只传递部分数据)
     */
    -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
      NSLog(@"接收到服务器的数据");
      //拼接数据
      [self.responseData appendData:data];
    }
    
    /*
     *当服务器的数据加载完毕时就会调用
     */
    -(void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
      NSLog(@"服务器的数据加载完毕");
      //     NSLog(@"responseData:%@",self.responseData);
      
      NSString * jsonStr = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding];
      
      NSLog(@"jsonStr:%@",jsonStr);
    }
    
    /*
     *请求错误(失败)的时候调用(请求超时\断网\没有网\,一般指客户端错误)
     */
    -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    {
      NSLog(@"请求错误:%@",error);
    }
    @end
    

    HJQURLProtocol.m文件

    #import "HJQURLProtocol.h"
    
    static NSString * const URLProtocolHandledKey = @"URLProtocolHandledKey";
    
    @interface HJQURLProtocol()<NSURLSessionDelegate>
    @property(nonatomic,strong)NSURLSession * session;
    @end
    
    @implementation HJQURLProtocol
    
    +(BOOL)canInitWithRequest:(NSURLRequest *)request
    {
      //是http或https则拦截处理
      NSString * scheme = [[request.URL scheme] lowercaseString];//获取URL转小写
      if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"])
      {
        //有拦截处理过的则不再拦截,否则会在这死循环
        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request])
        {
          return NO;
        }
        else
        {
          return YES;
        }
      }
      else
      {
        return NO;
      }
    }
    
    //改变请求request
    +(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
    {
      return request;
    }
    
    //开始请求
    -(void)startLoading
    {
      NSMutableURLRequest * mutableRequest = [[self request] mutableCopy];
      //标识该request已经处理过了,防止无限循环
      [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableRequest];
      
      
      //设置代理
      NSString * proxyHost = @"127.0.0.1";
      NSNumber * proxyPort = [NSNumber numberWithInt:8888];
      
      //创建一个代理服务器,包括HTTP或HTTPS代理,当然还可以添加SOCKS,FTP,RTSP等
      NSDictionary * proxyDic = @{
                                  @"HTTPEnable": @YES,
                                  @"HTTPProxy": proxyHost,
                                  @"HTTPPort": proxyPort,
                                  @"HTTPSEnable": @YES,
                                  @"HTTPSProxy": proxyHost,
                                  @"HTTPSPort": proxyPort
                                  };
      
      NSURLSessionConfiguration * configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];//创建一个临时会话配置
      configuration.connectionProxyDictionary = proxyDic;
      
      //网络请求
      self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
      NSURLSessionTask * task = [self.session dataTaskWithRequest:self.request];
      [task  resume];//开始任务
      
    }
    
    //结束请求
    -(void)stopLoading
    {
      [self.session invalidateAndCancel];
      self.session = nil;
    }
    
    #pragma mark ---- NSURLSessionDelegate
    /*
      NSURLSessionDelegate接到数据后,通过URLProtocol传出去
     */
    //失败
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
      if (error)
      {
        [self.client URLProtocol:self didFailWithError:error]; //请求错误
      }
      else
      {
        [self.client URLProtocolDidFinishLoading:self]; //完成加载
      }
    }
    //接收到响应
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
      
      [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; //创建一个响应(缓存策略:不缓存)
      completionHandler(NSURLSessionResponseAllow);
      
    }
    //接收到数据
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
    {
      [self.client URLProtocol:self didLoadData:data]; //接收到数据
    }
    
    @end
    

    效果:你以为你请求的是https://www.baidu.com,但其实已经转到127.0.0.1代理服务器上了

    Snip20181228_16.png

    相关文章

      网友评论

          本文标题:通过URLProtocol拦截网络请求后进行ip替换或请求代理设

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