使用NSURLProtocol實現本地靜態文件的web服務器

需求前提:

最近公司有這樣一個需求,要用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失效。

webView攔截URL.png

  • 猜想2:NSURLProtocol攔截 ~ 可以攔截的網絡請求包括NSURLSession,NSURLConnection以及UIWebVIew,(WKWebView需要特定的方式實現才能做到url被攔截,后續補充)

嘗試攔截 https://www.baidu.com, 此代理方法攔截到了所有資源的url,因此猜想2可行。

NSURLProtocol攔截URL.png

實現流程:
一、在想要攔截的地方先注冊:

[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;
}
NSURLProtocol攔截資源地址.png

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容