一些需要配hosts的情形:
配hosts的方式:
上面修改hosts的方式有些不方便。都要修改hosts文件。 下面介绍一种不需要修改hosts文件,在开发过程中可以自由切换线上线下环境的配置hosts的方法。
NSURLProtocol
详细的URLProtocol用法,这里就不赘述了。可以自己去google或者参考这篇文章。 大概说一下NSURLProtocol的作用以及用法
NSURLProtocol的作用
NSURLProtocol可以捕获App上所有的网络请求(包括UIWebView中JS产生的一些请求),进行一些中间的操作,比如可以进行缓存的优化、hosts的修改、请求重定向、自定义网络请求的返回结果
NSURLProtocol的用法
定义一子类MyURLProtocol继承NSURLProtocol。实现以下方法
URL Loading System当接收到一个请求,它会去搜索已经注册过的protocol去处理这个请求。每个自定义的protocol都会告诉系统,他是否会处理这个请求。就是通过这个函数的返回值来判断.如果没有protocol可以处理这个请求,系统会自己去处理这个请求
1 2 3 4 5 6 7 8
| + (BOOL)canInitWithRequest:(NSURLRequest *)request { //看看是否已经处理过了,防止无限循环 if ([NSURLProtocol propertyForKey:@"URLProtocolHandledKey" inRequest:request]) { return NO; } return YES; }
|
- +canonicalRequestForRequest:
一般是在这个函数修改捕获的request的header等。
1 2 3
| + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; }
|
- +requestIsCacheEquivalent:toRequest: 判断你定制的两个请求是否一样,如果一样应该使用同样的缓存. 一般是给父类调用
1 2 3
| + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { return [super requestIsCacheEquivalent:a toRequest:b]; }
|
- -startLoading -stopLoading 这里是URL Loading System通知你的URLprotocol开始加载处理一个请求/停止一个请求。一般是再这里开始自己的NSURLConnection
1 2 3 4 5 6 7 8 9 10 11 12
| - (void)startLoading { NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy]; //标示改request已经处理过了,防止无限循环。因为这里用NSURLConnection去请求数据,又会走一遍Protocol的流程 [NSURLProtocol setProperty:@YES forKey:@"URLProtocolHandledKey" inRequest:mutableReqeust]; self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self]; }
- (void)stopLoading { [self.connection cancel]; }
|
注
另外可以调用 urlprotocol的didLoadData来缓存数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| - (void) startLoading { // 这里可以自定义Response返回 id<NSURLProtocolClient> client = [self client]; NSURLRequest* request = [self request]; NSString *pathString = [[request URL] absoluteString]; NSString *fileName = [pathString lastPathComponent]; NSString *fileToLoad = nil; fileToLoad = [pathString substringFromIndex:7]; NSLog(@"%@\n%@", pathString, fileToLoad); NSData *data = [NSData dataWithContentsOfFile:fileToLoad]; NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:[request URL] statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:[NSDictionary dictionary]]; [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [client URLProtocol:self didLoadData:data]; [client URLProtocolDidFinishLoading:self]; }
|
- NSURLConnectionDataDelegate URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| - (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; }
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.client URLProtocol:self didLoadData:data]; }
- (void) connectionDidFinishLoading:(NSURLConnection *)connection { [self.client URLProtocolDidFinishLoading:self]; }
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { [self.client URLProtocol:self didFailWithError:error]; NSLog(@"error: %@",error); }
|
注册MyURLProtocol,使得自定义的URLProtocol生效
注册了自定义的URLProtocol后,就能拦截系统的网络请求。包括UIWebView上的请求
1 2 3 4
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [NSURLProtocol registerClass:[MyURLProtocol class]]; return YES; }
|
修改Host
替换URL中的host,并且设置header
- 在函数canonicalRequestForRequest捕获到URL请求后,mutableCopy一份request。替换URL中的host,设置header
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| + (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request { if ([request.URL host].length == 0) { return request; } NSMutableURLRequest *Myrequest = [request mutableCopy]; //设置UA,伪装成是safari打开。 因为我的网站有判断,不允许safari以外的浏览器打开 [Myrequest setValue:@"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/601.4.4 (KHTML, like Gecko) Version/9.0.3 Safari/601.4.4" forHTTPHeaderField:@"user-agent"]; NSString *originUrlString = [request.URL absoluteString]; NSArray *allHosts = [hostDict allKeys]; for (NSString *host in allHosts) { NSString *ip = [hostDict objectForKey:host]; //查找请求链接的host是否在host文件中配置了 NSRange hostRange = [originUrlString rangeOfString:host]; //查找请求链接的host(可能是ip)是否在host文件中配置了。 用于给httpHeader加Host字段。有时候整个网页重定向后,请求链接就会变成是ip组成的host,这个时候也要给加Host头 NSRange ipRange = [originUrlString rangeOfString:ip]; if (hostRange.location != NSNotFound) { NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip]; NSURL *url = [NSURL URLWithString:urlString]; Myrequest.URL = url; [Myrequest setValue:host forHTTPHeaderField:@"Host"]; break; }else if (ipRange.location != NSNotFound){ [Myrequest setValue:host forHTTPHeaderField:@"Host"]; break; } } return Myrequest; }
|
- 如果有重定向(一般发生在UIWebView上)。也要对重定向的链接做处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response { NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy]; [mutableReqeust setValue:@"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/601.4.4 (KHTML, like Gecko) Version/9.0.3 Safari/601.4.4" forHTTPHeaderField:@"user-agent"]; if (response) { if ([request.URL host].length == 0) { return request; } NSString *originUrlString = [request.URL absoluteString]; NSString *originHostString = @""; NSArray *hosts = [hostDict allKeys]; NSRange hostRange = NSMakeRange(NSNotFound, 0); for (int i = 0 ; i < hosts.count ; i++ ) { originHostString = hosts[i]; hostRange = [originUrlString rangeOfString:originHostString]; if (hostRange.location != NSNotFound) { break; } } NSString *ip = [hostDict objectForKey:originHostString];; // 替换域名 NSString *urlString = originUrlString; if (hostRange.location != NSNotFound) { urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip]; } NSURL *url = [NSURL URLWithString:urlString]; mutableReqeust.URL = url; if (hostRange.location != NSNotFound) { [mutableReqeust setValue:originHostString forHTTPHeaderField:@"Host"]; } [self.client URLProtocol:self wasRedirectedToRequest:mutableReqeust redirectResponse:response]; return mutableReqeust; }else{ return mutableReqeust; }
}
|
另外
如果你使用这个例子,不能很好的运行你需要配Host的网站。可以打印每个请求的header跟url调试。看看哪里不对,然后做相应的处理
绕过https检查
有时候你的网站可能用了不被信任的证书,给你的URLConnectionDelegate 增加下面两个方法。绕过HTTPS检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| //-----https - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection { return YES; }
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if ([challenge previousFailureCount]== 0) { //NSURLCredential 这个类是表示身份验证凭据不可变对象。凭证的实际类型声明的类的构造函数来确定。 NSURLCredential* cre = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [challenge.sender useCredential:cre forAuthenticationChallenge:challenge]; [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; } else { [challenge.sender cancelAuthenticationChallenge:challenge]; } }
|
例子看这里