需求前提:
最近公司有這樣一個需求,要用webView加載網頁游戲,由于蘋果方面不支持webView通過url從服務器端加載游戲,于是html,js等資源文件就放到了本地項目里面,通過webView加載html來實現交互。
但是html/js文件里有指向http/https的請求,這樣webView就加載不出來本地資源了,因此我們需要攔截http網絡請求,把這些url攔截到,然后構建成我們可以響應的方式。
攔截準備:
假設我們本地服務器的端口為1000,服務器名為:localhost,協議是http
那么需要攔截的URL為:http://localhost:1000
實現思路:
攔截URL有如下兩種方式:
- 猜想1:webView自己的代理方法攔截URL
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
嘗試攔截 https://www.baidu.com, 此代理方法確實攔截到了這個url,但是攔截不到內部圖片等具體資源的url,因此猜想1失效。
- 猜想2:NSURLProtocol攔截 ~ 可以攔截的網絡請求包括NSURLSession,NSURLConnection以及UIWebVIew,(WKWebView需要特定的方式實現才能做到url被攔截,后續補充)
嘗試攔截 https://www.baidu.com, 此代理方法攔截到了所有資源的url,因此猜想2可行。
實現流程:
一、在想要攔截的地方先注冊:
[NSURLProtocol registerClass:[CustomURLProtocol class]];
不想攔截則取消注冊:
[NSURLProtocol unregisterClass:[CustomURLProtocol class]];
二、攔截網絡請求,NSURLProtocol會依次執行下列方法:
1.該方法會拿到request的對象,我們可以通過該方法的返回值來篩選request是否需要被NSURLProtocol做攔截處理。
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSString *monitorStr = request.URL.absoluteString;
// NSLog(@"monitorStr=%@",monitorStr);
// 只處理 http://localhost:1000/... 的請求
if ( ([request.URL.absoluteString hasPrefix:@"http://localhost:1000”]))
{
//看看是否已經處理過了,防止無限循環
if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
return NO;
}
return YES;
}
return NO;
}
2.在該方法中,我們可以對request進行處理。例如修改請求頭部信息等。最后返回一個處理后的request實例。例如我們攔截到 https://www.baidu.com, 而通過修改request,將url指向http://www.lxweimin.com。 類似于webView代理方法攔截,將其指向一個新的url。如果不修改,則返回request本身。
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
// NSMutableURLRequest *mutableReqeust = [request mutableCopy];
// mutableReqeust = [self redirectHostInRequset:mutableReqeust];
return request;
}
3.在該方法中,把(處理過的或者不需處理的)request重新發送出去。我們要做的就是修改響應體response,騙過服務器,按我們拼接響應的方式去響應。以下為拼接響應體代碼。
- (void)startLoading
{
//1.獲取資源文件路徑 ajkyq/index.html
NSURL *url = [self request].URL;
NSString *resourcePath = url.path;
resourcePath = [resourcePath substringFromIndex:1];//把第一個/去掉
//2.讀取資源文件內容
NSString *path = [[NSBundle mainBundle] pathForResource:resourcePath ofType:nil];
NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path];
NSData *data = [file readDataToEndOfFile];
[file closeFile];
//3.拼接響應Response
NSInteger dataLength = data.length;
NSString *mimeType = [self getMIMETypeWithCAPIAtFilePath:path];
NSString *httpVersion = @"HTTP/1.1";
NSHTTPURLResponse *response = nil;
if (dataLength > 0) {
response = [self jointResponseWithData:data dataLength:dataLength mimeType:mimeType requestUrl:url statusCode:200 httpVersion:httpVersion];
} else {
response = [self jointResponseWithData:[@"404" dataUsingEncoding:NSUTF8StringEncoding] dataLength:3 mimeType:mimeType requestUrl:url statusCode:404 httpVersion:httpVersion];
}
//4.響應
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
}
#pragma mark - 拼接響應Response
-(NSHTTPURLResponse *)jointResponseWithData:(NSData *)data dataLength:(NSInteger)dataLength mimeType:(NSString *)mimeType requestUrl:(NSURL *)requestUrl statusCode:(NSInteger)statusCode httpVersion:(NSString *)httpVersion
{
NSDictionary *dict = @{@"Content-type":mimeType,
@"Content-length":[NSString stringWithFormat:@"%ld",dataLength]};
NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:requestUrl statusCode:statusCode HTTPVersion:httpVersion headerFields:dict];
return response;
}
備注:以上為NSURLProtocol實現攔截URL的流程,目前只適用于UIWebView,至于WKWebView攔截URL的實現方式后續會補充(蘋果做了一些操作,導致用不能用以上方式攔截WKWebView的URL)。我列舉了幾篇文章,思路是相通的。我會盡快把我寫的demo上傳到github,和大家分享。
1.http://www.lxweimin.com/p/02781c0bbca9 NSURLProtocol全攻略
2.https://github.com/liujinlongxa/NSURLProtocolDemo/blob/master/NSURLProtocolDemo/MySessionURLProtocol.m NSURLProtocolDemo
3.https://github.com/rnapier/RNCachingURLProtocol/blob/master/RNCachingURLProtocol.m 基于緩存的NSURLProtocol
4.https://github.com/yeatse/NSURLProtocol-WebKitSupport 基于WKWebKit的NSURLProtocol攔截
5.http://www.cnblogs.com/goodboy-heyang/p/5193741.html iOS開發之網絡編程--獲取文件的MIMEType