使用自定义 NSURLProtocol 时 UIWebView 不加载重定向页面的内容
UIWebView not loading contents of redirected page when using custom NSURLProtocol
我注册了一个 UIWebView 和一个自定义 NSURLProtocol class 来代理 HTTP 请求。我在加载 github.com 时遇到问题。如果我浏览到 https://github.com then it loads the page and its contents just fine. However, if I browse to http://github.com,它会正确加载 HTML,但不会加载图像或 CSS。这是我加载 https 版本时的样子:
这是我加载 http 版本时的样子:
这是我用来重现此问题的视图控制器的代码:
@interface ViewController ()
{
UIWebView *aWebView;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[NSURLProtocol registerClass:[WebViewProxyURLProtocol class]];
aWebView = [[UIWebView alloc] initWithFrame:self.view.frame];
aWebView.delegate = self;
[self.view addSubview:aWebView];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://github.com"]];
[aWebView loadRequest:request];
}
@end
这是 NSURLProtocol 的实现:
@interface WebViewProxyURLProtocol : NSURLProtocol <NSURLConnectionDataDelegate>
@end
@implementation WebViewProxyURLProtocol {
NSMutableURLRequest* correctedRequest;
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
return ([[request allHTTPHeaderFields] objectForKey:@"X-WebViewProxy"] == nil);
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return NO;
}
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client {
if (self = [super initWithRequest:request cachedResponse:cachedResponse client:client]) {
// Add header to prevent loop in proxy
correctedRequest = request.mutableCopy;
[correctedRequest addValue:@"True" forHTTPHeaderField:@"X-WebViewProxy"];
}
return self;
}
- (void)startLoading {
[NSURLConnection connectionWithRequest:correctedRequest delegate:self];
}
- (void)stopLoading {
correctedRequest = nil;
}
- (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];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
{
return request;
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
@end
如果我禁用自定义 NSURLProtocol class,它工作正常。我使用 Charles 检查 HTTP 请求和响应,无论是否使用 NSURLProtocol,它们看起来都一样。
那么问题来了:为什么UIWebView在使用NSURLConnection获取页面数据时,没有请求到网页内容呢?
我终于想通了。当我在 connection:didReceiveResponse 中收到重定向响应 (HTTP 3xx) 时:我不得不调用 [self.client URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response].
我们有同样的工作 - 重定向有效但内容未加载。
在 Apple doc 之后,他们提到应该使用 NSURLProtocol 子类中的 connection:willSendRequest:redirectResponse 委托方法处理重定向。
我们的解决方案最终看起来像这样:
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response {
if (response) {
NSMutableURLRequest *redirect = [request mutableCopy];
[NSURLProtocol removePropertyForKey:kFlagRequestHandled inRequest:redirect];
[RequestHelper addWebViewHeadersToRequest:redirect];
// THE IMPORTANT PART
[self.client URLProtocol:self wasRedirectedToRequest:redirect redirectResponse:response];
return redirect;
}
return request;
}
对于那些寻找 Swift 解决方案的人,我采用了@micmdk 所做的并对其进行了调整。
在我的例子中,我拦截了带有 URLProtocol
class 的请求。在 override func startLoading()
方法中,我根据请求设置了 属性:
override func startLoading() {
...
guard let mutableRequest = request as? NSMutableURLRequest else {
exit(0)
}
URLProtocol.setProperty("true", forKey:"requestHasBeenHandled", in: mutableRequest)
return mutableRequest as URLRequest
}
这会阻止处理重定向。所以我删除了 属性,像这样:
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
guard let redirect = request as? NSMutableURLRequest else {
exit(0)
}
URLProtocol.removeProperty(forKey: "requestHasBeenHandled", in: redirect)
self.client?.urlProtocol(self, wasRedirectedTo: redirect as URLRequest, redirectResponse: response)
}
已确认有效。图片和资产正确加载。
我注册了一个 UIWebView 和一个自定义 NSURLProtocol class 来代理 HTTP 请求。我在加载 github.com 时遇到问题。如果我浏览到 https://github.com then it loads the page and its contents just fine. However, if I browse to http://github.com,它会正确加载 HTML,但不会加载图像或 CSS。这是我加载 https 版本时的样子:
这是我加载 http 版本时的样子:
这是我用来重现此问题的视图控制器的代码:
@interface ViewController ()
{
UIWebView *aWebView;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[NSURLProtocol registerClass:[WebViewProxyURLProtocol class]];
aWebView = [[UIWebView alloc] initWithFrame:self.view.frame];
aWebView.delegate = self;
[self.view addSubview:aWebView];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://github.com"]];
[aWebView loadRequest:request];
}
@end
这是 NSURLProtocol 的实现:
@interface WebViewProxyURLProtocol : NSURLProtocol <NSURLConnectionDataDelegate>
@end
@implementation WebViewProxyURLProtocol {
NSMutableURLRequest* correctedRequest;
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
return ([[request allHTTPHeaderFields] objectForKey:@"X-WebViewProxy"] == nil);
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return NO;
}
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client {
if (self = [super initWithRequest:request cachedResponse:cachedResponse client:client]) {
// Add header to prevent loop in proxy
correctedRequest = request.mutableCopy;
[correctedRequest addValue:@"True" forHTTPHeaderField:@"X-WebViewProxy"];
}
return self;
}
- (void)startLoading {
[NSURLConnection connectionWithRequest:correctedRequest delegate:self];
}
- (void)stopLoading {
correctedRequest = nil;
}
- (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];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
{
return request;
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
@end
如果我禁用自定义 NSURLProtocol class,它工作正常。我使用 Charles 检查 HTTP 请求和响应,无论是否使用 NSURLProtocol,它们看起来都一样。
那么问题来了:为什么UIWebView在使用NSURLConnection获取页面数据时,没有请求到网页内容呢?
我终于想通了。当我在 connection:didReceiveResponse 中收到重定向响应 (HTTP 3xx) 时:我不得不调用 [self.client URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response].
我们有同样的工作 - 重定向有效但内容未加载。
在 Apple doc 之后,他们提到应该使用 NSURLProtocol 子类中的 connection:willSendRequest:redirectResponse 委托方法处理重定向。
我们的解决方案最终看起来像这样:
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response {
if (response) {
NSMutableURLRequest *redirect = [request mutableCopy];
[NSURLProtocol removePropertyForKey:kFlagRequestHandled inRequest:redirect];
[RequestHelper addWebViewHeadersToRequest:redirect];
// THE IMPORTANT PART
[self.client URLProtocol:self wasRedirectedToRequest:redirect redirectResponse:response];
return redirect;
}
return request;
}
对于那些寻找 Swift 解决方案的人,我采用了@micmdk 所做的并对其进行了调整。
在我的例子中,我拦截了带有 URLProtocol
class 的请求。在 override func startLoading()
方法中,我根据请求设置了 属性:
override func startLoading() {
...
guard let mutableRequest = request as? NSMutableURLRequest else {
exit(0)
}
URLProtocol.setProperty("true", forKey:"requestHasBeenHandled", in: mutableRequest)
return mutableRequest as URLRequest
}
这会阻止处理重定向。所以我删除了 属性,像这样:
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
guard let redirect = request as? NSMutableURLRequest else {
exit(0)
}
URLProtocol.removeProperty(forKey: "requestHasBeenHandled", in: redirect)
self.client?.urlProtocol(self, wasRedirectedTo: redirect as URLRequest, redirectResponse: response)
}
已确认有效。图片和资产正确加载。