问题:因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. Object类--equals方法

    equals方法 1.比较的是对象引用的是否指向同一块内存地址 public static void main(String[] args) { HuaWei huawei=new HuaWei(); ...

  2. c++文件打包工具实现

    没事做就来写一个打包的工具吧.很多是网络上面找的,只是我把他修改一下合并在一起. // PacketFile.cpp : 定义控制台应用程序的入口点. #include "stdafx.h& ...

  3. keybd_event 被 SendInput 替代

    keybd_event   函数功能:该函数合成一次击键事件.系统可使用这种合成的击键事件来产生WM_KEYUP或WM_KEYDOWN消息,键盘驱动程序的中断处理程序调用keybd_event函数.在 ...

  4. 从零开始优雅的使用mongodb实例

    基本连接 一.创建express工程testmon express testmon 二.精简app.js var express = require("express"); var ...

  5. 线程安全的CopyOnWriteArrayList介绍

    证明CopyOnWriteArrayList是线程安全的 先写一段代码证明CopyOnWriteArrayList确实是线程安全的. ReadThread.java import java.util. ...

  6. iOS:用Block写一个链式编程

    一.介绍 链式编程是一个比较新颖的编程方式,简单直观,用起来也比较舒服.目前比较有名的Mansory和BabyBlueTooth就是使用链式编程写的第三方框架. 二.写法 链式编程写法不同于传统方式, ...

  7. 13.1 dubbo服务降级源码解析

    从 9.1 客户端发起请求源码 的客户端请求总体流程图中,截取部分如下: //代理发出请求 proxy0.sayHello(String paramString) -->InvokerInvoc ...

  8. C# ConcurrentBag实现

    ConcurrentBag可以理解为是一个线程安全无序集合,API比我们的list要弱一点,那我们来看看它的实现: public class ConcurrentBag<T> : IPro ...

  9. 【Spark】Spark-架构

    Spark-架构 Spark Master at spark://node-01:7077 spark clustermanager_百度搜索 看了之后不再迷糊-Spark多种运行模式 - 简书 Sp ...

  10. Java 8 学习资料汇总【转载】

    原文地址 2014年3月18日,Java SE 8 发布,而 Java 9 预期2016年发布: 2011年7月7日,Java 7 发布,是2009年4月20日被Oracle 以74亿美元收购首次推出 ...