问题:因dns发生域名劫持 需要手动将URL请求的域名重定向到指定的IP地址
  最近在项目里由于电信那边发生dns发生域名劫持,因此需要手动将URL请求的域名重定向到指定的IP地址,但是由于请求可能是通过NSURLConnection,NSURLSession或者AFNetworking等方式,因此要想统一进行处理,一开始是想通过Method Swizzling去hook cfnetworking底层方法,后来发现其实有个更好的方法--NSURLProtocol。

NSURLProtocol

NSURLProtocol能够让你去重新定义苹果的URL加载系统 (URL Loading System)的行为,URL Loading System里有许多类用于处理URL请求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等,当URL Loading System使用NSURLRequest去获取资源的时候,它会创建一个NSURLProtocol子类的实例,你不应该直接实例化一个NSURLProtocol,NSURLProtocol看起来像是一个协议,但其实这是一个类,而且必须使用该类的子类,并且需要被注册。

使用场景

不管你是通过UIWebView, NSURLConnection 或者第三方库 (AFNetworking, MKNetworkKit等),他们都是基于NSURLConnection或者 NSURLSession实现的,因此你可以通过NSURLProtocol做自定义的操作。

  • 重定向网络请求
  • 忽略网络请求,使用本地缓存
  • 自定义网络请求的返回结果
  • 拦截图片加载请求,转为从本地文件加载
  • 一些全局的网络请求设置
  • 快速进行测试环境的切换
  • 过滤掉一些非法请求
  • 网络的缓存处理(H5离线包 和 网络图片缓存)
  • 可以拦截UIWebView,基于系统的NSURLConnection或者NSURLSession进行封装的网络请求。目前WKWebView无法被NSURLProtocol拦截。
  • 当有多个自定义NSURLProtocol注册到系统中的话,会按照他们注册的反向顺序依次调用URL加载流程。当其中有一个NSURLProtocol拦截到请求的话,后续的NSURLProtocol就无法拦截到该请求。

拦截网络请求

子类化NSURLProtocol并注册

@interface CustomURLProtocol : NSURLProtocol
@end

然后在application:didFinishLaunchingWithOptions:方法中注册该CustomURLProtocol,一旦注册完毕后,它就有机会来处理所有交付给URL Loading system的网络请求。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//注册protocol
[NSURLProtocol registerClass:[CustomURLProtocol class]];
return YES;
}

实现CustomURLProtocol

#import "CustomURLProtocol.h"

static NSString* const URLProtocolHandledKey = @"URLProtocolHandledKey";

@interface CustomURLProtocol ()<NSURLConnectionDelegate>
@property (nonatomic, strong) NSURLConnection *connection; @end
@implementation CustomURLProtocol //这里面写重写的方法 @end

