crn_http_protocol_handler_proxy_with_client_thread.mm 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. // Copyright 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #import "ios/net/crn_http_protocol_handler_proxy_with_client_thread.h"
  5. #include <stddef.h>
  6. #include "base/check.h"
  7. #include "base/time/time.h"
  8. #import "ios/net/protocol_handler_util.h"
  9. #include "net/base/auth.h"
  10. #include "net/url_request/url_request.h"
  11. #if !defined(__has_feature) || !__has_feature(objc_arc)
  12. #error "This file requires ARC support."
  13. #endif
  14. // When the protocol is invalidated, no synchronization (lock) is needed:
  15. // - The actual calls to the protocol and its invalidation are all done on
  16. // clientThread_ and thus are serialized.
  17. // - When a proxy method is called, the protocol is compared to nil. There may
  18. // be a conflict at this point, in the case the protocol is being invalidated
  19. // during this comparison. However, in such a case, the actual value of the
  20. // pointer does not matter: an invalid pointer will behave as a valid one and
  21. // post a task on the clientThread_, and that task will be handled correctly,
  22. // as described by the item above.
  23. @interface CRNHTTPProtocolHandlerProxyWithClientThread () {
  24. __weak NSURLProtocol* _protocol;
  25. // Thread used to call the client back.
  26. // This thread does not have a base::MessageLoop, and thus does not work with
  27. // the usual task posting functions.
  28. __weak NSThread* _clientThread;
  29. // The run loop modes to use when posting tasks to |clientThread_|.
  30. NSArray* _runLoopModes;
  31. // The request URL.
  32. NSString* _url;
  33. // The creation time of the request.
  34. base::Time _creationTime;
  35. // |requestComplete_| is used in debug to check that the client is not called
  36. // after completion.
  37. BOOL _requestComplete;
  38. BOOL _paused;
  39. // Contains code blocks to execute when the connection transitions from paused
  40. // to resumed state.
  41. NSMutableArray<void (^)()>* _queuedBlocks;
  42. }
  43. // Performs queued blocks on |clientThread_| using |runLoopModes_|.
  44. - (void)runQueuedBlocksOnClientThread;
  45. // These functions are just wrappers around the corresponding
  46. // NSURLProtocolClient methods, used for task posting.
  47. - (void)didFailWithErrorOnClientThread:(NSError*)error;
  48. - (void)didLoadDataOnClientThread:(NSData*)data;
  49. - (void)didReceiveResponseOnClientThread:(NSURLResponse*)response;
  50. - (void)wasRedirectedToRequestOnClientThread:(NSURLRequest*)request
  51. redirectResponse:(NSURLResponse*)response;
  52. - (void)didFinishLoadingOnClientThread;
  53. @end
  54. @implementation CRNHTTPProtocolHandlerProxyWithClientThread
  55. - (instancetype)initWithProtocol:(NSURLProtocol*)protocol
  56. clientThread:(NSThread*)clientThread
  57. runLoopMode:(NSString*)mode {
  58. DCHECK(protocol);
  59. DCHECK(clientThread);
  60. if ((self = [super init])) {
  61. _protocol = protocol;
  62. _url = [[[[protocol request] URL] absoluteString] copy];
  63. _creationTime = base::Time::Now();
  64. _clientThread = clientThread;
  65. // Use the common run loop mode in addition to the client thread mode, in
  66. // hope that our tasks are executed even if the client thread changes mode
  67. // later on.
  68. if ([mode isEqualToString:NSRunLoopCommonModes])
  69. _runLoopModes = @[ NSRunLoopCommonModes ];
  70. else
  71. _runLoopModes = @[ mode, NSRunLoopCommonModes ];
  72. _queuedBlocks = [[NSMutableArray alloc] init];
  73. }
  74. return self;
  75. }
  76. - (void)invalidate {
  77. DCHECK([NSThread currentThread] == _clientThread);
  78. _protocol = nil;
  79. _requestComplete = YES;
  80. // Note that there may still be queued blocks here, if the chrome network
  81. // stack continues to emit events after the system network stack has paused
  82. // the request, and then the system network stack destroys the request.
  83. _queuedBlocks = nil;
  84. }
  85. - (void)runQueuedBlocksOnClientThread {
  86. DCHECK([NSThread currentThread] == _clientThread);
  87. DCHECK(!_requestComplete || !_protocol);
  88. // Each of the queued blocks may cause the system network stack to pause
  89. // this request, in which case |runQueuedBlocksOnClientThread| should
  90. // immediately stop running further queued invocations. The queue will be
  91. // drained again the next time the system network stack calls |resume|.
  92. //
  93. // Specifically, the system stack can call back into |pause| with this
  94. // function still on the call stack. However, since new blocks are
  95. // enqueued on this thread via posted invocations, no new blocks can be
  96. // added while this function is running.
  97. while (!_paused && _queuedBlocks.count > 0) {
  98. void (^block)() = _queuedBlocks[0];
  99. // Since |_queuedBlocks| owns the only reference to each queued
  100. // block, this function has to retain another reference before removing
  101. // the queued block from the array.
  102. block();
  103. [_queuedBlocks removeObjectAtIndex:0];
  104. }
  105. }
  106. - (void)postBlockToClientThread:(dispatch_block_t)block {
  107. DCHECK(block);
  108. [self performSelector:@selector(performBlockOnClientThread:)
  109. onThread:_clientThread
  110. withObject:[block copy]
  111. waitUntilDone:NO
  112. modes:_runLoopModes];
  113. }
  114. - (void)performBlockOnClientThread:(dispatch_block_t)block {
  115. DCHECK([NSThread currentThread] == _clientThread);
  116. DCHECK(!_requestComplete || !_protocol);
  117. DCHECK(block);
  118. if (!_paused) {
  119. block();
  120. } else {
  121. [_queuedBlocks addObject:block];
  122. }
  123. }
  124. #pragma mark Proxy methods called from any thread.
  125. - (void)didFailWithNSErrorCode:(NSInteger)nsErrorCode
  126. netErrorCode:(int)netErrorCode {
  127. DCHECK(_clientThread);
  128. if (!_protocol)
  129. return;
  130. NSError* error =
  131. net::GetIOSError(nsErrorCode, netErrorCode, _url, _creationTime);
  132. [self postBlockToClientThread:^{
  133. [self didFailWithErrorOnClientThread:error];
  134. }];
  135. }
  136. - (void)didLoadData:(NSData*)data {
  137. DCHECK(_clientThread);
  138. if (!_protocol)
  139. return;
  140. [self postBlockToClientThread:^{
  141. [self didLoadDataOnClientThread:data];
  142. }];
  143. }
  144. - (void)didReceiveResponse:(NSURLResponse*)response {
  145. DCHECK(_clientThread);
  146. if (!_protocol)
  147. return;
  148. [self postBlockToClientThread:^{
  149. [self didReceiveResponseOnClientThread:response];
  150. }];
  151. }
  152. - (void)wasRedirectedToRequest:(NSURLRequest*)request
  153. nativeRequest:(net::URLRequest*)nativeRequest
  154. redirectResponse:(NSURLResponse*)redirectResponse {
  155. DCHECK(_clientThread);
  156. if (!_protocol)
  157. return;
  158. [self postBlockToClientThread:^{
  159. [self wasRedirectedToRequestOnClientThread:request
  160. redirectResponse:redirectResponse];
  161. }];
  162. }
  163. - (void)didFinishLoading {
  164. DCHECK(_clientThread);
  165. if (!_protocol)
  166. return;
  167. [self postBlockToClientThread:^{
  168. [self didFinishLoadingOnClientThread];
  169. }];
  170. }
  171. // Feature support methods that don't forward to the NSURLProtocolClient.
  172. - (void)didCreateNativeRequest:(net::URLRequest*)nativeRequest {
  173. // no-op.
  174. }
  175. #pragma mark Proxy methods called from the client thread.
  176. - (void)didFailWithErrorOnClientThread:(NSError*)error {
  177. _requestComplete = YES;
  178. [[_protocol client] URLProtocol:_protocol didFailWithError:error];
  179. }
  180. - (void)didLoadDataOnClientThread:(NSData*)data {
  181. [[_protocol client] URLProtocol:_protocol didLoadData:data];
  182. }
  183. - (void)didReceiveResponseOnClientThread:(NSURLResponse*)response {
  184. [[_protocol client] URLProtocol:_protocol
  185. didReceiveResponse:response
  186. cacheStoragePolicy:NSURLCacheStorageNotAllowed];
  187. }
  188. - (void)wasRedirectedToRequestOnClientThread:(NSURLRequest*)request
  189. redirectResponse:(NSURLResponse*)redirectResponse {
  190. [[_protocol client] URLProtocol:_protocol
  191. wasRedirectedToRequest:request
  192. redirectResponse:redirectResponse];
  193. }
  194. - (void)didFinishLoadingOnClientThread {
  195. _requestComplete = YES;
  196. [[_protocol client] URLProtocolDidFinishLoading:_protocol];
  197. }
  198. - (void)pause {
  199. DCHECK([NSThread currentThread] == _clientThread);
  200. // It's legal (in fact, required) for |pause| to be called after the request
  201. // has already finished, so the usual invalidation DCHECK is missing here.
  202. _paused = YES;
  203. }
  204. - (void)resume {
  205. DCHECK([NSThread currentThread] == _clientThread);
  206. DCHECK(!_requestComplete || !_protocol);
  207. _paused = NO;
  208. [self runQueuedBlocksOnClientThread];
  209. }
  210. @end