写点什么

iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求

  • 2019-12-09
  • 本文字数:3078 字

    阅读完需:约 10 分钟

iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求

这篇文章会提供一种在 Cocoa 层拦截所有 HTTP 请求的方法,其实标题已经说明了拦截 HTTP 请求需要的了解的就是 NSURLProtocol


由于文章的内容较长,会分成两部分,这篇文章介绍 NSURLProtocol 拦截 HTTP 请求的原理,另一篇文章如何进行 HTTP Mock 介绍这个原理在 OHHTTPStubs 中的应用,它是如何 Mock(伪造)某个 HTTP 请求对应的响应的。

NSURLProtocol

NSURLProtocol 是苹果为我们提供的 URL Loading System 的一部分,这是一张从官方文档贴过来的图片:



官方文档对 NSURLProtocol 的描述是这样的:


An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.


在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的 NSURLProtocol 对象处理对应的 URL 请求,而我们需要做的就是写一个继承自 NSURLProtocol 的类,并通过 - registerClass: 方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。


这样,我们需要解决的核心问题就变成了如何使用 NSURLProtocol 来处理所有的网络请求,这里使用苹果官方文档中的 CustomHTTPProtocol 进行介绍,你可以点击这里下载源代码。


在这个工程中 CustomHTTPProtocol.m 是需要重点关注的文件,CustomHTTPProtocol 就是 NSURLProtocol 的子类:


Objective-C


@interface CustomHTTPProtocol : NSURLProtocol
...
@end
复制代码


现在重新回到需要解决的问题,也就是 如何使用 NSURLProtocol 拦截 HTTP 请求?,有这个么几个问题需要去解决:


  • 如何决定哪些请求需要当前协议对象处理?

  • 对当前的请求对象需要进行哪些处理?

  • NSURLProtocol 如何实例化?

  • 如何发出 HTTP 请求并且将响应传递给调用者?


上面的这几个问题其实都可以通过 NSURLProtocol 为我们提供的 API 来解决,决定请求是否需要当前协议对象处理的方法是:+ canInitWithRequest


Objective-C


+ (BOOL)canInitWithRequest:(NSURLRequest *)request {  BOOL shouldAccept;  NSURL *url;  NSString *scheme;
shouldAccept = (request != nil); if (shouldAccept) { url = [request URL]; shouldAccept = (url != nil); } return shouldAccept;}
复制代码


因为项目中的这个方法是大约有 60 多行,在这里只粘贴了其中的一部分,只为了说明该方法的作用:每一次请求都会有一个 NSURLRequest 实例,上述方法会拿到所有的请求对象,我们就可以根据对应的请求选择是否处理该对象;而上面的代码只会处理所有 URL 不为空的请求。


请求经过 + canInitWithRequest: 方法过滤之后,我们得到了所有要处理的请求,接下来需要对请求进行一定的操作,而这都会在 + canonicalRequestForRequest: 中进行,虽然它与 + canInitWithRequest: 方法传入的 request 对象都是一个,但是最好不要在 + canInitWithRequest: 中操作对象,可能会有语义上的问题;所以,我们需要覆写 + canonicalRequestForRequest: 方法提供一个标准的请求对象:


Objective-C


+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {  return request;}
复制代码


这里对请求不做任何修改,直接返回,当然你也可以给这个请求加个 header,只要最后返回一个 NSURLRequest 对象就可以。


在得到了需要的请求对象之后,就可以初始化一个 NSURLProtocol 对象了:


Objective-C


- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client {  return [super initWithRequest:request cachedResponse:cachedResponse client:client];}
复制代码


在这里直接调用 super 的指定构造器方法,实例化一个对象,然后就进入了发送网络请求,获取数据并返回的阶段了:


Objective-C


- (void)startLoading {  NSURLSession *session = [NSURLSession sessionWithConfiguration:[[NSURLSessionConfiguration alloc] init] delegate:self delegateQueue:nil];  NSURLSessionDataTask *task = [session dataTaskWithRequest:self.request];  [task resume];}
复制代码


这里使用简化了 CustomHTTPClient 中的项目代码,可以达到几乎相同的效果。


你可以在 - startLoading 中使用任何方法来对协议对象持有的 request 进行转发,包括 NSURLSessionNSURLConnection 甚至使用 AFNetworking 等网络库,只要你能在回调方法中把数据传回 client,帮助其正确渲染就可以,比如这样:


Objective-C


- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {  [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
completionHandler(NSURLSessionResponseAllow);}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [[self client] URLProtocol:self didLoadData:data];}
复制代码


当然这里省略后的代码只会保证大多数情况下的正确执行,只是给你一个对获取响应数据粗略的认知,如果你需要更加详细的代码,我觉得最好还是查看一下 CustomHTTPProtocol 中对 HTTP 响应处理的代码,也就是 NSURLSessionDelegate 协议实现的部分。


client 你可以理解为当前网络请求的发起者,所有的 client 都实现了 NSURLProtocolClient 协议,协议的作用就是在 HTTP 请求发出以及接受响应时向其它对象传输数据:


Objective-C


@protocol NSURLProtocolClient <NSObject>...- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;...@end
复制代码


