截获h5页面自定义协议,替换成本地资源

截获请求,进行替换,可以保证安全性,也可以用来进行缓存机制

2016-02-23 | 阅读

需求:在webview加载H5页面时,截获h5的请求,替换成加载本地资源.也就是本地缓存的实现.

截获webview中的js css等静态文件替换成本地内容

使用NSURLCache能够截获到所有的网络请求,并进行本地缓存的替换.

对于截获到的内容,需要返回缓存的,创建一个NSCachedURLResponse返回.其创建方法为 [NSCachedURLResponse alloc] initWithResponse:(NSURLResponse *)response data:(NSData *)data.

所以在此之前要创建一个NSURLResponse,response的构造函数为 : [NSURLResponse alloc] initWithURL:(NSURL *)url MIMEType:(NSString *)MIMEType expectedContentLength:(NSInteger)length textEncodingName:(NSString *)name

首先尝试直接修改URL的方式

NSURLCache中,这个函数cachedResponseForRequest:中传来的参数其实是NSMutableURLRequest,直接设置其属性URL就可以转发请求,代码如下:

NSURL *url = [NSURL URLWithString:[@"file://" stringByAppendingString:path]];
((NSMutableURLRequest *)request).URL = url;

但之后发现在ios 7中,这个cachedResponseForRequest:这个函数传递的参数并不是一个NSMutableURLRequest,而且NSURLCacheNSURLProtocol截获的URL要少,所以换在NSURLProtocol中进行截获替换:

static NSString *const ReplaceJSProtocol        = @"xxx://";
static NSString *const JSBundleName             = @"xxxx/plugins/";
static const NSInteger ProtocolLength           = 21;
static const NSInteger SuffixLengthDotJS        = 3;

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    NSURL* URL = request.URL;
    NSString* urlString = URL.absoluteString;
    if ([[urlString lowercaseString] hasPrefix:ReplaceJSProtocol]) {
        return YES;
    }
    return NO;
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

- (void)startLoading {
    NSURL* URL = self.request.URL;
    NSString* urlString = URL.absoluteString;
    NSInteger jsFileNameLength = urlString.length - ProtocolLength -SuffixLengthDotJS;
        NSString *jsFileName = [urlString substringWithRange:NSMakeRange(ProtocolLength, jsFileNameLength)];
        NSString *path = [[NSBundle mainBundle] pathForResource:[JSBundleName stringByAppendingString:jsFileName] ofType:@"js"];
        // URL跳转
        if (path) {
            NSData *data = [[NSData alloc] initWithContentsOfFile:path];
            NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[NSURL URLWithString:@""] MIMEType:@"text/html" expectedContentLength:data.length textEncodingName:nil];
            [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
            [self.client URLProtocol:self didLoadData:data];
            [self.client URLProtocolDidFinishLoading:self];
        } else {
            [self.client URLProtocol:self didFailWithError:[NSError errorWithDomain:@"xxxx" code:10086 userInfo:nil]];
        }
}

最后发现NSURLProtocol也抓取不到某些请求

在iOS中的webview中,如果当前访问的网站是https协议,就无法正常使用自定义协议的URL来加载内容.即在这些https协议下的网站,设置这样加载js文件:custom://helloworld.js,则在iOS下,即使通过NSURLProtocol也无法截获到请求,webview直接忽略了这些加载项.

原因估计是,苹果的目的为了安全. 使用自定义协议的目的,如在本地实现一种替换机制,既然不指望通过正常的协议来加载数据,而https的目的是为了安全,你直接用本地的内容替换网页内的部分,显然是不安全的,所以Apple选择阻止掉这些不合理的自定义协议的请求.

最后只好改成普通的https协议,来进行截获,有:

static NSString *const ReplaceJSProtocol        = @"https://xxxx/";