Reputation: 398
I have a UIWebView and a custom NSURLProtocol class registered to proxy HTTP requests. I have a problem loading github.com on it. If I browse to https://github.com then it loads the page and its contents just fine. However, if I browse to http://github.com, it loads the HTML correctly but it doesn't load the images or CSS. Here's how it looks like when I load the https version:
Here's how it looks like when I load the http version:
Here's the code for the view controller that I used to reproduce this problem:
@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
Here's the NSURLProtocol implementation:
@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
If I disable the custom NSURLProtocol class, it works fine. I used Charles to inspect the HTTP requests and responses and they look identical with and without NSURLProtocol.
So the question is: why does UIWebView not request the contents of the web page when using NSURLConnection to get the page data?
Upvotes: 3
Views: 3967
Reputation: 11
For those looking for a Swift solution, I have taken what @micmdk did and adapted it.
In my case, I intercept requests with a URLProtocol
class. In the override func startLoading()
method I set a property on the request:
override func startLoading() {
...
guard let mutableRequest = request as? NSMutableURLRequest else {
exit(0)
}
URLProtocol.setProperty("true", forKey:"requestHasBeenHandled", in: mutableRequest)
return mutableRequest as URLRequest
}
This prevents redirects from being handled. So I remove the property, like so:
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)
}
Have confirmed that it works. Images and assets load correctly.
Upvotes: 1
Reputation: 951
We had the same work - the redirect worked but the content wasn't loaded.
Following the Apple doc they mention that redirects should be handled using the connection:willSendRequest:redirectResponse delegate method in the NSURLProtocol subclass.
Our solution did end up looking like this:
- (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;
}
Upvotes: 5
Reputation: 398
I figured this out eventually. When I get a redirect response (HTTP 3xx) in connection:didReceiveResponse: I had to call [self.client URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response].
Upvotes: 4