当然这个协议中还有很多其他的方法,比如 HTTPS 验证、重定向以及响应缓存相关的方法,你需要在合适的时候调用这些代理方法,对信息进行传递。


如果你只是继承了 NSURLProtocol 并且实现了上述方法,依然不能达到预期的效果,完成对 HTTP 请求的拦截,你还需要在 URL 加载系统中注册当前类:


Objective-C


[NSURLProtocol registerClass:self];
复制代码


需要注意的是 NSURLProtocol 只能拦截 UIURLConnectionNSURLSessionUIWebView 中的请求,对于 WKWebView 中发出的网络请求也无能为力,如果真的要拦截来自 WKWebView 中的请求,还是需要实现 WKWebView 对应的 WKNavigationDelegate,并在代理方法中获取请求。

无论是 NSURLProtocolNSURLConnection 还是 NSURLSession 都会走底层的 socket,但是 WKWebView 可能由于基于 WebKit,并不会执行 C socket 相关的函数对 HTTP 请求进行处理,具体会执行什么代码暂时不是很清楚,如果对此有兴趣的读者,可以联系笔者一起讨论。

总结

如果你只想了解如何对 HTTP 请求进行拦截,其实看到这里就可以了,不过如果你想应用文章中的内容或者希望了解如何伪造 HTTP 响应,可以看下一篇文章如何进行 HTTP Mock

References


Github Repo:iOS-Source-Code-Analyze


Source: https://draveness.me/intercept


本文转载自 Draveness 技术博客。


原文链接:https://draveness.me/intercept


2019-12-09 15:541310

评论

发布
暂无评论
发现更多内容

项目动态|Apache Pulsar 2.7.4 版本发布

Apache Pulsar

开源 架构 云原生 中间件 Apache Pulsar

手把手教你学Dapr - 3. 使用Dapr运行第一个.Net程序

MASA技术团队

C# .net 微软 微服务 dapr

中国AIOps们,你们究竟是在骗谁?

Geek_f56666

云计算 AIOPS 云服务

手把手教你学Dapr - 5. 状态管理

MASA技术团队

C# .net 微软 微服务 dapr

详解 HDFS 底层交互原理

五分钟学大数据

hdfs 1月月更

中国AIOps们,你们究竟是在骗谁?

观测观测

AIOPS

技术分享| 探索视频感知编码

anyRTC开发者

音视频 WebRTC 视频编码 视频压缩 视频感知编码

『内存中的操作系统』内存虚拟化又是什么

soolaugust

操作系统 内存

手把手教你学Dapr - 8. 绑定

MASA技术团队

C# .net 微软 微服务 dapr

使用轻量应用服务器部署Docsify在线文档平台

阿里云弹性计算

阿里云 轻量应用 征文投稿

C#8.0 可空引用类型

MASA技术团队

C# .net 微软 微服务 项目

gpushare.com_基于去噪Transformer的无监督句子编码【EMNLP 2021】

恒源云

深度学习 语音识别 transform

从 ClickHouse 到 ByteHouse:实时数据分析场景下的优化实践

字节跳动数据平台

数据库 字节跳动 Clickhouse 实时数据分析 bytehouse

【渗透技术】一个渗透测试工具人是怎样操作的

H

网络安全 渗透测试

手把手教你学Dapr - 6. 发布订阅

MASA技术团队

C# .net 微软 微服务 dapr

Blazor是春天还是寒风里的挣扎

MASA技术团队

C# .net 微软 微服务 blazor

盘点 2021 征文大赛获奖名单公布!

InfoQ写作社区官方

盘点2021 热门活动

MS Office 冷门却实用的技巧及软件安装包分享(文末有福利)

淋雨

Office office365

阿里云弹性计算年度关键词:强劲、突破、开放

阿里云弹性计算

阿里云 弹性计算 计算巢 神龙架构 无影

PK体系“配齐”!亚信科技数据库与麒麟软件OS、飞腾CPU完成产品适配

亚信AntDB数据库

AntDB 亚信科技

淘宝小部件 Canvas 渲染流程与原理全解析

阿里巴巴终端技术

小程序 淘宝 渲染

深入解析 Apache Pulsar 系列(一):客户端消息确认

Apache Pulsar

开源 架构 云原生 Apache Pulsar 消息中间件

JavaScript 使用 Markdown 制作 PPT

devpoint

markdown 1月月更 slidev

手把手教你学Dapr - 4. 服务调用

MASA技术团队

C# .net 微软 微服务 dapr

全国社保基金理事会副理事长陈文辉一行莅临青藤调研

青藤云安全

普通开发者,如何成为Apache项目的commiter ?| 人物专访

云智慧AIOps社区

开源 程序员 算法 运维 时序数据

SpringBoot:如何优雅地进行参数传递、响应数据封装、异常处理?

CRMEB

如何提高你的写作技巧

坚果

1月月更

详细图解Netty Reactor启动全流程

bin的技术小屋

微服务 网络编程 中间件 Java IO netty

手把手教你学Dapr - 7. Actors

MASA技术团队

C# .net 微软 微服务 dapr

手把手教你学Dapr - 9. 可观测性

MASA技术团队

C# .net 微软 微服务 dapr

iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求_语言 & 开发_Draveness_InfoQ精选文章