WKWebView的请求拦截和修改实现。技术方法:NSURLProtocol
- 需求,拦截WKWebview中的所有网络请求,并且对亲够Request的httpheader中添加字段token,等信息。
- 实现技术,利用NSURLProtocol。
- 首先实现一个继承自NSURLProtocol的自定义类:MYSchemeURLProtocol,完整的代码实现如下:
//类文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#define MYSchemeURLHeaderTokenAdd 0
FOUNDATION_EXTERN NSString *const URLLoadingNotification;
FOUNDATION_EXTERN NSString *const HttpProtocolKey;
FOUNDATION_EXTERN NSString *const HttpsProtocolKey;
@interface MYSchemeURLProtocol : NSURLProtocol
+ (void)registSchemeURLProtocol;
+ (void)unregistSchemeURLProtocol;
@end
@interface NSMutableURLRequest (MYHeaderAppend)
- (BOOL)appendRemoteAccessHeaders;
@end
NS_ASSUME_NONNULL_END
//================.m ===================
#import "MYSchemeURLProtocol.h"
#import "JSON.h"
NSString *const URLLoadingNotification = @"com.zspace.urlLoadingNotification";
static NSString *kURLProtocolHandledKey = @"URLProtocolHandledKey";
NSString *const HttpProtocolKey = @"http";
NSString *const HttpsProtocolKey = @"https";
@interface MYSchemeURLProtocol()<NSURLSessionDelegate>
@property (atomic,strong,readwrite) NSURLSessionDataTask *task;
@property (nonatomic,strong) NSURLSession *session;
@property (nonatomic, strong) NSOperationQueue *queue;
@end
@implementation MYSchemeURLProtocol
+ (void)registSchemeURLProtocol {
// 防止苹果静态检查 将 WKBrowsingContextController 拆分,然后再拼凑起来
NSArray *privateStrArr = @[@"Controller", @"Context", @"Browsing", @"K", @"W"];
NSString *className = [[[privateStrArr reverseObjectEnumerator] allObjects] componentsJoinedByString:@""];
Class cls = NSClassFromString(className);
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if (cls && sel) {
if ([(id)cls respondsToSelector:sel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
// 注册自定义协议
// [(id)cls performSelector:sel withObject:@"CustomProtocol"];
// 注册http协议
[(id)cls performSelector:sel withObject:HttpProtocolKey];
// 注册https协议
// [(id)cls performSelector:sel withObject:HttpsProtocolKey];
#pragma clang diagnostic pop
}
}
// SechemaURLProtocol 自定义类 继承于 NSURLProtocol
[NSURLProtocol registerClass:[MYSchemeURLProtocol class]];
}
+ (void)unregistSchemeURLProtocol {
[NSURLProtocol unregisterClass:[MYSchemeURLProtocol class]];
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSString *scheme = [[request URL] scheme];
// 判断是否需要进入自定义加载器
if ([scheme caseInsensitiveCompare:HttpProtocolKey] == NSOrderedSame
/*|| [scheme caseInsensitiveCompare:HttpsProtocolKey] == NSOrderedSame*/)
{
//看看是否已经处理过了,防止无限循环
if ([NSURLProtocol propertyForKey:kURLProtocolHandledKey inRequest:request]) {
NSLogInfo(@"hookRequest-canInitWithRequest NO %@",request.URL);
return NO;
}
}
NSLogInfo(@"hookRequest-canInitWithRequest YES %@",request.URL);
return YES;
}
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
// 执行自定义操作,例如添加统一的请求头等
return mutableReqeust;
}
// 判重
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)startLoading
{
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
// 标示改request已经处理过了,防止无限循环
[NSURLProtocol setProperty:@YES forKey:kURLProtocolHandledKey inRequest:mutableReqeust];
#if MYSchemeURLHeaderTokenAdd
// 处理请求 body
if (self.request.HTTPBody && [self.request valueForHTTPHeaderField:@"Content-Type"]) {
[mutableReqeust setHTTPBody:self.request.HTTPBody];
[mutableReqeust setValue:[self.request valueForHTTPHeaderField:@"Content-Type"] forHTTPHeaderField:@"Content-Type"];
}
if([mutableReqeust.URL.absoluteString containsString:@"Users/authenticatebyname"]){
NSLogInfo(@"跳过");
}
NSString *body = [NTYJSON parse:mutableReqeust.HTTPBody];
NSLogInfo(@"self.HTTPBody:%@",body);
NSLogInfo(@"self.HTTPBodyStream:%@",mutableReqeust.HTTPBodyStream);
[mutableReqeust appendRemoteAccessHeaders];
#endif
// 通知更新URL
[[NSNotificationCenter defaultCenter] postNotificationName:URLLoadingNotification object:mutableReqeust.URL.absoluteString];
NSURLSessionConfiguration *configure = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:configure delegate:self delegateQueue:self.queue];
self.task = [self.session dataTaskWithRequest:mutableReqeust];
[self.task resume];
}
- (void)stopLoading
{
[self.session invalidateAndCancel];
self.session = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:URLLoadingNotification object:@""];
}
#pragma mark - Getter
- (NSOperationQueue *)queue
{
if (!_queue) {
_queue = [[NSOperationQueue alloc] init];
}
return _queue;
}
@end
@implementation MYSchemeURLProtocol(NSURLSessionDelegate)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error != nil) {
NSLogInfo(@"didComplete url:%@",task.originalRequest.URL.absoluteString);
// 检查是否为 WKWebView 的网络请求
[self.client URLProtocol:self didFailWithError:error];
}else
{
[self.client URLProtocolDidFinishLoading:self];
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))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];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse * _Nullable))completionHandler
{
completionHandler(proposedResponse);
}
//TODO: 重定向
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)newRequest completionHandler:(void (^)(NSURLRequest *))completionHandler
{
NSLogInfo(@"hookRequest-willPerformHTTP:%@",newRequest.URL);
NSMutableURLRequest* redirectRequest;
redirectRequest = [newRequest mutableCopy];
[[self class] removePropertyForKey:kURLProtocolHandledKey inRequest:redirectRequest];
#if MYSchemeURLHeaderTokenAdd
[redirectRequest appendRemoteAccessHeaders];
#endif
[[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response];
[self.task cancel];
[[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]];
}
@end
@implementation NSMutableURLRequest (MYHeaderAppend)
/*
还需要增加两个header:
Nas-Neigh-Ip
Nas-Neigh-Port
分别是目标ip和目标端口,请注意port需要用字符串类型
*/
- (BOOL)appendRemoteAccessHeaders {
//MARK:MY_token 添加进请求头
// 添加自定义的 HTTP 头
NSString *tokenValue = [MYUserService shared].currentUser.token;
NSString *holdToken = [self.allHTTPHeaderFields valueForKey:@"z-token"];
if([self.URL.absoluteString containsString:@"Users/authenticatebyname"]){
NSLogInfo(@"跳过");
}
if(isEmpty(holdToken)){
NSString *body = [NTYJSON parse:self.HTTPBody];
NSLogInfo(@"self.HTTPBody:%@ url:%@",body,self.URL.absoluteString);
NSLogInfo(@"self.HTTPBodyStream:%@",self.HTTPBodyStream);
NSURLComponents *remoteURLComs = [NSURLComponents componentsWithString:[MYUserService shared].latestRemoteAccessURL];
NSString *portStr = STRING(@"%@",[remoteURLComs port]);
NSString *hostStr = remoteURLComs.host;
[self setValue:tokenValue forHTTPHeaderField:@"z-token"];
[self setValue:hostStr forHTTPHeaderField:@"Nas-Neigh-Ip"];
[self setValue:portStr forHTTPHeaderField:@"Nas-Neigh-Port"];
NSLogInfo(@"remoteAccessURL&appendheaders::%@\n%@",self.URL,self.allHTTPHeaderFields);
return YES;
}
return NO;
}
@end
- 接下来,在需要进行拦截的WebViewController中,注册MYSchemeURLProtocol。核心代码示例如下:
#import "MYWebviewController.h"
#import "MYSchemeURLProtocol.h"
@interface JKJWebviewController ()<WKNavigationDelegate,WKScriptMessageHandler,WKUIDelegate>
@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) NSURLRequest *request;
@end
@implementation JKJWebviewController
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
#if JKJSchemeURLHeaderTokenAdd
if(self.refModule.refModuleRequest == JKJRefModuleRequestRemoteAccessWebLoad){
[JKJSchemeURLProtocol unregistSchemeURLProtocol];
}
#endif
}
- (void)viewDidLoad {
[super viewDidLoad];
//注册代码
#if JKJSchemeURLHeaderTokenAdd
if(self.refModule.refModuleRequest == JKJRefModuleRequestRemoteAccessWebLoad){
[JKJSchemeURLProtocol registSchemeURLProtocol];
}
#endif
// Do any additional setup after loading the view.
self.title = self.webTitle;
self.edgesForExtendedLayout = UIRectEdgeNone;
WKWebViewConfiguration *configuration = [self configuration];
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
webView.navigationDelegate = self;
webView.UIDelegate = self;
[webView.scrollView setShowsVerticalScrollIndicator:NO];
[self.view addSubview:webView];
if (self.needObserveTitle) {
[webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
}
self.webView = webView;
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[NSURL fileURLWithPath:urlString]];
[webView loadRequest:req];
self.request = req;
}
- 执行结果:
- 所有请求都可以进行拦截,
- 所有get请求结果拦截修改和加载表现正常
- post请求中的body体丢失, 这个问题暂时无法在所运用的场景中使用到,由于后期很多链接都有post请求转发,并且携带有body体,NSURLProtocol拦截之后body体丢失问题无法得到完整的解决,最后放弃了拦截修改。
- 暂未找到需求逻辑最佳解决方案,待后续处理补充。
原文地址:https://blog.csdn.net/u012812881/article/details/137510506
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!