blob: 0289e81d630f848a358f9dac2a4aae8fb0025f61 [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/net/crn_http_protocol_handler_proxy_with_client_thread.h"
#include "base/logging.h"
#import "base/mac/scoped_nsobject.h"
#include "base/time/time.h"
#import "ios/net/protocol_handler_util.h"
#include "net/base/auth.h"
#include "net/url_request/url_request.h"
// When the protocol is invalidated, no synchronization (lock) is needed:
// - The actual calls to the protocol and its invalidation are all done on
// clientThread_ and thus are serialized.
// - When a proxy method is called, the protocol is compared to nil. There may
// be a conflict at this point, in the case the protocol is being invalidated
// during this comparison. However, in such a case, the actual value of the
// pointer does not matter: an invalid pointer will behave as a valid one and
// post a task on the clientThread_, and that task will be handled correctly,
// as described by the item above.
@interface CRNHTTPProtocolHandlerProxyWithClientThread () {
__weak NSURLProtocol* _protocol;
// Thread used to call the client back.
// This thread does not have a base::MessageLoop, and thus does not work with
// the usual task posting functions.
__weak NSThread* _clientThread;
// The run loop modes to use when posting tasks to |clientThread_|.
base::scoped_nsobject<NSArray> _runLoopModes;
// The request URL.
base::scoped_nsobject<NSString> _url;
// The creation time of the request.
base::Time _creationTime;
// |requestComplete_| is used in debug to check that the client is not called
// after completion.
BOOL _requestComplete;
}
// Performs the selector on |clientThread_| using |runLoopModes_|.
- (void)performSelectorOnClientThread:(SEL)aSelector withObject:(id)arg;
// These functions are just wrappers around the corresponding
// NSURLProtocolClient methods, used for task posting.
- (void)didFailWithErrorOnClientThread:(NSError*)error;
- (void)didLoadDataOnClientThread:(NSData*)data;
- (void)didReceiveResponseOnClientThread:(NSURLResponse*)response;
- (void)wasRedirectedToRequestOnClientThread:(NSArray*)params;
- (void)didFinishLoadingOnClientThread;
@end
@implementation CRNHTTPProtocolHandlerProxyWithClientThread
- (instancetype)initWithProtocol:(NSURLProtocol*)protocol
clientThread:(NSThread*)clientThread
runLoopMode:(NSString*)mode {
DCHECK(protocol);
DCHECK(clientThread);
if ((self = [super init])) {
_protocol = protocol;
_url.reset([[[[protocol request] URL] absoluteString] copy]);
_creationTime = base::Time::Now();
_clientThread = clientThread;
// Use the common run loop mode in addition to the client thread mode, in
// hope that our tasks are executed even if the client thread changes mode
// later on.
if ([mode isEqualToString:NSRunLoopCommonModes])
_runLoopModes.reset([@[ NSRunLoopCommonModes ] retain]);
else
_runLoopModes.reset([@[ mode, NSRunLoopCommonModes ] retain]);
}
return self;
}
- (void)invalidate {
DCHECK([NSThread currentThread] == _clientThread);
_protocol = nil;
_requestComplete = YES;
}
- (void)performSelectorOnClientThread:(SEL)aSelector withObject:(id)arg {
[self performSelector:aSelector
onThread:_clientThread
withObject:arg
waitUntilDone:NO
modes:_runLoopModes];
}
#pragma mark Proxy methods called from any thread.
- (void)didFailWithNSErrorCode:(NSInteger)nsErrorCode
netErrorCode:(int)netErrorCode {
DCHECK(_clientThread);
if (!_protocol)
return;
NSError* error =
net::GetIOSError(nsErrorCode, netErrorCode, _url, _creationTime);
[self performSelectorOnClientThread:@selector(didFailWithErrorOnClientThread:)
withObject:error];
}
- (void)didLoadData:(NSData*)data {
DCHECK(_clientThread);
if (!_protocol)
return;
[self performSelectorOnClientThread:@selector(didLoadDataOnClientThread:)
withObject:data];
}
- (void)didReceiveResponse:(NSURLResponse*)response {
DCHECK(_clientThread);
if (!_protocol)
return;
[self
performSelectorOnClientThread:@selector(didReceiveResponseOnClientThread:)
withObject:response];
}
- (void)wasRedirectedToRequest:(NSURLRequest*)request
nativeRequest:(net::URLRequest*)nativeRequest
redirectResponse:(NSURLResponse*)redirectResponse {
DCHECK(_clientThread);
if (!_protocol)
return;
[self performSelectorOnClientThread:@selector(
wasRedirectedToRequestOnClientThread:)
withObject:@[ request, redirectResponse ]];
}
- (void)didFinishLoading {
DCHECK(_clientThread);
if (!_protocol)
return;
[self performSelectorOnClientThread:@selector(didFinishLoadingOnClientThread)
withObject:nil];
}
// Feature support methods that don't forward to the NSURLProtocolClient.
- (void)didCreateNativeRequest:(net::URLRequest*)nativeRequest {
// no-op.
}
- (void)didRecieveAuthChallenge:(net::AuthChallengeInfo*)authInfo
nativeRequest:(const net::URLRequest&)nativeRequest
callback:(const network_client::AuthCallback&)callback {
// If we get this far, authentication has failed.
base::string16 empty;
callback.Run(false, empty, empty);
}
- (void)cancelAuthRequest {
// no-op.
}
- (void)setUnderlyingClient:(id<CRNNetworkClientProtocol>)underlyingClient {
// This is the lowest level.
DCHECK(!underlyingClient);
}
#pragma mark Proxy methods called from the client thread.
- (void)didFailWithErrorOnClientThread:(NSError*)error {
DCHECK([NSThread currentThread] == _clientThread);
DCHECK(!_requestComplete || !_protocol);
_requestComplete = YES;
[[_protocol client] URLProtocol:_protocol didFailWithError:error];
}
- (void)didLoadDataOnClientThread:(NSData*)data {
DCHECK([NSThread currentThread] == _clientThread);
DCHECK(!_requestComplete || !_protocol);
[[_protocol client] URLProtocol:_protocol didLoadData:data];
}
- (void)didReceiveResponseOnClientThread:(NSURLResponse*)response {
DCHECK([NSThread currentThread] == _clientThread);
DCHECK(!_requestComplete || !_protocol);
[[_protocol client] URLProtocol:_protocol
didReceiveResponse:response
cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)wasRedirectedToRequestOnClientThread:(NSArray*)params {
DCHECK([NSThread currentThread] == _clientThread);
DCHECK_EQ(2u, [params count]);
DCHECK([params[0] isKindOfClass:[NSURLRequest class]]);
DCHECK([params[1] isKindOfClass:[NSURLResponse class]]);
DCHECK(!_requestComplete || !_protocol);
[[_protocol client] URLProtocol:_protocol
wasRedirectedToRequest:params[0]
redirectResponse:params[1]];
}
- (void)didFinishLoadingOnClientThread {
DCHECK([NSThread currentThread] == _clientThread);
DCHECK(!_requestComplete || !_protocol);
_requestComplete = YES;
[[_protocol client] URLProtocolDidFinishLoading:_protocol];
}
@end