|
|
|
@ -56,7 +56,6 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
// Make them read-write. |
|
|
|
|
@property(atomic, strong) NSDictionary *responseHeaders; |
|
|
|
|
@property(atomic, strong) NSDictionary *responseTrailers; |
|
|
|
|
@property(atomic) BOOL isWaitingForToken; |
|
|
|
|
|
|
|
|
|
- (instancetype)initWithHost:(NSString *)host |
|
|
|
|
path:(NSString *)path |
|
|
|
@ -425,9 +424,6 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
// queue |
|
|
|
|
dispatch_queue_t _responseQueue; |
|
|
|
|
|
|
|
|
|
// Whether the call is finished. If it is, should not call finishWithError again. |
|
|
|
|
BOOL _finished; |
|
|
|
|
|
|
|
|
|
// The OAuth2 token fetched from a token provider. |
|
|
|
|
NSString *_fetchedOauth2AccessToken; |
|
|
|
|
} |
|
|
|
@ -448,24 +444,28 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path]; |
|
|
|
|
switch (callSafety) { |
|
|
|
|
case GRPCCallSafetyDefault: |
|
|
|
|
callFlags[hostAndPath] = @0; |
|
|
|
|
break; |
|
|
|
|
case GRPCCallSafetyIdempotentRequest: |
|
|
|
|
callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST; |
|
|
|
|
break; |
|
|
|
|
case GRPCCallSafetyCacheableRequest: |
|
|
|
|
callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_CACHEABLE_REQUEST; |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
break; |
|
|
|
|
@synchronized(callFlags) { |
|
|
|
|
switch (callSafety) { |
|
|
|
|
case GRPCCallSafetyDefault: |
|
|
|
|
callFlags[hostAndPath] = @0; |
|
|
|
|
break; |
|
|
|
|
case GRPCCallSafetyIdempotentRequest: |
|
|
|
|
callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST; |
|
|
|
|
break; |
|
|
|
|
case GRPCCallSafetyCacheableRequest: |
|
|
|
|
callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_CACHEABLE_REQUEST; |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
+ (uint32_t)callFlagsForHost:(NSString *)host path:(NSString *)path { |
|
|
|
|
NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path]; |
|
|
|
|
return [callFlags[hostAndPath] intValue]; |
|
|
|
|
@synchronized(callFlags) { |
|
|
|
|
return [callFlags[hostAndPath] intValue]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Designated initializer |
|
|
|
@ -506,7 +506,7 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
_callOptions = [callOptions copy]; |
|
|
|
|
|
|
|
|
|
// Serial queue to invoke the non-reentrant methods of the grpc_call object. |
|
|
|
|
_callQueue = dispatch_queue_create("io.grpc.call", NULL); |
|
|
|
|
_callQueue = dispatch_queue_create("io.grpc.call", DISPATCH_QUEUE_SERIAL); |
|
|
|
|
|
|
|
|
|
_requestWriter = requestWriter; |
|
|
|
|
|
|
|
|
@ -523,66 +523,48 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)setResponseDispatchQueue:(dispatch_queue_t)queue { |
|
|
|
|
if (_state != GRXWriterStateNotStarted) { |
|
|
|
|
return; |
|
|
|
|
@synchronized(self) { |
|
|
|
|
if (_state != GRXWriterStateNotStarted) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
_responseQueue = queue; |
|
|
|
|
} |
|
|
|
|
_responseQueue = queue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#pragma mark Finish |
|
|
|
|
|
|
|
|
|
// This function should support being called within a @synchronized(self) block in another function |
|
|
|
|
// Should not manipulate _requestWriter for deadlock prevention. |
|
|
|
|
- (void)finishWithError:(NSError *)errorOrNil { |
|
|
|
|
@synchronized(self) { |
|
|
|
|
if (_state == GRXWriterStateFinished) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
_state = GRXWriterStateFinished; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// If there were still request messages coming, stop them. |
|
|
|
|
@synchronized(_requestWriter) { |
|
|
|
|
_requestWriter.state = GRXWriterStateFinished; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (errorOrNil) { |
|
|
|
|
[_responseWriteable cancelWithError:errorOrNil]; |
|
|
|
|
} else { |
|
|
|
|
[_responseWriteable enqueueSuccessfulCompletion]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[GRPCConnectivityMonitor unregisterObserver:self]; |
|
|
|
|
|
|
|
|
|
// If the call isn't retained anywhere else, it can be deallocated now. |
|
|
|
|
_retainSelf = nil; |
|
|
|
|
} |
|
|
|
|
if (errorOrNil) { |
|
|
|
|
[_responseWriteable cancelWithError:errorOrNil]; |
|
|
|
|
} else { |
|
|
|
|
[_responseWriteable enqueueSuccessfulCompletion]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)cancelCall { |
|
|
|
|
// Can be called from any thread, any number of times. |
|
|
|
|
@synchronized(self) { |
|
|
|
|
[_wrappedCall cancel]; |
|
|
|
|
// If the call isn't retained anywhere else, it can be deallocated now. |
|
|
|
|
_retainSelf = nil; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)cancel { |
|
|
|
|
@synchronized(self) { |
|
|
|
|
[self cancelCall]; |
|
|
|
|
self.isWaitingForToken = NO; |
|
|
|
|
} |
|
|
|
|
[self |
|
|
|
|
maybeFinishWithError:[NSError |
|
|
|
|
errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeCancelled |
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey : @"Canceled by app"}]]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)maybeFinishWithError:(NSError *)errorOrNil { |
|
|
|
|
BOOL toFinish = NO; |
|
|
|
|
@synchronized(self) { |
|
|
|
|
if (_finished == NO) { |
|
|
|
|
_finished = YES; |
|
|
|
|
toFinish = YES; |
|
|
|
|
if (_state == GRXWriterStateFinished) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
[self finishWithError:[NSError |
|
|
|
|
errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeCancelled |
|
|
|
|
userInfo:@{NSLocalizedDescriptionKey : @"Canceled by app"}]]; |
|
|
|
|
[_wrappedCall cancel]; |
|
|
|
|
} |
|
|
|
|
if (toFinish == YES) { |
|
|
|
|
[self finishWithError:errorOrNil]; |
|
|
|
|
} |
|
|
|
|
_requestWriter.state = GRXWriterStateFinished; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)dealloc { |
|
|
|
@ -609,21 +591,24 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
// TODO(jcanizales): Rename to readResponseIfNotPaused. |
|
|
|
|
- (void)startNextRead { |
|
|
|
|
@synchronized(self) { |
|
|
|
|
if (self.state == GRXWriterStatePaused) { |
|
|
|
|
if (_state != GRXWriterStateStarted) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
dispatch_async(_callQueue, ^{ |
|
|
|
|
__weak GRPCCall *weakSelf = self; |
|
|
|
|
__weak GRXConcurrentWriteable *weakWriteable = self->_responseWriteable; |
|
|
|
|
[self startReadWithHandler:^(grpc_byte_buffer *message) { |
|
|
|
|
__strong GRPCCall *strongSelf = weakSelf; |
|
|
|
|
__strong GRXConcurrentWriteable *strongWriteable = weakWriteable; |
|
|
|
|
NSLog(@"message received"); |
|
|
|
|
if (message == NULL) { |
|
|
|
|
// No more messages from the server |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
__strong GRPCCall *strongSelf = weakSelf; |
|
|
|
|
if (strongSelf == nil) { |
|
|
|
|
grpc_byte_buffer_destroy(message); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
NSData *data = [NSData grpc_dataWithByteBuffer:message]; |
|
|
|
|
grpc_byte_buffer_destroy(message); |
|
|
|
|
if (!data) { |
|
|
|
@ -631,21 +616,26 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
// don't want to throw, because the app shouldn't crash for a behavior |
|
|
|
|
// that's on the hands of any server to have. Instead we finish and ask |
|
|
|
|
// the server to cancel. |
|
|
|
|
[strongSelf cancelCall]; |
|
|
|
|
[strongSelf |
|
|
|
|
maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeResourceExhausted |
|
|
|
|
userInfo:@{ |
|
|
|
|
NSLocalizedDescriptionKey : |
|
|
|
|
@"Client does not have enough memory to " |
|
|
|
|
@"hold the server response." |
|
|
|
|
}]]; |
|
|
|
|
return; |
|
|
|
|
@synchronized(strongSelf) { |
|
|
|
|
[strongSelf |
|
|
|
|
finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeResourceExhausted |
|
|
|
|
userInfo:@{ |
|
|
|
|
NSLocalizedDescriptionKey : |
|
|
|
|
@"Client does not have enough memory to " |
|
|
|
|
@"hold the server response." |
|
|
|
|
}]]; |
|
|
|
|
[strongSelf->_wrappedCall cancel]; |
|
|
|
|
} |
|
|
|
|
strongSelf->_requestWriter.state = GRXWriterStateFinished; |
|
|
|
|
} else { |
|
|
|
|
@synchronized(strongSelf) { |
|
|
|
|
[strongSelf->_responseWriteable enqueueValue:data |
|
|
|
|
completionHandler:^{ |
|
|
|
|
[strongSelf startNextRead]; |
|
|
|
|
}]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
[strongWriteable enqueueValue:data |
|
|
|
|
completionHandler:^{ |
|
|
|
|
[strongSelf startNextRead]; |
|
|
|
|
}]; |
|
|
|
|
}]; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
@ -684,11 +674,13 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
initWithMetadata:headers |
|
|
|
|
flags:callSafetyFlags |
|
|
|
|
handler:nil]; // No clean-up needed after SEND_INITIAL_METADATA |
|
|
|
|
if (!_unaryCall) { |
|
|
|
|
[_wrappedCall startBatchWithOperations:@[ op ]]; |
|
|
|
|
} else { |
|
|
|
|
[_unaryOpBatch addObject:op]; |
|
|
|
|
} |
|
|
|
|
dispatch_async(_callQueue, ^{ |
|
|
|
|
if (!self->_unaryCall) { |
|
|
|
|
[self->_wrappedCall startBatchWithOperations:@[ op ]]; |
|
|
|
|
} else { |
|
|
|
|
[self->_unaryOpBatch addObject:op]; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#pragma mark GRXWriteable implementation |
|
|
|
@ -703,9 +695,7 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
// Resume the request writer. |
|
|
|
|
GRPCCall *strongSelf = weakSelf; |
|
|
|
|
if (strongSelf) { |
|
|
|
|
@synchronized(strongSelf->_requestWriter) { |
|
|
|
|
strongSelf->_requestWriter.state = GRXWriterStateStarted; |
|
|
|
|
} |
|
|
|
|
strongSelf->_requestWriter.state = GRXWriterStateStarted; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -721,13 +711,17 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)writeValue:(id)value { |
|
|
|
|
// TODO(jcanizales): Throw/assert if value isn't NSData. |
|
|
|
|
NSAssert([value isKindOfClass:[NSData class]], @"value must be of type NSData"); |
|
|
|
|
|
|
|
|
|
@synchronized(self) { |
|
|
|
|
if (_state == GRXWriterStateFinished) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Pause the input and only resume it when the C layer notifies us that writes |
|
|
|
|
// can proceed. |
|
|
|
|
@synchronized(_requestWriter) { |
|
|
|
|
_requestWriter.state = GRXWriterStatePaused; |
|
|
|
|
} |
|
|
|
|
_requestWriter.state = GRXWriterStatePaused; |
|
|
|
|
|
|
|
|
|
dispatch_async(_callQueue, ^{ |
|
|
|
|
// Write error is not processed here. It is handled by op batch of GRPC_OP_RECV_STATUS_ON_CLIENT |
|
|
|
@ -766,17 +760,20 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
// The second one (completionHandler), whenever the RPC finishes for any reason. |
|
|
|
|
- (void)invokeCallWithHeadersHandler:(void (^)(NSDictionary *))headersHandler |
|
|
|
|
completionHandler:(void (^)(NSError *, NSDictionary *))completionHandler { |
|
|
|
|
// TODO(jcanizales): Add error handlers for async failures |
|
|
|
|
[_wrappedCall |
|
|
|
|
startBatchWithOperations:@[ [[GRPCOpRecvMetadata alloc] initWithHandler:headersHandler] ]]; |
|
|
|
|
[_wrappedCall |
|
|
|
|
startBatchWithOperations:@[ [[GRPCOpRecvStatus alloc] initWithHandler:completionHandler] ]]; |
|
|
|
|
dispatch_async(_callQueue, ^{ |
|
|
|
|
// TODO(jcanizales): Add error handlers for async failures |
|
|
|
|
[self->_wrappedCall |
|
|
|
|
startBatchWithOperations:@[ [[GRPCOpRecvMetadata alloc] initWithHandler:headersHandler] ]]; |
|
|
|
|
[self->_wrappedCall |
|
|
|
|
startBatchWithOperations:@[ [[GRPCOpRecvStatus alloc] initWithHandler:completionHandler] ]]; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)invokeCall { |
|
|
|
|
__weak GRPCCall *weakSelf = self; |
|
|
|
|
[self invokeCallWithHeadersHandler:^(NSDictionary *headers) { |
|
|
|
|
// Response headers received. |
|
|
|
|
NSLog(@"response received"); |
|
|
|
|
__strong GRPCCall *strongSelf = weakSelf; |
|
|
|
|
if (strongSelf) { |
|
|
|
|
strongSelf.responseHeaders = headers; |
|
|
|
@ -784,6 +781,7 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
completionHandler:^(NSError *error, NSDictionary *trailers) { |
|
|
|
|
NSLog(@"completion received"); |
|
|
|
|
__strong GRPCCall *strongSelf = weakSelf; |
|
|
|
|
if (strongSelf) { |
|
|
|
|
strongSelf.responseTrailers = trailers; |
|
|
|
@ -794,112 +792,114 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
[userInfo addEntriesFromDictionary:error.userInfo]; |
|
|
|
|
} |
|
|
|
|
userInfo[kGRPCTrailersKey] = strongSelf.responseTrailers; |
|
|
|
|
// TODO(jcanizales): The C gRPC library doesn't guarantee that the headers block will be |
|
|
|
|
// called before this one, so an error might end up with trailers but no headers. We |
|
|
|
|
// shouldn't call finishWithError until ater both blocks are called. It is also when |
|
|
|
|
// this is done that we can provide a merged view of response headers and trailers in a |
|
|
|
|
// thread-safe way. |
|
|
|
|
if (strongSelf.responseHeaders) { |
|
|
|
|
userInfo[kGRPCHeadersKey] = strongSelf.responseHeaders; |
|
|
|
|
} |
|
|
|
|
// Since gRPC core does not guarantee the headers block being called before this block, |
|
|
|
|
// responseHeaders might be nil. |
|
|
|
|
userInfo[kGRPCHeadersKey] = strongSelf.responseHeaders; |
|
|
|
|
error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; |
|
|
|
|
} |
|
|
|
|
[strongSelf maybeFinishWithError:error]; |
|
|
|
|
[strongSelf finishWithError:error]; |
|
|
|
|
strongSelf->_requestWriter.state = GRXWriterStateFinished; |
|
|
|
|
} |
|
|
|
|
}]; |
|
|
|
|
// Now that the RPC has been initiated, request writes can start. |
|
|
|
|
@synchronized(_requestWriter) { |
|
|
|
|
[_requestWriter startWithWriteable:self]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#pragma mark GRXWriter implementation |
|
|
|
|
|
|
|
|
|
// Lock acquired inside startWithWriteable: |
|
|
|
|
- (void)startCallWithWriteable:(id<GRXWriteable>)writeable { |
|
|
|
|
_responseWriteable = |
|
|
|
|
[[GRXConcurrentWriteable alloc] initWithWriteable:writeable dispatchQueue:_responseQueue]; |
|
|
|
|
|
|
|
|
|
GRPCPooledChannel *channel = |
|
|
|
|
[[GRPCChannelPool sharedInstance] channelWithHost:_host callOptions:_callOptions]; |
|
|
|
|
GRPCWrappedCall *wrappedCall = [channel wrappedCallWithPath:_path |
|
|
|
|
completionQueue:[GRPCCompletionQueue completionQueue] |
|
|
|
|
callOptions:_callOptions]; |
|
|
|
|
|
|
|
|
|
if (wrappedCall == nil) { |
|
|
|
|
[self maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeUnavailable |
|
|
|
|
userInfo:@{ |
|
|
|
|
NSLocalizedDescriptionKey : |
|
|
|
|
@"Failed to create call or channel." |
|
|
|
|
}]]; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@synchronized(self) { |
|
|
|
|
_wrappedCall = wrappedCall; |
|
|
|
|
} |
|
|
|
|
if (_state == GRXWriterStateFinished) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_responseWriteable = |
|
|
|
|
[[GRXConcurrentWriteable alloc] initWithWriteable:writeable dispatchQueue:_responseQueue]; |
|
|
|
|
|
|
|
|
|
GRPCPooledChannel *channel = |
|
|
|
|
[[GRPCChannelPool sharedInstance] channelWithHost:_host callOptions:_callOptions]; |
|
|
|
|
_wrappedCall = [channel wrappedCallWithPath:_path |
|
|
|
|
completionQueue:[GRPCCompletionQueue completionQueue] |
|
|
|
|
callOptions:_callOptions]; |
|
|
|
|
|
|
|
|
|
if (_wrappedCall == nil) { |
|
|
|
|
[self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeUnavailable |
|
|
|
|
userInfo:@{ |
|
|
|
|
NSLocalizedDescriptionKey : |
|
|
|
|
@"Failed to create call or channel." |
|
|
|
|
}]]; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
[self sendHeaders]; |
|
|
|
|
[self invokeCall]; |
|
|
|
|
[self sendHeaders]; |
|
|
|
|
[self invokeCall]; |
|
|
|
|
|
|
|
|
|
// Connectivity monitor is not required for CFStream |
|
|
|
|
char *enableCFStream = getenv(kCFStreamVarName); |
|
|
|
|
if (enableCFStream == nil || enableCFStream[0] != '1') { |
|
|
|
|
[GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChanged:)]; |
|
|
|
|
// Connectivity monitor is not required for CFStream |
|
|
|
|
char *enableCFStream = getenv(kCFStreamVarName); |
|
|
|
|
if (enableCFStream == nil || enableCFStream[0] != '1') { |
|
|
|
|
[GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChanged:)]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Now that the RPC has been initiated, request writes can start. |
|
|
|
|
[_requestWriter startWithWriteable:self]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)startWithWriteable:(id<GRXWriteable>)writeable { |
|
|
|
|
id<GRPCAuthorizationProtocol> tokenProvider = nil; |
|
|
|
|
@synchronized(self) { |
|
|
|
|
_state = GRXWriterStateStarted; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled). |
|
|
|
|
// This makes RPCs in which the call isn't externally retained possible (as long as it is started |
|
|
|
|
// before being autoreleased). |
|
|
|
|
// Care is taken not to retain self strongly in any of the blocks used in this implementation, so |
|
|
|
|
// that the life of the instance is determined by this retain cycle. |
|
|
|
|
_retainSelf = self; |
|
|
|
|
// Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled). |
|
|
|
|
// This makes RPCs in which the call isn't externally retained possible (as long as it is |
|
|
|
|
// started before being autoreleased). Care is taken not to retain self strongly in any of the |
|
|
|
|
// blocks used in this implementation, so that the life of the instance is determined by this |
|
|
|
|
// retain cycle. |
|
|
|
|
_retainSelf = self; |
|
|
|
|
|
|
|
|
|
if (_callOptions == nil) { |
|
|
|
|
GRPCMutableCallOptions *callOptions = [[GRPCHost callOptionsForHost:_host] mutableCopy]; |
|
|
|
|
if (_serverName.length != 0) { |
|
|
|
|
callOptions.serverAuthority = _serverName; |
|
|
|
|
} |
|
|
|
|
if (_timeout > 0) { |
|
|
|
|
callOptions.timeout = _timeout; |
|
|
|
|
} |
|
|
|
|
uint32_t callFlags = [GRPCCall callFlagsForHost:_host path:_path]; |
|
|
|
|
if (callFlags != 0) { |
|
|
|
|
if (callFlags == GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST) { |
|
|
|
|
_callSafety = GRPCCallSafetyIdempotentRequest; |
|
|
|
|
} else if (callFlags == GRPC_INITIAL_METADATA_CACHEABLE_REQUEST) { |
|
|
|
|
_callSafety = GRPCCallSafetyCacheableRequest; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (_callOptions == nil) { |
|
|
|
|
GRPCMutableCallOptions *callOptions = [[GRPCHost callOptionsForHost:_host] mutableCopy]; |
|
|
|
|
if (_serverName.length != 0) { |
|
|
|
|
callOptions.serverAuthority = _serverName; |
|
|
|
|
} |
|
|
|
|
if (_timeout > 0) { |
|
|
|
|
callOptions.timeout = _timeout; |
|
|
|
|
} |
|
|
|
|
uint32_t callFlags = [GRPCCall callFlagsForHost:_host path:_path]; |
|
|
|
|
if (callFlags != 0) { |
|
|
|
|
if (callFlags == GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST) { |
|
|
|
|
_callSafety = GRPCCallSafetyIdempotentRequest; |
|
|
|
|
} else if (callFlags == GRPC_INITIAL_METADATA_CACHEABLE_REQUEST) { |
|
|
|
|
_callSafety = GRPCCallSafetyCacheableRequest; |
|
|
|
|
id<GRPCAuthorizationProtocol> tokenProvider = self.tokenProvider; |
|
|
|
|
if (tokenProvider != nil) { |
|
|
|
|
callOptions.authTokenProvider = tokenProvider; |
|
|
|
|
} |
|
|
|
|
_callOptions = callOptions; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
id<GRPCAuthorizationProtocol> tokenProvider = self.tokenProvider; |
|
|
|
|
if (tokenProvider != nil) { |
|
|
|
|
callOptions.authTokenProvider = tokenProvider; |
|
|
|
|
} |
|
|
|
|
_callOptions = callOptions; |
|
|
|
|
NSAssert(_callOptions.authTokenProvider == nil || _callOptions.oauth2AccessToken == nil, |
|
|
|
|
@"authTokenProvider and oauth2AccessToken cannot be set at the same time"); |
|
|
|
|
|
|
|
|
|
tokenProvider = _callOptions.authTokenProvider; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
NSAssert(_callOptions.authTokenProvider == nil || _callOptions.oauth2AccessToken == nil, |
|
|
|
|
@"authTokenProvider and oauth2AccessToken cannot be set at the same time"); |
|
|
|
|
if (_callOptions.authTokenProvider != nil) { |
|
|
|
|
@synchronized(self) { |
|
|
|
|
self.isWaitingForToken = YES; |
|
|
|
|
} |
|
|
|
|
[_callOptions.authTokenProvider getTokenWithHandler:^(NSString *token) { |
|
|
|
|
@synchronized(self) { |
|
|
|
|
if (self.isWaitingForToken) { |
|
|
|
|
if (token) { |
|
|
|
|
self->_fetchedOauth2AccessToken = [token copy]; |
|
|
|
|
if (tokenProvider != nil) { |
|
|
|
|
__weak typeof(self) weakSelf = self; |
|
|
|
|
[tokenProvider getTokenWithHandler:^(NSString *token) { |
|
|
|
|
__strong typeof(self) strongSelf = weakSelf; |
|
|
|
|
if (strongSelf) { |
|
|
|
|
@synchronized(strongSelf) { |
|
|
|
|
if (strongSelf->_state == GRXWriterStateNotStarted) { |
|
|
|
|
if (token) { |
|
|
|
|
strongSelf->_fetchedOauth2AccessToken = [token copy]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
[self startCallWithWriteable:writeable]; |
|
|
|
|
self.isWaitingForToken = NO; |
|
|
|
|
} |
|
|
|
|
[strongSelf startCallWithWriteable:writeable]; |
|
|
|
|
} |
|
|
|
|
}]; |
|
|
|
|
} else { |
|
|
|
@ -938,16 +938,21 @@ const char *kCFStreamVarName = "grpc_cfstream"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)connectivityChanged:(NSNotification *)note { |
|
|
|
|
// Cancel underlying call upon this notification |
|
|
|
|
// Cancel underlying call upon this notification. |
|
|
|
|
|
|
|
|
|
// Retain because connectivity manager only keeps weak reference to GRPCCall. |
|
|
|
|
__strong GRPCCall *strongSelf = self; |
|
|
|
|
if (strongSelf) { |
|
|
|
|
[self cancelCall]; |
|
|
|
|
[self |
|
|
|
|
maybeFinishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeUnavailable |
|
|
|
|
userInfo:@{ |
|
|
|
|
NSLocalizedDescriptionKey : @"Connectivity lost." |
|
|
|
|
}]]; |
|
|
|
|
@synchronized(strongSelf) { |
|
|
|
|
[_wrappedCall cancel]; |
|
|
|
|
[strongSelf |
|
|
|
|
finishWithError:[NSError errorWithDomain:kGRPCErrorDomain |
|
|
|
|
code:GRPCErrorCodeUnavailable |
|
|
|
|
userInfo:@{ |
|
|
|
|
NSLocalizedDescriptionKey : @"Connectivity lost." |
|
|
|
|
}]]; |
|
|
|
|
} |
|
|
|
|
strongSelf->_requestWriter.state = GRXWriterStateFinished; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|