开发者社区> ruobin> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

研究笔记:iOS中使用WebViewProxy拦截URL请求

简介:
+关注继续查看

概述

先说明下iOS中加载url的正常流程:
1.客户端发送NSURLRequest给server
2.server返回对应的NSURLResponse

如果被WebViewProxy拦截,则流程变为:
1.客户端发送NSURLRequest给server
2.这个request被WebViewProxy拦截
3.proxy将修改后的新request发送给server
4.server返回response给proxy
5.proxy将返回的数据以url response或者回调的形式返回给客户端。

那么WebViewProxy的拦截原理是怎样的呢?

拦截原理

首先,WebViewProxy定义一个自定义的protocol(NSURLProtocol的子类)

@interface WebViewProxyURLProtocol : NSURLProtocol

题外话,子类必须实现NSURLProtocol的以下几个方法才能正常工作,当然这部分工作WebViewProxy已经都帮我们搞定了:

+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b;
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client;
- (void)startLoading;
- (void)stopLoading;

然后把URLProtocol的子类注册到url loading system中

+ (void)initialize {
    ...
    [NSURLProtocol registerClass:[WebViewProxyURLProtocol class]];
}

这样,以后app每次发送url request时都检查此request是否适用于WebViewProxyURLProtocol(过滤条件也是用户定义的),如果符合筛选条件,则使用WebViewProxyURLProtocol来加载该url。

这里需要注意下,原始的request和修改后的新request,可能都符合拦截条件,所以为了不使其无限制的拦截并循环处理下去,需要设置一个检测条件,保证每个request至多被处理一次。流程见下图。
Copy_of_Copy_of_CLTV_DOL_Flowchart

拦截的原理依据如下(NSURLProtocol的registerClass方法):

+ (BOOL)registerClass:(Class)protocolClass

When the URL loading system begins to load a request, each registered protocol class is consulted in turn to see if it can be initialized with the specified request. The first NSURLProtocol subclass to return YES when sent a canInitWithRequest: message is used to perform the URL load. There is no guarantee that all registered protocol classes will be consulted.
Classes are consulted in the reverse order of their registration.

使用举例

简单的拦截例子:

[WebViewProxy handleRequestsMatching:[NSPredicate predicateWithFormat:@"host MATCHES[cd] '[foo|bar]'"]  handler:^(NSURLRequest* req, WVPResponse *res) {
    [res respondWithText:@"Hi!"];
}];

简单的转发例子:

[WebViewProxy handleRequestsWithHost:@"example.proxy" handler:^(NSURLRequest *req, WVPResponse *res) {
    NSString* proxyUrl = [req.URL.absoluteString stringByReplacingOccurrencesOfString:@"example.proxy" withString:@"example.com"];
    NSURLRequest* proxyReq = [NSURLRequest requestWithURL:[NSURL URLWithString:proxyUrl]];
    [NSURLConnection connectionWithRequest:proxyReq delegate:res];
}];

更多详见https://github.com/marcuswestin/WebViewProxy
值得注意的是处理server返回的response时,有三套api可供选择:

High level API返回image, text, html or json

Low level API返回HTTP头和NSData

Piping API:从NSURLConnection返回data/error


注意事项:


注册自定义protocol可能和其他模块或sdk的拦截功能冲突,导致拦截无效,依据是上文中的:The first NSURLProtocol subclass to return YES when sent a canInitWithRequest: message is used to perform the URL load.


WebViewProxy每次拦截成功后,都会在请求的url尾部加上一个fragment后缀(#__webviewproxyreq__)用来标记该url已拦截,防止下次再次拦截从而造成死循环。这样有个隐患,就是url会被污染,可能影响某些正常功能。

一个解决方案是在http头中增加一个标记字段来表示该url已经被拦截过,从而跳出循环。

- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client {
    ...
        // 原有逻辑,注释掉
        //NSString* correctedFragment;
        //if (_correctedRequest.URL.fragment) {
        //    correctedFragment = @"__webviewproxyreq__";
        //} else {
        //    correctedFragment = @"#__webviewproxyreq__";
        //}
        //_correctedRequest.URL = [NSURL URLWithString:[request.URL.absoluteString stringByAppendingString:correctedFragment]];

        //使用http头来标记的新逻辑     Add by zhouyi.
        [_correctedRequest addValue:[@(YES) stringValue] forHTTPHeaderField:webViewProxyFlagKey];
    ...
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
   ...
    // 新增标记字段,表示已经被WebViewProxy处理过
    // Add by zhouyi.
    NSString* proxyFlag = request.allHTTPHeaderFields[webViewProxyFlagKey];
    if (proxyFlag)
    {
        return NO;
    }
    // 这是原有逻辑,注释掉
    //if ([webViewProxyLoopDetection evaluateWithObject:request.URL])
    //{
    //    return NO;
    //}
    ...
}

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Java J.U.C 学习笔记-使用篇(一)
Java 多线程如果根据使用场景分的话可以分为两类类。 1. 线程间的竞争。多个线程同时竞争一个资源。这个时候需要想办法保证,资源在同一时刻只能由一个线程操作。 2. 线程间的通信。多个线程间需要通过信息传递。协同完成一件事情,这个时候要想办法处理线程间如何通信,如果交互。
11 0
OpenGL学习笔记(八):进一步理解VAO、VBO和SHADER,并使用VAO、VBO和SHADER绘制一个三角形 下
OpenGL学习笔记(八):进一步理解VAO、VBO和SHADER,并使用VAO、VBO和SHADER绘制一个三角形
78 0
Apache Hive--自定义函数 UDF 开发| 学习笔记
快速学习Apache Hive--自定义函数 UDF 开发
57 0
日志库 winston 的学习笔记 - logger.info 打印到控制台上的实现原理
日志库 winston 的学习笔记 - logger.info 打印到控制台上的实现原理
29 0
前端打包利器webpack里utils.cssLoaders的工作原理调试
前端打包利器webpack里utils.cssLoaders的工作原理调试
23 0
Coding and Paper Letter(二十五)
资源整理。接上篇,这一篇主要针对论文。
1393 0
Spring4.0MVC学习资料,注解自动扫描bean,自动注入bean(二)
<p>Spring4.0的新特性我们在上一章已经介绍过了。包括它对jdk8的支持,Groovy Bean Definition DSL的支持,核心容器功能的改进,Web开发改进,测试框架改进等等。这张我们主要介绍spring4.0的自动扫描功能,以及对bean的过滤等特性进行学习。</p> <p>好吧,废话少说,我们来看看代码吧。</p> <p></p> <pre code_snip
2387 0
X86 DBCA, NETCA GIVE JAVA HOTSPOT ERROR IF ON X86_64 HARDWARE
    在使用DBCA命令创建新的数据库时,DBCA命令无法启动。运行的环境是宿主机64bit+AMD cpu, 而客户机为Linux 32bit + Grid Infrastructure(32) + Oracle database software(32)的情形。
1060 0
+关注
2
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载