注册好了之后,现在可以开始实现NSURLProtocol的一些方法:

  • +canInitWithRequest:
    这个方法主要是说明你是否打算处理对应的request,如果不打算处理,返回NO,URL Loading System会使用系统默认的行为去处理;如果打算处理,返回YES,然后你就需要处理该请求的所有东西,包括获取请求数据并返回给 URL Loading System

    网络数据可以简单的通过NSURLConnection去获取,而且每个NSURLProtocol对象都有一个NSURLProtocolClient实例,可以通过该client将获取到的数据返回给URL Loading System

    这里有个需要注意的地方,想象一下,当你去加载一个URL资源的时候,URL Loading System会询问CustomURLProtocol是否能处理该请求,你返回YES,然后URL Loading System会创建一个CustomURLProtocol实例然后调用NSURLConnection去获取数据,然而这也会调用URL Loading System,而你在+canInitWithRequest:中又总是返回YES,这样URL Loading System又会创建一个CustomURLProtocol实例导致无限循环。我们应该保证每个request只被处理一次,可以通过+setProperty:forKey:inRequest:标示那些已经处理过的request,然后在+canInitWithRequest:中查询该request是否已经处理过了,如果是则返回NO。

    + (BOOL)canInitWithRequest:(NSURLRequest *)request
    {
    //只处理http和https请求 返回NO默认让系统去处理
    NSString *scheme = [[request URL] scheme];
    if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
    [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame))
    {
    //看看是否已经处理过了,防止无限循环 根据业务来截取
    if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
    return NO;
    } //还要在这里截取DSN解析请求中的链接 判断拦截域名请求的链接如果是返回NO
    if (判断拦截域名请求的链接) {
    return NO;
    } return YES;
    }
    return NO;
    }
  • +canonicalRequestForRequest 如果没有特殊需求,直接返回request就可以了, 可以在开始加载中startLoading方法中 修改request,比如添加header,修改host,请求重定向等
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
  return request;
 } 
  • +requestIsCacheEquivalent:toRequest:
    主要判断两个request是否相同,如果相同的话可以使用缓存数据,通常只需要调用父类的实现。
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
  • -startLoading -stopLoading
    这两个方法主要是开始和取消相应的request,而且需要标示那些已经处理过的request。

    - (void)startLoading
    {
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
    //标示改request已经处理过了,防止无限循环
    [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
    //重定向
    self.connection = [NSURLConnection connectionWithRequest:[self dealDNS: mutableReqeust] delegate:self];
    } - (void)stopLoading
    {
    [self.connection cancel];
     self.connection = nil ;
    } //解决劫持 重定向到ip地址
    - (NSMutableURLRequest *)dealDNS:(NSMutableURLRequest *)request{ if ([request.URL host].length == ) {
    return request;
    } NSString *originUrlString = [request.URL absoluteString];
    NSString *originHostString = [request.URL host];
    NSRange hostRange = [originUrlString rangeOfString:originHostString];
    if (hostRange.location == NSNotFound) {
    return request;
    } //根据当前host(如baidu.com)请求获取到IP(如172.128.3.3) 并替换到URL中
    //定向到IP 改IP需要根据 host 去请求获取到
    NSString *ip = 根据 host请求获取; // (如baidu.com)同步请求获取到IP(如172.128.3.3) 注意⚠️这个请求的链接需要在canInitWithRequest里面过滤掉 // 替换域名
    //URL:https://www.baidu.com 替换成了https://172.128.3.3
    NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip];
    NSURL *url = [NSURL URLWithString:urlString];
    request.URL = url; //给改IP设置Cookie
    [CookieManager setWebViewCookieForDomain:ipUrl.host]; return request;
    }
  • NSURLConnectionDataDelegate方法
    在处理网络请求的时候会调用到该代理方法,我们需要将收到的消息通过client返回给URL Loading System
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
} - (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
} - (void) connectionDidFinishLoading:(NSURLConnection *)connection {
[self.client URLProtocolDidFinishLoading:self];
} - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
} //解决发送IP地址的HTTPS请求 证书验证
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{ if (!challenge) {
return;
}
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
//构造一个NSURLCredential发送给发起方
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } else { //对于其他验证方法直接进行处理流程
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } }

注意⚠️:这里面在 手动将URL请求的域名重定向到指定的IP地址 去请求的时候需要 对HTTPS请求校验证书



