【转】使用 NSURLProtocol 和 NSURLSession 拦截 UIWebView 的 HTTP 请求(包括 Ajax 请求)

原文:http://www.cnblogs.com/wobuyayi/p/6283599.html

【转】使用 NSURLProtocol 和 NSURLSession 拦截 UIWebView 的 HTTP 请求(包括 Ajax 请求)_第1张图片

【问题】

服务器端有一个网站需要 AD 认证,整站都开了 Basic 认证,包括图片,CSS 等资源,我在 HTTP 请求头里面添加认证所需的用户名和密码,传递到服务器端可以认证通过。我在 UIWebViewshouldStartLoadWithRequest 代理方法中拦截 UIWebView 的请求,然后在请求的 Header 中添加认证所需的用户名和密码,然后使用 NSURLSession 重新发出 HTTP 的请求,这种方法可以解决大部分的网络请求,但是无法拦截到网页内部的 Ajax 请求,所以所有的 Ajax 请求都会失败,一旦遇到 Ajax 请求,认证都会失败,并且网页会失去响应?

【解决思路】

使用 NSURLProtocol 拦截 UIWebView 内部的所有请求,包括 Ajax 请求,在所有的请求头中添加认证所需的用户名和密码。

注意:NSURLProtocol 只能拦截 UIWebViewNSURLConnectionNSURLSession 和基于 NSURLConnenctionNSURLSession 实现的第三方框架(如:AFNetworking)发出的网络请求,无法拦截 WKWebviewCFNetwork 以及基于 CFNetwork 实现的第三方框架(如:MKNetworkit)出的网络请求。

一、下面提供一个完整的 NSURLProtocol 的实现类:

// RichURLSessionProtocol.h

#import 

@interface RichURLSessionProtocol : NSURLProtocol

@end
// RichURLSessionProtocol.m
#import "RichURLSessionProtocol.h"

static NSString * const RichURLProtocolHandledKey = @"RichURLProtocolHandledKey";

@interface RichURLSessionProtocol()

@property (atomic,strong,readwrite) NSURLSessionDataTask *task;
@property (nonatomic,strong) NSURLSession *session;

@end

@implementation RichURLSessionProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    //只处理http和https请求
    NSString *scheme = [[request URL] scheme];
    if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
          [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame))
    {
        //        NSLog(@"====>%@",request.URL);
        
        //看看是否已经处理过了,防止无限循环
        if ([NSURLProtocol propertyForKey:RichURLProtocolHandledKey inRequest:request]) {
            return NO;
        }
        
        return YES;
    }
    return NO;
}

+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
    /** 可以在此处添加头等信息  */
    NSMutableURLRequest *mutableReqeust = [request mutableCopy];
    return mutableReqeust;
}
- (void)startLoading
{
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
    //打标签,防止无限循环
    [NSURLProtocol setProperty:@YES forKey:RichURLProtocolHandledKey inRequest:mutableReqeust];
    
    NSURLSessionConfiguration *configure = [NSURLSessionConfiguration defaultSessionConfiguration];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    self.session  = [NSURLSession sessionWithConfiguration:configure delegate:self delegateQueue:queue];
    self.task = [self.session dataTaskWithRequest:mutableReqeust];
    [self.task resume];
}

- (void)stopLoading
{
    [self.session invalidateAndCancel];
    self.session = nil;
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    if (error != nil) {
        [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
{
    NSMutableURLRequest*    redirectRequest;
    redirectRequest = [newRequest mutableCopy];
    [[self class] removePropertyForKey:RichURLProtocolHandledKey inRequest:redirectRequest];
    [[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response];
    
    [self.task cancel];
    [[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]];
}

- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id)client
{
    
    NSMutableURLRequest*    redirectRequest;
    redirectRequest = [request mutableCopy];
    
    //添加认证信息
    NSString *authString = [[[NSString stringWithFormat:@"%@:%@", kGlobal.userInfo.sAccount, kGlobal.userInfo.sPassword] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
    authString = [NSString stringWithFormat: @"Basic %@", authString];
    [redirectRequest setValue:authString forHTTPHeaderField:@"Authorization"];
    NSLog(@"拦截的请求:%@",request.URL.absoluteString);
    
    self = [super initWithRequest:redirectRequest cachedResponse:cachedResponse client:client];
    if (self) {
        
        // Some stuff
    }
    return self;
}

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{
    
    NSLog(@"自定义Protocol开始认证...");
    NSString *authMethod = [[challenge protectionSpace] authenticationMethod];
    NSLog(@"%@认证...",authMethod);
    
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        NSURLCredential *card = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
        completionHandler(NSURLSessionAuthChallengeUseCredential,card);
    }
    
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodNTLM]) {
        if ([challenge previousFailureCount] == 0) {
            NSURLCredential *credential = [NSURLCredential credentialWithUser:kGlobal.userInfo.sAccount password:kGlobal.userInfo.sPassword persistence:NSURLCredentialPersistenceForSession];
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
        }else{
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,nil);
        }
    }
    
    NSLog(@"自定义Protocol认证结束");
}

@end

二、使用自定义 NSURLProtocol 类的方法:

1、在单个的 UIViewController 中使用
//导入自定义NSURLProtocol类

#import "RichURLSessionProtocol.h"
//在ViewDidLoad中添加拦截网络请求的代码

//注册网络请求拦截
[NSURLProtocol registerClass:[RichURLSessionProtocol class]];
//在ViewWillDisappear中添加取消网络拦截的代码

//取消注册网络请求拦截
[NSURLProtocol unregisterClass:[RichURLSessionProtocol class]];
2、拦截整个 App 中所有的网络请求
//直接在AppDelegate中的didFinishLaunchingWithOptions注册网络拦截代码

//注册Protocol
[NSURLProtocol registerClass:[RichURLSessionProtocol class]];

三、Demo

Demo 地址

四、参考资料

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

(完)

你可能感兴趣的:(【转】使用 NSURLProtocol 和 NSURLSession 拦截 UIWebView 的 HTTP 请求(包括 Ajax 请求))