| /* |
| * |
| * Copyright 2015 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| #import <Foundation/Foundation.h> |
| |
| #import "GRPCChannel.h" |
| #import "GRPCChannelFactory.h" |
| #import "GRPCChannelPool.h" |
| #import "GRPCConnectivityMonitor.h" |
| #import "GRPCCronetChannelFactory.h" |
| #import "GRPCInsecureChannelFactory.h" |
| #import "GRPCSecureChannelFactory.h" |
| #import "version.h" |
| |
| #import <GRPCClient/GRPCCall+Cronet.h> |
| #include <grpc/support/log.h> |
| |
| extern const char *kCFStreamVarName; |
| |
| @implementation GRPCChannelConfiguration |
| |
| - (nullable instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions { |
| NSAssert(host.length, @"Host must not be empty."); |
| NSAssert(callOptions, @"callOptions must not be empty."); |
| if ((self = [super init])) { |
| _host = [host copy]; |
| _callOptions = [callOptions copy]; |
| } |
| return self; |
| } |
| |
| - (id<GRPCChannelFactory>)channelFactory { |
| NSError *error; |
| id<GRPCChannelFactory> factory; |
| GRPCTransportType type = _callOptions.transportType; |
| switch (type) { |
| case GRPCTransportTypeChttp2BoringSSL: |
| // TODO (mxyan): Remove when the API is deprecated |
| #ifdef GRPC_COMPILE_WITH_CRONET |
| if (![GRPCCall isUsingCronet]) { |
| #endif |
| factory = [GRPCSecureChannelFactory |
| factoryWithPEMRootCertificates:_callOptions.PEMRootCertificates |
| privateKey:_callOptions.PEMPrivateKey |
| certChain:_callOptions.PEMCertChain |
| error:&error]; |
| if (factory == nil) { |
| NSLog(@"Error creating secure channel factory: %@", error); |
| } |
| return factory; |
| #ifdef GRPC_COMPILE_WITH_CRONET |
| } |
| #endif |
| // fallthrough |
| case GRPCTransportTypeCronet: |
| return [GRPCCronetChannelFactory sharedInstance]; |
| case GRPCTransportTypeInsecure: |
| return [GRPCInsecureChannelFactory sharedInstance]; |
| } |
| } |
| |
| - (NSDictionary *)channelArgs { |
| NSMutableDictionary *args = [NSMutableDictionary new]; |
| |
| NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING; |
| NSString *userAgentPrefix = _callOptions.userAgentPrefix; |
| if (userAgentPrefix) { |
| args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = |
| [_callOptions.userAgentPrefix stringByAppendingFormat:@" %@", userAgent]; |
| } else { |
| args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent; |
| } |
| |
| NSString *hostNameOverride = _callOptions.hostNameOverride; |
| if (hostNameOverride) { |
| args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = hostNameOverride; |
| } |
| |
| if (_callOptions.responseSizeLimit) { |
| args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] = |
| [NSNumber numberWithUnsignedInteger:_callOptions.responseSizeLimit]; |
| } |
| |
| if (_callOptions.compressionAlgorithm != GRPC_COMPRESS_NONE) { |
| args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] = |
| [NSNumber numberWithInt:_callOptions.compressionAlgorithm]; |
| } |
| |
| if (_callOptions.keepaliveInterval != 0) { |
| args[@GRPC_ARG_KEEPALIVE_TIME_MS] = |
| [NSNumber numberWithUnsignedInteger:(unsigned int)(_callOptions.keepaliveInterval * 1000)]; |
| args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] = |
| [NSNumber numberWithUnsignedInteger:(unsigned int)(_callOptions.keepaliveTimeout * 1000)]; |
| } |
| |
| if (_callOptions.retryEnabled == NO) { |
| args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:_callOptions.retryEnabled]; |
| } |
| |
| if (_callOptions.connectMinTimeout > 0) { |
| args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] = |
| [NSNumber numberWithUnsignedInteger:(unsigned int)(_callOptions.connectMinTimeout * 1000)]; |
| } |
| if (_callOptions.connectInitialBackoff > 0) { |
| args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber |
| numberWithUnsignedInteger:(unsigned int)(_callOptions.connectInitialBackoff * 1000)]; |
| } |
| if (_callOptions.connectMaxBackoff > 0) { |
| args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] = |
| [NSNumber numberWithUnsignedInteger:(unsigned int)(_callOptions.connectMaxBackoff * 1000)]; |
| } |
| |
| if (_callOptions.logContext != nil) { |
| args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = _callOptions.logContext; |
| } |
| |
| if (_callOptions.channelPoolDomain.length != 0) { |
| args[@GRPC_ARG_CHANNEL_POOL_DOMAIN] = _callOptions.channelPoolDomain; |
| } |
| |
| [args addEntriesFromDictionary:_callOptions.additionalChannelArgs]; |
| |
| return args; |
| } |
| |
| - (nonnull id)copyWithZone:(nullable NSZone *)zone { |
| GRPCChannelConfiguration *newConfig = |
| [[GRPCChannelConfiguration alloc] initWithHost:_host callOptions:_callOptions]; |
| |
| return newConfig; |
| } |
| |
| - (BOOL)isEqual:(id)object { |
| NSAssert([object isKindOfClass:[GRPCChannelConfiguration class]], @"Illegal :isEqual"); |
| GRPCChannelConfiguration *obj = (GRPCChannelConfiguration *)object; |
| if (!(obj.host == _host || [obj.host isEqualToString:_host])) return NO; |
| if (!(obj.callOptions == _callOptions || [obj.callOptions isChannelOptionsEqualTo:_callOptions])) |
| return NO; |
| |
| return YES; |
| } |
| |
| - (NSUInteger)hash { |
| NSUInteger result = 0; |
| result ^= _host.hash; |
| result ^= _callOptions.channelOptionsHash; |
| |
| return result; |
| } |
| |
| @end |
| |
| #pragma mark GRPCChannelPool |
| |
| @implementation GRPCChannelPool { |
| NSMutableDictionary<GRPCChannelConfiguration *, GRPCChannel *> *_channelPool; |
| } |
| |
| - (instancetype)init { |
| if ((self = [super init])) { |
| _channelPool = [NSMutableDictionary dictionary]; |
| |
| // Connectivity monitor is not required for CFStream |
| char *enableCFStream = getenv(kCFStreamVarName); |
| if (enableCFStream == nil || enableCFStream[0] != '1') { |
| [GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChange:)]; |
| } |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| [GRPCConnectivityMonitor unregisterObserver:self]; |
| } |
| |
| - (GRPCChannel *)channelWithConfiguration:(GRPCChannelConfiguration *)configuration { |
| __block GRPCChannel *channel; |
| @synchronized(self) { |
| if ([_channelPool objectForKey:configuration]) { |
| channel = _channelPool[configuration]; |
| [channel ref]; |
| } else { |
| channel = [GRPCChannel createChannelWithConfiguration:configuration]; |
| if (channel != nil) { |
| _channelPool[configuration] = channel; |
| } |
| } |
| } |
| return channel; |
| } |
| |
| - (void)removeChannel:(GRPCChannel *)channel { |
| @synchronized(self) { |
| [_channelPool |
| enumerateKeysAndObjectsUsingBlock:^(GRPCChannelConfiguration *_Nonnull key, |
| GRPCChannel *_Nonnull obj, BOOL *_Nonnull stop) { |
| if (obj == channel) { |
| [self->_channelPool removeObjectForKey:key]; |
| } |
| }]; |
| } |
| } |
| |
| - (void)removeAllChannels { |
| @synchronized(self) { |
| _channelPool = [NSMutableDictionary dictionary]; |
| } |
| } |
| |
| - (void)removeAndCloseAllChannels { |
| @synchronized(self) { |
| [_channelPool |
| enumerateKeysAndObjectsUsingBlock:^(GRPCChannelConfiguration *_Nonnull key, |
| GRPCChannel *_Nonnull obj, BOOL *_Nonnull stop) { |
| [obj disconnect]; |
| }]; |
| _channelPool = [NSMutableDictionary dictionary]; |
| } |
| } |
| |
| - (void)connectivityChange:(NSNotification *)note { |
| [self removeAndCloseAllChannels]; |
| } |
| |
| @end |