iOS - NSURLProtocol详解和应用的更多相关文章

  1. 【转】IOS AutoLayout详解(三)用代码实现(附Demo下载)

    转载自:blog.csdn.net/hello_hwc IOS SDK详解 前言: 在开发的过程中,有时候创建View没办法通过Storyboard来进行,又需要AutoLayout,这时候用代码创建 ...

  2. IOS SDK详解

    来源:http://blog.csdn.net/column/details/huangwenchen-ios-sdk.html?page=1#42803301 博客专栏>移动开发专栏>I ...

  3. iOS路由详解

    本文如题,路由详解,注定是一篇详细解释iOS路由原理及使用的文章,由于此时正在外地出差,无法详细一一写出,只能不定时的补充. 一.什么是iOS路由 路由一词来源于路由器,可以实现层级之间消息转发的功能 ...

  4. IOS 手势详解

    在IOS中手势可以让用户有很好的体验,因此我们有必要去了解一下手势. (在设置手势是有很多值得注意的地方) *是需要设置为Yes的点击无法响应* *要把手势添加到所需点击的View,否则无法响应* 手 ...

  5. IOS SizeClasses 详解

    SizeClasses 详解 iOS 8在应用界面的可视化设计上添加了一个新的特性-Size Classes.对于任何设备来说,界面的宽度和高度都只分为三种描述:紧凑,任意和宽松.这样开发者便可以无视 ...

  6. iOS模式详解—「runtime面试、工作」看我就 🐒 了 ^_^.

    Write in the first[写在最前] 对于从事 iOS 开发人员来说,当提到 ** runtime时,我想都可以说出来 「runtime 运行时」和基本使用的方法.相信很多开发者跟我当初一 ...

  7. iOS 模式详解—「runtime面试、工作」看我就 🐒 了 ^_^.

    引导 Copyright © PBwaterln Unauthorized shall not be *copy reprinted* . 对于从事 iOS 开发人员来说,所有的人都会答出「runti ...

  8. ios学习--详解IPhone动画效果类型及实现方法

    详解IPhone动画效果类型及实现方法是本文要介绍的内容,主要介绍了iphone中动画的实现方法,不多说,我们一起来看内容. 实现iphone漂亮的动画效果主要有两种方法,一种是UIView层面的,一 ...

  9. iOS CoreAnimation详解(一) 有关Layer的动画

    以前由于项目需要 也写了一些动画 ,但是知识不系统,很散.这段时间趁着项目完成的空袭,来跟着大神的脚步系统的总结一下iOS中Core Animation的知识点. 原博客地址:http://blog. ...

随机推荐

  1. copy unicode HTML to clipboard

    How to copy unicode HTML code to the clipboard in html format, so it can be pasted into Writer, Word ...

  2. dhtmlxtree 节点 展开收缩:新增了直接点 文本内容 也 实现了 展开收缩 功能(并记住了展开、收缩状态)

    dhtmlxtree 节点 展开收缩通常情况我们按 +- 就实现了 展开收缩 功能,为了方便我们新增了直接点 文本内容 也 实现了 展开收缩 功能(并记住了展开.收缩状态) tree = new dh ...

  3. 27.移除元素(c++方法实现)

    问题描述: 给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间 ...

  4. .net源码调试 http://referencesource.microsoft.com/

    其实关于.net源码调试 网上的资料已经很多了,我以前转载的文章有 VS2010下如何调试Framework源代码(即FCL) 和 如何使你的应用程序调试进.NET Framework 4.5源代码内 ...

  5. Python根据多个空格Split字符串

    下面的String: 1 沪1 上海市 1850 1350 400 1300 1186/1644(嘉定约100,松江69 奉贤68 2007年上海常住人口1858万人,户籍人口1378.86万人,来沪 ...

  6. C# System.Collections.SortedList

    using System; using System.Collections; public class SamplesSortedList { public static void Main() { ...

  7. MySQL查看当前的连接信息

    1:查看当前有多少个连接 mysql> status; Threads: 4 2:查看连接的详细信息 mysql> SHOW FULL PROCESSLIST;

  8. 解决 Docker pull 出现的net/http: TLS handshake timeout 的一个办法

    echo "DOCKER_OPTS=\"\$DOCKER_OPTS --registry-mirror=http://f2d6cb40.m.daocloud.io\"&q ...

  9. java command line error opening registry key 'Software\JavaSoft\Java Runtime Environment' java.dll

    C:\Users\huxxxxchan>javaError: opening registry key 'Software\JavaSoft\Java Runtime Environment'E ...

  10. Jmeter远程测试

    11.3 详解JMeter远程测试(1) 2012-04-09 09:14 温素剑 电子工业出版社 字号:T | T 综合评级: 想读(7)  在读(2)  已读(0)   品书斋鉴(0)   已有9 ...