aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/objective-c/GRPCClient
diff options
context:
space:
mode:
Diffstat (limited to 'src/objective-c/GRPCClient')
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+ChannelArg.h34
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+ChannelArg.m5
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h10
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+Cronet.h13
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+OAuth2.h29
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+Tests.h25
-rw-r--r--src/objective-c/GRPCClient/GRPCCall+Tests.m4
-rw-r--r--src/objective-c/GRPCClient/GRPCCall.h174
-rw-r--r--src/objective-c/GRPCClient/GRPCCall.m470
-rw-r--r--src/objective-c/GRPCClient/GRPCCallOptions.h348
-rw-r--r--src/objective-c/GRPCClient/GRPCCallOptions.m525
-rw-r--r--src/objective-c/GRPCClient/internal/GRPCCallOptions+Internal.h39
-rw-r--r--src/objective-c/GRPCClient/private/ChannelArgsUtil.h38
-rw-r--r--src/objective-c/GRPCClient/private/ChannelArgsUtil.m94
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannel.h71
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannel.m339
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannelFactory.h34
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannelPool+Test.h51
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannelPool.h101
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannelPool.m276
-rw-r--r--src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m4
-rw-r--r--src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h36
-rw-r--r--src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m79
-rw-r--r--src/objective-c/GRPCClient/private/GRPCHost.h29
-rw-r--r--src/objective-c/GRPCClient/private/GRPCHost.m288
-rw-r--r--src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h35
-rw-r--r--src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m43
-rw-r--r--src/objective-c/GRPCClient/private/GRPCRequestHeaders.m4
-rw-r--r--src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h38
-rw-r--r--src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m135
-rw-r--r--src/objective-c/GRPCClient/private/GRPCWrappedCall.h14
-rw-r--r--src/objective-c/GRPCClient/private/GRPCWrappedCall.m122
-rw-r--r--src/objective-c/GRPCClient/private/version.h2
33 files changed, 2853 insertions, 656 deletions
diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
index 803f19dedf..2ddd53a5c6 100644
--- a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
+++ b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
@@ -19,52 +19,20 @@
#include <AvailabilityMacros.h>
-typedef NS_ENUM(NSInteger, GRPCCompressAlgorithm) {
- GRPCCompressNone,
- GRPCCompressDeflate,
- GRPCCompressGzip,
-};
-
-/**
- * Methods to configure GRPC channel options.
- */
+// Deprecated interface. Please use GRPCCallOptions instead.
@interface GRPCCall (ChannelArg)
-/**
- * Use the provided @c userAgentPrefix at the beginning of the HTTP User Agent string for all calls
- * to the specified @c host.
- */
+ (void)setUserAgentPrefix:(nonnull NSString *)userAgentPrefix forHost:(nonnull NSString *)host;
-
-/** The default response size limit is 4MB. Set this to override that default. */
+ (void)setResponseSizeLimit:(NSUInteger)limit forHost:(nonnull NSString *)host;
-
+ (void)closeOpenConnections DEPRECATED_MSG_ATTRIBUTE(
"The API for this feature is experimental, "
"and might be removed or modified at any "
"time.");
-
+ (void)setDefaultCompressMethod:(GRPCCompressAlgorithm)algorithm forhost:(nonnull NSString *)host;
-
-/** Enable keepalive and configure keepalive parameters. A user should call this function once to
- * enable keepalive for a particular host. gRPC client sends a ping after every \a interval ms to
- * check if the transport is still alive. After waiting for \a timeout ms, if the client does not
- * receive the ping ack, it closes the transport; all pending calls to this host will fail with
- * error GRPC_STATUS_INTERNAL with error information "keepalive watchdog timeout". */
+ (void)setKeepaliveWithInterval:(int)interval
timeout:(int)timeout
forHost:(nonnull NSString *)host;
-
-/** Enable/Disable automatic retry of gRPC calls on the channel. If automatic retry is enabled, the
- * retry is controlled by server's service config. If automatic retry is disabled, failed calls are
- * immediately returned to the application layer. */
+ (void)enableRetry:(BOOL)enabled forHost:(nonnull NSString *)host;
-
-/** Set channel connection timeout and backoff parameters. All parameters are positive integers in
- * milliseconds. Set a parameter to 0 to make gRPC use default value for that parameter.
- *
- * Refer to gRPC's doc at https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md for the
- * details of each parameter. */
+ (void)setMinConnectTimeout:(unsigned int)timeout
initialBackoff:(unsigned int)initialBackoff
maxBackoff:(unsigned int)maxBackoff
diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
index 0e631fb3ad..ae60d6208e 100644
--- a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
+++ b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
@@ -18,6 +18,7 @@
#import "GRPCCall+ChannelArg.h"
+#import "private/GRPCChannelPool.h"
#import "private/GRPCHost.h"
#import <grpc/impl/codegen/compression_types.h>
@@ -31,11 +32,11 @@
+ (void)setResponseSizeLimit:(NSUInteger)limit forHost:(nonnull NSString *)host {
GRPCHost *hostConfig = [GRPCHost hostWithAddress:host];
- hostConfig.responseSizeLimitOverride = @(limit);
+ hostConfig.responseSizeLimitOverride = limit;
}
+ (void)closeOpenConnections {
- [GRPCHost flushChannelCache];
+ [[GRPCChannelPool sharedInstance] disconnectAllChannels];
}
+ (void)setDefaultCompressMethod:(GRPCCompressAlgorithm)algorithm forhost:(nonnull NSString *)host {
diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h b/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h
index d7d15c4ee3..7d6f79b765 100644
--- a/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h
+++ b/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h
@@ -18,20 +18,12 @@
#import "GRPCCall.h"
-/** Helpers for setting TLS Trusted Roots, Client Certificates, and Private Key */
+// Deprecated interface. Please use GRPCCallOptions instead.
@interface GRPCCall (ChannelCredentials)
-/**
- * Use the provided @c pemRootCert as the set of trusted root Certificate Authorities for @c host.
- */
+ (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCert
forHost:(nonnull NSString *)host
error:(NSError *_Nullable *_Nullable)errorPtr;
-/**
- * Configures @c host with TLS/SSL Client Credentials and optionally trusted root Certificate
- * Authorities. If @c pemRootCerts is nil, the default CA Certificates bundled with gRPC will be
- * used.
- */
+ (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCerts
withPrivateKey:(nullable NSString *)pemPrivateKey
withCertChain:(nullable NSString *)pemCertChain
diff --git a/src/objective-c/GRPCClient/GRPCCall+Cronet.h b/src/objective-c/GRPCClient/GRPCCall+Cronet.h
index 2a5f6e9cf0..3059c6f186 100644
--- a/src/objective-c/GRPCClient/GRPCCall+Cronet.h
+++ b/src/objective-c/GRPCClient/GRPCCall+Cronet.h
@@ -20,22 +20,11 @@
#import "GRPCCall.h"
-/**
- * Methods for using cronet transport.
- */
+// Deprecated interface. Please use GRPCCallOptions instead.
@interface GRPCCall (Cronet)
-/**
- * This method should be called before issuing the first RPC. It should be
- * called only once. Create an instance of Cronet engine in your app elsewhere
- * and pass the instance pointer in the stream_engine parameter. Once set,
- * all subsequent RPCs will use Cronet transport. The method is not thread
- * safe.
- */
+ (void)useCronetWithEngine:(stream_engine*)engine;
-
+ (stream_engine*)cronetEngine;
-
+ (BOOL)isUsingCronet;
@end
diff --git a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h
index adb1042aa0..60cdc50bfd 100644
--- a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h
+++ b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h
@@ -18,34 +18,13 @@
#import "GRPCCall.h"
-/**
- * The protocol of an OAuth2 token object from which GRPCCall can acquire a token.
- */
-@protocol GRPCAuthorizationProtocol
-- (void)getTokenWithHandler:(void (^)(NSString *token))hander;
-@end
+#import "GRPCCallOptions.h"
-/** Helpers for setting and reading headers compatible with OAuth2. */
+// Deprecated interface. Please use GRPCCallOptions instead.
@interface GRPCCall (OAuth2)
-/**
- * Setting this property is equivalent to setting "Bearer <passed token>" as the value of the
- * request header with key "authorization" (the authorization header). Setting it to nil removes the
- * authorization header from the request.
- * The value obtained by getting the property is the OAuth2 bearer token if the authorization header
- * of the request has the form "Bearer <token>", or nil otherwise.
- */
-@property(atomic, copy) NSString *oauth2AccessToken;
-
-/** Returns the value (if any) of the "www-authenticate" response header (the challenge header). */
-@property(atomic, readonly) NSString *oauth2ChallengeHeader;
-
-/**
- * The authorization token object to be used when starting the call. If the value is set to nil, no
- * oauth authentication will be used.
- *
- * If tokenProvider exists, it takes precedence over the token set by oauth2AccessToken.
- */
+@property(atomic, copy) NSString* oauth2AccessToken;
+@property(atomic, copy, readonly) NSString* oauth2ChallengeHeader;
@property(atomic, strong) id<GRPCAuthorizationProtocol> tokenProvider;
@end
diff --git a/src/objective-c/GRPCClient/GRPCCall+Tests.h b/src/objective-c/GRPCClient/GRPCCall+Tests.h
index 5d35182ae5..edaa5ed582 100644
--- a/src/objective-c/GRPCClient/GRPCCall+Tests.h
+++ b/src/objective-c/GRPCClient/GRPCCall+Tests.h
@@ -18,34 +18,13 @@
#import "GRPCCall.h"
-/**
- * Methods to let tune down the security of gRPC connections for specific hosts. These shouldn't be
- * used in releases, but are sometimes needed for testing.
- */
+// Deprecated interface. Please use GRPCCallOptions instead.
@interface GRPCCall (Tests)
-/**
- * Establish all SSL connections to the provided host using the passed SSL target name and the root
- * certificates found in the file at |certsPath|.
- *
- * Must be called before any gRPC call to that host is made. It's illegal to pass the same host to
- * more than one invocation of the methods of this category.
- */
+ (void)useTestCertsPath:(NSString *)certsPath
testName:(NSString *)testName
forHost:(NSString *)host;
-
-/**
- * Establish all connections to the provided host using cleartext instead of SSL.
- *
- * Must be called before any gRPC call to that host is made. It's illegal to pass the same host to
- * more than one invocation of the methods of this category.
- */
+ (void)useInsecureConnectionsForHost:(NSString *)host;
-
-/**
- * Resets all host configurations to their default values, and flushes all connections from the
- * cache.
- */
+ (void)resetHostSettings;
+
@end
diff --git a/src/objective-c/GRPCClient/GRPCCall+Tests.m b/src/objective-c/GRPCClient/GRPCCall+Tests.m
index 0db3ad6b39..ac3b6a658f 100644
--- a/src/objective-c/GRPCClient/GRPCCall+Tests.m
+++ b/src/objective-c/GRPCClient/GRPCCall+Tests.m
@@ -20,6 +20,8 @@
#import "private/GRPCHost.h"
+#import "GRPCCallOptions.h"
+
@implementation GRPCCall (Tests)
+ (void)useTestCertsPath:(NSString *)certsPath
@@ -42,7 +44,7 @@
+ (void)useInsecureConnectionsForHost:(NSString *)host {
GRPCHost *hostConfig = [GRPCHost hostWithAddress:host];
- hostConfig.secure = NO;
+ hostConfig.transportType = GRPCTransportTypeInsecure;
}
+ (void)resetHostSettings {
diff --git a/src/objective-c/GRPCClient/GRPCCall.h b/src/objective-c/GRPCClient/GRPCCall.h
index ddc6ae054d..6669067fbf 100644
--- a/src/objective-c/GRPCClient/GRPCCall.h
+++ b/src/objective-c/GRPCClient/GRPCCall.h
@@ -37,6 +37,10 @@
#include <AvailabilityMacros.h>
+#include "GRPCCallOptions.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
#pragma mark gRPC errors
/** Domain of NSError objects produced by gRPC. */
@@ -140,42 +144,148 @@ typedef NS_ENUM(NSUInteger, GRPCErrorCode) {
};
/**
- * Safety remark of a gRPC method as defined in RFC 2616 Section 9.1
+ * Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by
+ * the server.
*/
-typedef NS_ENUM(NSUInteger, GRPCCallSafety) {
- /** Signal that there is no guarantees on how the call affects the server state. */
- GRPCCallSafetyDefault = 0,
- /** Signal that the call is idempotent. gRPC is free to use PUT verb. */
- GRPCCallSafetyIdempotentRequest = 1,
- /** Signal that the call is cacheable and will not affect server state. gRPC is free to use GET
- verb. */
- GRPCCallSafetyCacheableRequest = 2,
-};
+extern NSString *const kGRPCHeadersKey;
+extern NSString *const kGRPCTrailersKey;
+
+/** An object can implement this protocol to receive responses from server from a call. */
+@protocol GRPCResponseHandler<NSObject>
+
+@required
/**
- * Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by
- * the server.
+ * All the responses must be issued to a user-provided dispatch queue. This property specifies the
+ * dispatch queue to be used for issuing the notifications.
+ */
+@property(atomic, readonly) dispatch_queue_t dispatchQueue;
+
+@optional
+
+/**
+ * Issued when initial metadata is received from the server.
+ */
+- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata;
+
+/**
+ * Issued when a message is received from the server. The message is the raw data received from the
+ * server, with decompression and without proto deserialization.
*/
-extern id const kGRPCHeadersKey;
-extern id const kGRPCTrailersKey;
+- (void)didReceiveRawMessage:(nullable NSData *)message;
+
+/**
+ * Issued when a call finished. If the call finished successfully, \a error is nil and \a
+ * trainingMetadata consists any trailing metadata received from the server. Otherwise, \a error
+ * is non-nil and contains the corresponding error information, including gRPC error codes and
+ * error descriptions.
+ */
+- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata
+ error:(nullable NSError *)error;
+
+@end
+
+/**
+ * Call related parameters. These parameters are automatically specified by Protobuf. If directly
+ * using the \a GRPCCall2 class, users should specify these parameters manually.
+ */
+@interface GRPCRequestOptions : NSObject<NSCopying>
+
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype) new NS_UNAVAILABLE;
+
+/** Initialize with all properties. */
+- (instancetype)initWithHost:(NSString *)host
+ path:(NSString *)path
+ safety:(GRPCCallSafety)safety NS_DESIGNATED_INITIALIZER;
+
+/** The host serving the RPC service. */
+@property(copy, readonly) NSString *host;
+/** The path to the RPC call. */
+@property(copy, readonly) NSString *path;
+/**
+ * Specify whether the call is idempotent or cachable. gRPC may select different HTTP verbs for the
+ * call based on this information. The default verb used by gRPC is POST.
+ */
+@property(readonly) GRPCCallSafety safety;
+
+@end
#pragma mark GRPCCall
-/** Represents a single gRPC remote call. */
-@interface GRPCCall : GRXWriter
+/**
+ * A \a GRPCCall2 object represents an RPC call.
+ */
+@interface GRPCCall2 : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype) new NS_UNAVAILABLE;
/**
- * The authority for the RPC. If nil, the default authority will be used. This property must be nil
- * when Cronet transport is enabled.
+ * Designated initializer for a call.
+ * \param requestOptions Protobuf generated parameters for the call.
+ * \param responseHandler The object to which responses should be issued.
+ * \param callOptions Options for the call.
*/
-@property(atomic, copy, readwrite) NSString *serverName;
+- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
+ responseHandler:(id<GRPCResponseHandler>)responseHandler
+ callOptions:(nullable GRPCCallOptions *)callOptions
+ NS_DESIGNATED_INITIALIZER;
+/**
+ * Convenience initializer for a call that uses default call options (see GRPCCallOptions.m for
+ * the default options).
+ */
+- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
+ responseHandler:(id<GRPCResponseHandler>)responseHandler;
/**
- * The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to
- * positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed
- * within \a timeout seconds. A negative value is not allowed.
+ * Starts the call. This function must only be called once for each instance.
*/
-@property NSTimeInterval timeout;
+- (void)start;
+
+/**
+ * Cancel the request of this call at best effort. It attempts to notify the server that the RPC
+ * should be cancelled, and issue didCloseWithTrailingMetadata:error: callback with error code
+ * CANCELED if no other error code has already been issued.
+ */
+- (void)cancel;
+
+/**
+ * Send a message to the server. Data are sent as raw bytes in gRPC message frames.
+ */
+- (void)writeData:(NSData *)data;
+
+/**
+ * Finish the RPC request and half-close the call. The server may still send messages and/or
+ * trailers to the client. The method must only be called once and after start is called.
+ */
+- (void)finish;
+
+/**
+ * Get a copy of the original call options.
+ */
+@property(readonly, copy) GRPCCallOptions *callOptions;
+
+/** Get a copy of the original request options. */
+@property(readonly, copy) GRPCRequestOptions *requestOptions;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnullability-completeness"
+
+/**
+ * This interface is deprecated. Please use \a GRPCcall2.
+ *
+ * Represents a single gRPC remote call.
+ */
+@interface GRPCCall : GRXWriter
+
+- (instancetype)init NS_UNAVAILABLE;
/**
* The container of the request headers of an RPC conforms to this protocol, which is a subset of
@@ -236,7 +346,7 @@ extern id const kGRPCTrailersKey;
*/
- (instancetype)initWithHost:(NSString *)host
path:(NSString *)path
- requestsWriter:(GRXWriter *)requestsWriter NS_DESIGNATED_INITIALIZER;
+ requestsWriter:(GRXWriter *)requestWriter;
/**
* Finishes the request side of this call, notifies the server that the RPC should be cancelled, and
@@ -245,22 +355,13 @@ extern id const kGRPCTrailersKey;
- (void)cancel;
/**
- * Set the call flag for a specific host path.
- *
- * Host parameter should not contain the scheme (http:// or https://), only the name or IP addr
- * and the port number, for example @"localhost:5050".
+ * The following methods are deprecated.
*/
+ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path;
-
-/**
- * Set the dispatch queue to be used for callbacks. Current implementation requires \a queue to be a
- * serial queue.
- *
- * This configuration is only effective before the call starts.
- */
+@property(atomic, copy, readwrite) NSString *serverName;
+@property NSTimeInterval timeout;
- (void)setResponseDispatchQueue:(dispatch_queue_t)queue;
-// TODO(jcanizales): Let specify a deadline. As a category of GRXWriter?
@end
#pragma mark Backwards compatibiity
@@ -283,3 +384,4 @@ DEPRECATED_MSG_ATTRIBUTE("Use NSDictionary or NSMutableDictionary instead.")
@interface NSMutableDictionary (GRPCRequestHeaders)<GRPCRequestHeaders>
@end
#pragma clang diagnostic pop
+#pragma clang diagnostic pop
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m
index 084fbdeb49..83c6edc6e3 100644
--- a/src/objective-c/GRPCClient/GRPCCall.m
+++ b/src/objective-c/GRPCClient/GRPCCall.m
@@ -20,11 +20,16 @@
#import "GRPCCall+OAuth2.h"
+#import <RxLibrary/GRXBufferedPipe.h>
#import <RxLibrary/GRXConcurrentWriteable.h>
#import <RxLibrary/GRXImmediateSingleWriter.h>
+#import <RxLibrary/GRXWriter+Immediate.h>
#include <grpc/grpc.h>
#include <grpc/support/time.h>
+#import "GRPCCallOptions.h"
+#import "private/GRPCChannelPool.h"
+#import "private/GRPCCompletionQueue.h"
#import "private/GRPCConnectivityMonitor.h"
#import "private/GRPCHost.h"
#import "private/GRPCRequestHeaders.h"
@@ -52,6 +57,313 @@ const char *kCFStreamVarName = "grpc_cfstream";
@property(atomic, strong) NSDictionary *responseHeaders;
@property(atomic, strong) NSDictionary *responseTrailers;
@property(atomic) BOOL isWaitingForToken;
+
+- (instancetype)initWithHost:(NSString *)host
+ path:(NSString *)path
+ callSafety:(GRPCCallSafety)safety
+ requestsWriter:(GRXWriter *)requestsWriter
+ callOptions:(GRPCCallOptions *)callOptions;
+
+@end
+
+@implementation GRPCRequestOptions
+
+- (instancetype)initWithHost:(NSString *)host path:(NSString *)path safety:(GRPCCallSafety)safety {
+ NSAssert(host.length != 0 && path.length != 0, @"host and path cannot be empty");
+ if (host.length == 0 || path.length == 0) {
+ return nil;
+ }
+ if ((self = [super init])) {
+ _host = [host copy];
+ _path = [path copy];
+ _safety = safety;
+ }
+ return self;
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+ GRPCRequestOptions *request =
+ [[GRPCRequestOptions alloc] initWithHost:_host path:_path safety:_safety];
+
+ return request;
+}
+
+@end
+
+@implementation GRPCCall2 {
+ /** Options for the call. */
+ GRPCCallOptions *_callOptions;
+ /** The handler of responses. */
+ id<GRPCResponseHandler> _handler;
+
+ // Thread safety of ivars below are protected by _dispatchQueue.
+
+ /**
+ * Make use of legacy GRPCCall to make calls. Nullified when call is finished.
+ */
+ GRPCCall *_call;
+ /** Flags whether initial metadata has been published to response handler. */
+ BOOL _initialMetadataPublished;
+ /** Streaming call writeable to the underlying call. */
+ GRXBufferedPipe *_pipe;
+ /** Serial dispatch queue for tasks inside the call. */
+ dispatch_queue_t _dispatchQueue;
+ /** Flags whether call has started. */
+ BOOL _started;
+ /** Flags whether call has been canceled. */
+ BOOL _canceled;
+ /** Flags whether call has been finished. */
+ BOOL _finished;
+}
+
+- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
+ responseHandler:(id<GRPCResponseHandler>)responseHandler
+ callOptions:(GRPCCallOptions *)callOptions {
+ NSAssert(requestOptions.host.length != 0 && requestOptions.path.length != 0,
+ @"Neither host nor path can be nil.");
+ NSAssert(requestOptions.safety <= GRPCCallSafetyCacheableRequest, @"Invalid call safety value.");
+ NSAssert(responseHandler != nil, @"Response handler required.");
+ if (requestOptions.host.length == 0 || requestOptions.path.length == 0) {
+ return nil;
+ }
+ if (requestOptions.safety > GRPCCallSafetyCacheableRequest) {
+ return nil;
+ }
+ if (responseHandler == nil) {
+ return nil;
+ }
+
+ if ((self = [super init])) {
+ _requestOptions = [requestOptions copy];
+ if (callOptions == nil) {
+ _callOptions = [[GRPCCallOptions alloc] init];
+ } else {
+ _callOptions = [callOptions copy];
+ }
+ _handler = responseHandler;
+ _initialMetadataPublished = NO;
+ _pipe = [GRXBufferedPipe pipe];
+ // Set queue QoS only when iOS version is 8.0 or above and Xcode version is 9.0 or above
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
+ if (@available(iOS 8.0, macOS 10.10, *)) {
+ _dispatchQueue = dispatch_queue_create(
+ NULL,
+ dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
+ } else {
+#else
+ {
+#endif
+ _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+ }
+ dispatch_set_target_queue(_dispatchQueue, responseHandler.dispatchQueue);
+ _started = NO;
+ _canceled = NO;
+ _finished = NO;
+ }
+
+ return self;
+}
+
+- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
+ responseHandler:(id<GRPCResponseHandler>)responseHandler {
+ return
+ [self initWithRequestOptions:requestOptions responseHandler:responseHandler callOptions:nil];
+}
+
+- (void)start {
+ GRPCCall *copiedCall = nil;
+ @synchronized(self) {
+ NSAssert(!_started, @"Call already started.");
+ NSAssert(!_canceled, @"Call already canceled.");
+ if (_started) {
+ return;
+ }
+ if (_canceled) {
+ return;
+ }
+
+ _started = YES;
+ if (!_callOptions) {
+ _callOptions = [[GRPCCallOptions alloc] init];
+ }
+
+ _call = [[GRPCCall alloc] initWithHost:_requestOptions.host
+ path:_requestOptions.path
+ callSafety:_requestOptions.safety
+ requestsWriter:_pipe
+ callOptions:_callOptions];
+ if (_callOptions.initialMetadata) {
+ [_call.requestHeaders addEntriesFromDictionary:_callOptions.initialMetadata];
+ }
+ copiedCall = _call;
+ }
+
+ void (^valueHandler)(id value) = ^(id value) {
+ @synchronized(self) {
+ if (self->_handler) {
+ if (!self->_initialMetadataPublished) {
+ self->_initialMetadataPublished = YES;
+ [self issueInitialMetadata:self->_call.responseHeaders];
+ }
+ if (value) {
+ [self issueMessage:value];
+ }
+ }
+ }
+ };
+ void (^completionHandler)(NSError *errorOrNil) = ^(NSError *errorOrNil) {
+ @synchronized(self) {
+ if (self->_handler) {
+ if (!self->_initialMetadataPublished) {
+ self->_initialMetadataPublished = YES;
+ [self issueInitialMetadata:self->_call.responseHeaders];
+ }
+ [self issueClosedWithTrailingMetadata:self->_call.responseTrailers error:errorOrNil];
+ }
+ // Clearing _call must happen *after* dispatching close in order to get trailing
+ // metadata from _call.
+ if (self->_call) {
+ // Clean up the request writers. This should have no effect to _call since its
+ // response writeable is already nullified.
+ [self->_pipe writesFinishedWithError:nil];
+ self->_call = nil;
+ self->_pipe = nil;
+ }
+ }
+ };
+ id<GRXWriteable> responseWriteable =
+ [[GRXWriteable alloc] initWithValueHandler:valueHandler completionHandler:completionHandler];
+ [copiedCall startWithWriteable:responseWriteable];
+}
+
+- (void)cancel {
+ GRPCCall *copiedCall = nil;
+ @synchronized(self) {
+ if (_canceled) {
+ return;
+ }
+
+ _canceled = YES;
+
+ copiedCall = _call;
+ _call = nil;
+ _pipe = nil;
+
+ if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
+ dispatch_async(_dispatchQueue, ^{
+ // Copy to local so that block is freed after cancellation completes.
+ id<GRPCResponseHandler> copiedHandler = nil;
+ @synchronized(self) {
+ copiedHandler = self->_handler;
+ self->_handler = nil;
+ }
+
+ [copiedHandler didCloseWithTrailingMetadata:nil
+ error:[NSError errorWithDomain:kGRPCErrorDomain
+ code:GRPCErrorCodeCancelled
+ userInfo:@{
+ NSLocalizedDescriptionKey :
+ @"Canceled by app"
+ }]];
+ });
+ } else {
+ _handler = nil;
+ }
+ }
+ [copiedCall cancel];
+}
+
+- (void)writeData:(NSData *)data {
+ GRXBufferedPipe *copiedPipe = nil;
+ @synchronized(self) {
+ NSAssert(!_canceled, @"Call already canceled.");
+ NSAssert(!_finished, @"Call is half-closed before sending data.");
+ if (_canceled) {
+ return;
+ }
+ if (_finished) {
+ return;
+ }
+
+ if (_pipe) {
+ copiedPipe = _pipe;
+ }
+ }
+ [copiedPipe writeValue:data];
+}
+
+- (void)finish {
+ GRXBufferedPipe *copiedPipe = nil;
+ @synchronized(self) {
+ NSAssert(_started, @"Call not started.");
+ NSAssert(!_canceled, @"Call already canceled.");
+ NSAssert(!_finished, @"Call already half-closed.");
+ if (!_started) {
+ return;
+ }
+ if (_canceled) {
+ return;
+ }
+ if (_finished) {
+ return;
+ }
+
+ if (_pipe) {
+ copiedPipe = _pipe;
+ _pipe = nil;
+ }
+ _finished = YES;
+ }
+ [copiedPipe writesFinishedWithError:nil];
+}
+
+- (void)issueInitialMetadata:(NSDictionary *)initialMetadata {
+ @synchronized(self) {
+ if (initialMetadata != nil &&
+ [_handler respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
+ dispatch_async(_dispatchQueue, ^{
+ id<GRPCResponseHandler> copiedHandler = nil;
+ @synchronized(self) {
+ copiedHandler = self->_handler;
+ }
+ [copiedHandler didReceiveInitialMetadata:initialMetadata];
+ });
+ }
+ }
+}
+
+- (void)issueMessage:(id)message {
+ @synchronized(self) {
+ if (message != nil && [_handler respondsToSelector:@selector(didReceiveRawMessage:)]) {
+ dispatch_async(_dispatchQueue, ^{
+ id<GRPCResponseHandler> copiedHandler = nil;
+ @synchronized(self) {
+ copiedHandler = self->_handler;
+ }
+ [copiedHandler didReceiveRawMessage:message];
+ });
+ }
+ }
+}
+
+- (void)issueClosedWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+ @synchronized(self) {
+ if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
+ dispatch_async(_dispatchQueue, ^{
+ id<GRPCResponseHandler> copiedHandler = nil;
+ @synchronized(self) {
+ copiedHandler = self->_handler;
+ // Clean up _handler so that no more responses are reported to the handler.
+ self->_handler = nil;
+ }
+ [copiedHandler didCloseWithTrailingMetadata:trailingMetadata error:error];
+ });
+ } else {
+ _handler = nil;
+ }
+ }
+}
+
@end
// The following methods of a C gRPC call object aren't reentrant, and thus
@@ -75,6 +387,8 @@ const char *kCFStreamVarName = "grpc_cfstream";
NSString *_host;
NSString *_path;
+ GRPCCallSafety _callSafety;
+ GRPCCallOptions *_callOptions;
GRPCWrappedCall *_wrappedCall;
GRPCConnectivityMonitor *_connectivityMonitor;
@@ -113,6 +427,9 @@ const char *kCFStreamVarName = "grpc_cfstream";
// 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;
}
@synthesize state = _state;
@@ -127,6 +444,9 @@ const char *kCFStreamVarName = "grpc_cfstream";
}
+ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path {
+ if (host.length == 0 || path.length == 0) {
+ return;
+ }
NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
switch (callSafety) {
case GRPCCallSafetyDefault:
@@ -148,24 +468,42 @@ const char *kCFStreamVarName = "grpc_cfstream";
return [callFlags[hostAndPath] intValue];
}
-- (instancetype)init {
- return [self initWithHost:nil path:nil requestsWriter:nil];
-}
-
// Designated initializer
- (instancetype)initWithHost:(NSString *)host
path:(NSString *)path
requestsWriter:(GRXWriter *)requestWriter {
+ return [self initWithHost:host
+ path:path
+ callSafety:GRPCCallSafetyDefault
+ requestsWriter:requestWriter
+ callOptions:nil];
+}
+
+- (instancetype)initWithHost:(NSString *)host
+ path:(NSString *)path
+ callSafety:(GRPCCallSafety)safety
+ requestsWriter:(GRXWriter *)requestWriter
+ callOptions:(GRPCCallOptions *)callOptions {
+ // Purposely using pointer rather than length (host.length == 0) for backwards compatibility.
+ NSAssert(host != nil && path != nil, @"Neither host nor path can be nil.");
+ NSAssert(safety <= GRPCCallSafetyCacheableRequest, @"Invalid call safety value.");
+ NSAssert(requestWriter.state == GRXWriterStateNotStarted,
+ @"The requests writer can't be already started.");
if (!host || !path) {
- [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."];
+ return nil;
+ }
+ if (safety > GRPCCallSafetyCacheableRequest) {
+ return nil;
}
if (requestWriter.state != GRXWriterStateNotStarted) {
- [NSException raise:NSInvalidArgumentException
- format:@"The requests writer can't be already started."];
+ return nil;
}
+
if ((self = [super init])) {
_host = [host copy];
_path = [path copy];
+ _callSafety = safety;
+ _callOptions = [callOptions copy];
// Serial queue to invoke the non-reentrant methods of the grpc_call object.
_callQueue = dispatch_queue_create("io.grpc.call", NULL);
@@ -209,11 +547,7 @@ const char *kCFStreamVarName = "grpc_cfstream";
[_responseWriteable enqueueSuccessfulCompletion];
}
- // Connectivity monitor is not required for CFStream
- char *enableCFStream = getenv(kCFStreamVarName);
- if (enableCFStream == nil || enableCFStream[0] != '1') {
- [GRPCConnectivityMonitor unregisterObserver:self];
- }
+ [GRPCConnectivityMonitor unregisterObserver:self];
// If the call isn't retained anywhere else, it can be deallocated now.
_retainSelf = nil;
@@ -221,13 +555,14 @@ const char *kCFStreamVarName = "grpc_cfstream";
- (void)cancelCall {
// Can be called from any thread, any number of times.
- [_wrappedCall cancel];
+ @synchronized(self) {
+ [_wrappedCall cancel];
+ }
}
- (void)cancel {
- if (!self.isWaitingForToken) {
+ @synchronized(self) {
[self cancelCall];
- } else {
self.isWaitingForToken = NO;
}
[self
@@ -317,11 +652,37 @@ const char *kCFStreamVarName = "grpc_cfstream";
#pragma mark Send headers
-- (void)sendHeaders:(NSDictionary *)headers {
+- (void)sendHeaders {
+ // TODO (mxyan): Remove after deprecated methods are removed
+ uint32_t callSafetyFlags = 0;
+ switch (_callSafety) {
+ case GRPCCallSafetyDefault:
+ callSafetyFlags = 0;
+ break;
+ case GRPCCallSafetyIdempotentRequest:
+ callSafetyFlags = GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST;
+ break;
+ case GRPCCallSafetyCacheableRequest:
+ callSafetyFlags = GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
+ break;
+ }
+
+ NSMutableDictionary *headers = [_requestHeaders mutableCopy];
+ NSString *fetchedOauth2AccessToken;
+ @synchronized(self) {
+ fetchedOauth2AccessToken = _fetchedOauth2AccessToken;
+ }
+ if (fetchedOauth2AccessToken != nil) {
+ headers[@"authorization"] = [kBearerPrefix stringByAppendingString:fetchedOauth2AccessToken];
+ } else if (_callOptions.oauth2AccessToken != nil) {
+ headers[@"authorization"] =
+ [kBearerPrefix stringByAppendingString:_callOptions.oauth2AccessToken];
+ }
+
// TODO(jcanizales): Add error handlers for async failures
GRPCOpSendMetadata *op = [[GRPCOpSendMetadata alloc]
initWithMetadata:headers
- flags:[GRPCCall callFlagsForHost:_host path:_path]
+ flags:callSafetyFlags
handler:nil]; // No clean-up needed after SEND_INITIAL_METADATA
if (!_unaryCall) {
[_wrappedCall startBatchWithOperations:@[ op ]];
@@ -458,13 +819,27 @@ const char *kCFStreamVarName = "grpc_cfstream";
_responseWriteable =
[[GRXConcurrentWriteable alloc] initWithWriteable:writeable dispatchQueue:_responseQueue];
- _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host
- serverName:_serverName
- path:_path
- timeout:_timeout];
- NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?");
+ 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;
+ }
- [self sendHeaders:_requestHeaders];
+ [self sendHeaders];
[self invokeCall];
// Connectivity monitor is not required for CFStream
@@ -486,18 +861,45 @@ const char *kCFStreamVarName = "grpc_cfstream";
// that the life of the instance is determined by this retain cycle.
_retainSelf = self;
- if (self.tokenProvider != nil) {
- self.isWaitingForToken = YES;
- __weak typeof(self) weakSelf = self;
- [self.tokenProvider getTokenWithHandler:^(NSString *token) {
- typeof(self) strongSelf = weakSelf;
- if (strongSelf && strongSelf.isWaitingForToken) {
- if (token) {
- NSString *t = [kBearerPrefix stringByAppendingString:token];
- strongSelf.requestHeaders[kAuthorizationHeader] = t;
+ 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;
+ }
+
+ 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];
+ }
+ [self startCallWithWriteable:writeable];
+ self.isWaitingForToken = NO;
}
- [strongSelf startCallWithWriteable:writeable];
- strongSelf.isWaitingForToken = NO;
}
}];
} else {
diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.h b/src/objective-c/GRPCClient/GRPCCallOptions.h
new file mode 100644
index 0000000000..b5bf4c9eb6
--- /dev/null
+++ b/src/objective-c/GRPCClient/GRPCCallOptions.h
@@ -0,0 +1,348 @@
+/*
+ *
+ * Copyright 2018 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>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Safety remark of a gRPC method as defined in RFC 2616 Section 9.1
+ */
+typedef NS_ENUM(NSUInteger, GRPCCallSafety) {
+ /** Signal that there is no guarantees on how the call affects the server state. */
+ GRPCCallSafetyDefault = 0,
+ /** Signal that the call is idempotent. gRPC is free to use PUT verb. */
+ GRPCCallSafetyIdempotentRequest = 1,
+ /**
+ * Signal that the call is cacheable and will not affect server state. gRPC is free to use GET
+ * verb.
+ */
+ GRPCCallSafetyCacheableRequest = 2,
+};
+
+// Compression algorithm to be used by a gRPC call
+typedef NS_ENUM(NSUInteger, GRPCCompressionAlgorithm) {
+ GRPCCompressNone = 0,
+ GRPCCompressDeflate,
+ GRPCCompressGzip,
+ GRPCStreamCompressGzip,
+};
+
+// GRPCCompressAlgorithm is deprecated; use GRPCCompressionAlgorithm
+typedef GRPCCompressionAlgorithm GRPCCompressAlgorithm;
+
+/** The transport to be used by a gRPC call */
+typedef NS_ENUM(NSUInteger, GRPCTransportType) {
+ GRPCTransportTypeDefault = 0,
+ /** gRPC internal HTTP/2 stack with BoringSSL */
+ GRPCTransportTypeChttp2BoringSSL = 0,
+ /** Cronet stack */
+ GRPCTransportTypeCronet,
+ /** Insecure channel. FOR TEST ONLY! */
+ GRPCTransportTypeInsecure,
+};
+
+/**
+ * Implement this protocol to provide a token to gRPC when a call is initiated.
+ */
+@protocol GRPCAuthorizationProtocol
+
+/**
+ * This method is called when gRPC is about to start the call. When OAuth token is acquired,
+ * \a handler is expected to be called with \a token being the new token to be used for this call.
+ */
+- (void)getTokenWithHandler:(void (^)(NSString *_Nullable token))handler;
+
+@end
+
+@interface GRPCCallOptions : NSObject<NSCopying, NSMutableCopying>
+
+// Call parameters
+/**
+ * The authority for the RPC. If nil, the default authority will be used.
+ *
+ * Note: This property does not have effect on Cronet transport and will be ignored.
+ * Note: This property cannot be used to validate a self-signed server certificate. It control the
+ * :authority header field of the call and performs an extra check that server's certificate
+ * matches the :authority header.
+ */
+@property(copy, readonly, nullable) NSString *serverAuthority;
+
+/**
+ * The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to
+ * positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed
+ * within \a timeout seconds. A negative value is not allowed.
+ */
+@property(readonly) NSTimeInterval timeout;
+
+// OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
+
+/**
+ * The OAuth2 access token string. The string is prefixed with "Bearer " then used as value of the
+ * request's "authorization" header field. This parameter should not be used simultaneously with
+ * \a authTokenProvider.
+ */
+@property(copy, readonly, nullable) NSString *oauth2AccessToken;
+
+/**
+ * The interface to get the OAuth2 access token string. gRPC will attempt to acquire token when
+ * initiating the call. This parameter should not be used simultaneously with \a oauth2AccessToken.
+ */
+@property(readonly, nullable) id<GRPCAuthorizationProtocol> authTokenProvider;
+
+/**
+ * Initial metadata key-value pairs that should be included in the request.
+ */
+@property(copy, readonly, nullable) NSDictionary *initialMetadata;
+
+// Channel parameters; take into account of channel signature.
+
+/**
+ * Custom string that is prefixed to a request's user-agent header field before gRPC's internal
+ * user-agent string.
+ */
+@property(copy, readonly, nullable) NSString *userAgentPrefix;
+
+/**
+ * The size limit for the response received from server. If it is exceeded, an error with status
+ * code GRPCErrorCodeResourceExhausted is returned.
+ */
+@property(readonly) NSUInteger responseSizeLimit;
+
+/**
+ * The compression algorithm to be used by the gRPC call. For more details refer to
+ * https://github.com/grpc/grpc/blob/master/doc/compression.md
+ */
+@property(readonly) GRPCCompressionAlgorithm compressionAlgorithm;
+
+/**
+ * Enable/Disable gRPC call's retry feature. The default is enabled. For details of this feature
+ * refer to
+ * https://github.com/grpc/proposal/blob/master/A6-client-retries.md
+ */
+@property(readonly) BOOL retryEnabled;
+
+// HTTP/2 keep-alive feature. The parameter \a keepaliveInterval specifies the interval between two
+// PING frames. The parameter \a keepaliveTimeout specifies the length of the period for which the
+// call should wait for PING ACK. If PING ACK is not received after this period, the call fails.
+// Negative values are not allowed.
+@property(readonly) NSTimeInterval keepaliveInterval;
+@property(readonly) NSTimeInterval keepaliveTimeout;
+
+// Parameters for connection backoff. Negative values are not allowed.
+// For details of gRPC's backoff behavior, refer to
+// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
+@property(readonly) NSTimeInterval connectMinTimeout;
+@property(readonly) NSTimeInterval connectInitialBackoff;
+@property(readonly) NSTimeInterval connectMaxBackoff;
+
+/**
+ * Specify channel args to be used for this call. For a list of channel args available, see
+ * grpc/grpc_types.h
+ */
+@property(copy, readonly, nullable) NSDictionary *additionalChannelArgs;
+
+// Parameters for SSL authentication.
+
+/**
+ * PEM format root certifications that is trusted. If set to nil, gRPC uses a list of default
+ * root certificates.
+ */
+@property(copy, readonly, nullable) NSString *PEMRootCertificates;
+
+/**
+ * PEM format private key for client authentication, if required by the server.
+ */
+@property(copy, readonly, nullable) NSString *PEMPrivateKey;
+
+/**
+ * PEM format certificate chain for client authentication, if required by the server.
+ */
+@property(copy, readonly, nullable) NSString *PEMCertificateChain;
+
+/**
+ * Select the transport type to be used for this call.
+ */
+@property(readonly) GRPCTransportType transportType;
+
+/**
+ * Override the hostname during the TLS hostname validation process.
+ */
+@property(copy, readonly, nullable) NSString *hostNameOverride;
+
+/**
+ * A string that specify the domain where channel is being cached. Channels with different domains
+ * will not get cached to the same connection.
+ */
+@property(copy, readonly, nullable) NSString *channelPoolDomain;
+
+/**
+ * Channel id allows control of channel caching within a channelPoolDomain. A call with a unique
+ * channelID will create a new channel (connection) instead of reusing an existing one. Multiple
+ * calls in the same channelPoolDomain using identical channelID are allowed to share connection
+ * if other channel options are also the same.
+ */
+@property(readonly) NSUInteger channelID;
+
+/**
+ * Return if the channel options are equal to another object.
+ */
+- (BOOL)hasChannelOptionsEqualTo:(GRPCCallOptions *)callOptions;
+
+/**
+ * Hash for channel options.
+ */
+@property(readonly) NSUInteger channelOptionsHash;
+
+@end
+
+@interface GRPCMutableCallOptions : GRPCCallOptions<NSCopying, NSMutableCopying>
+
+// Call parameters
+/**
+ * The authority for the RPC. If nil, the default authority will be used.
+ *
+ * Note: This property does not have effect on Cronet transport and will be ignored.
+ * Note: This property cannot be used to validate a self-signed server certificate. It control the
+ * :authority header field of the call and performs an extra check that server's certificate
+ * matches the :authority header.
+ */
+@property(copy, readwrite, nullable) NSString *serverAuthority;
+
+/**
+ * The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to
+ * positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed
+ * within \a timeout seconds. Negative value is invalid; setting the parameter to negative value
+ * will reset the parameter to 0.
+ */
+@property(readwrite) NSTimeInterval timeout;
+
+// OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
+
+/**
+ * The OAuth2 access token string. The string is prefixed with "Bearer " then used as value of the
+ * request's "authorization" header field. This parameter should not be used simultaneously with
+ * \a authTokenProvider.
+ */
+@property(copy, readwrite, nullable) NSString *oauth2AccessToken;
+
+/**
+ * The interface to get the OAuth2 access token string. gRPC will attempt to acquire token when
+ * initiating the call. This parameter should not be used simultaneously with \a oauth2AccessToken.
+ */
+@property(readwrite, nullable) id<GRPCAuthorizationProtocol> authTokenProvider;
+
+/**
+ * Initial metadata key-value pairs that should be included in the request.
+ */
+@property(copy, readwrite, nullable) NSDictionary *initialMetadata;
+
+// Channel parameters; take into account of channel signature.
+
+/**
+ * Custom string that is prefixed to a request's user-agent header field before gRPC's internal
+ * user-agent string.
+ */
+@property(copy, readwrite, nullable) NSString *userAgentPrefix;
+
+/**
+ * The size limit for the response received from server. If it is exceeded, an error with status
+ * code GRPCErrorCodeResourceExhausted is returned.
+ */
+@property(readwrite) NSUInteger responseSizeLimit;
+
+/**
+ * The compression algorithm to be used by the gRPC call. For more details refer to
+ * https://github.com/grpc/grpc/blob/master/doc/compression.md
+ */
+@property(readwrite) GRPCCompressionAlgorithm compressionAlgorithm;
+
+/**
+ * Enable/Disable gRPC call's retry feature. The default is enabled. For details of this feature
+ * refer to
+ * https://github.com/grpc/proposal/blob/master/A6-client-retries.md
+ */
+@property(readwrite) BOOL retryEnabled;
+
+// HTTP/2 keep-alive feature. The parameter \a keepaliveInterval specifies the interval between two
+// PING frames. The parameter \a keepaliveTimeout specifies the length of the period for which the
+// call should wait for PING ACK. If PING ACK is not received after this period, the call fails.
+// Negative values are invalid; setting these parameters to negative value will reset the
+// corresponding parameter to 0.
+@property(readwrite) NSTimeInterval keepaliveInterval;
+@property(readwrite) NSTimeInterval keepaliveTimeout;
+
+// Parameters for connection backoff. Negative value is invalid; setting the parameters to negative
+// value will reset corresponding parameter to 0.
+// For details of gRPC's backoff behavior, refer to
+// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
+@property(readwrite) NSTimeInterval connectMinTimeout;
+@property(readwrite) NSTimeInterval connectInitialBackoff;
+@property(readwrite) NSTimeInterval connectMaxBackoff;
+
+/**
+ * Specify channel args to be used for this call. For a list of channel args available, see
+ * grpc/grpc_types.h
+ */
+@property(copy, readwrite, nullable) NSDictionary *additionalChannelArgs;
+
+// Parameters for SSL authentication.
+
+/**
+ * PEM format root certifications that is trusted. If set to nil, gRPC uses a list of default
+ * root certificates.
+ */
+@property(copy, readwrite, nullable) NSString *PEMRootCertificates;
+
+/**
+ * PEM format private key for client authentication, if required by the server.
+ */
+@property(copy, readwrite, nullable) NSString *PEMPrivateKey;
+
+/**
+ * PEM format certificate chain for client authentication, if required by the server.
+ */
+@property(copy, readwrite, nullable) NSString *PEMCertificateChain;
+
+/**
+ * Select the transport type to be used for this call.
+ */
+@property(readwrite) GRPCTransportType transportType;
+
+/**
+ * Override the hostname during the TLS hostname validation process.
+ */
+@property(copy, readwrite, nullable) NSString *hostNameOverride;
+
+/**
+ * A string that specify the domain where channel is being cached. Channels with different domains
+ * will not get cached to the same channel. For example, a gRPC example app may use the channel pool
+ * domain 'io.grpc.example' so that its calls do not reuse the channel created by other modules in
+ * the same process.
+ */
+@property(copy, readwrite, nullable) NSString *channelPoolDomain;
+
+/**
+ * Channel id allows a call to force creating a new channel (connection) rather than using a cached
+ * channel. Calls using distinct channelID's will not get cached to the same channel.
+ */
+@property(readwrite) NSUInteger channelID;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.m b/src/objective-c/GRPCClient/GRPCCallOptions.m
new file mode 100644
index 0000000000..e59a812bd8
--- /dev/null
+++ b/src/objective-c/GRPCClient/GRPCCallOptions.m
@@ -0,0 +1,525 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCCallOptions.h"
+#import "internal/GRPCCallOptions+Internal.h"
+
+// The default values for the call options.
+static NSString *const kDefaultServerAuthority = nil;
+static const NSTimeInterval kDefaultTimeout = 0;
+static NSDictionary *const kDefaultInitialMetadata = nil;
+static NSString *const kDefaultUserAgentPrefix = nil;
+static const NSUInteger kDefaultResponseSizeLimit = 0;
+static const GRPCCompressionAlgorithm kDefaultCompressionAlgorithm = GRPCCompressNone;
+static const BOOL kDefaultRetryEnabled = YES;
+static const NSTimeInterval kDefaultKeepaliveInterval = 0;
+static const NSTimeInterval kDefaultKeepaliveTimeout = 0;
+static const NSTimeInterval kDefaultConnectMinTimeout = 0;
+static const NSTimeInterval kDefaultConnectInitialBackoff = 0;
+static const NSTimeInterval kDefaultConnectMaxBackoff = 0;
+static NSDictionary *const kDefaultAdditionalChannelArgs = nil;
+static NSString *const kDefaultPEMRootCertificates = nil;
+static NSString *const kDefaultPEMPrivateKey = nil;
+static NSString *const kDefaultPEMCertificateChain = nil;
+static NSString *const kDefaultOauth2AccessToken = nil;
+static const id<GRPCAuthorizationProtocol> kDefaultAuthTokenProvider = nil;
+static const GRPCTransportType kDefaultTransportType = GRPCTransportTypeChttp2BoringSSL;
+static NSString *const kDefaultHostNameOverride = nil;
+static const id kDefaultLogContext = nil;
+static NSString *const kDefaultChannelPoolDomain = nil;
+static const NSUInteger kDefaultChannelID = 0;
+
+// Check if two objects are equal. Returns YES if both are nil;
+static BOOL areObjectsEqual(id obj1, id obj2) {
+ if (obj1 == obj2) {
+ return YES;
+ }
+ if (obj1 == nil || obj2 == nil) {
+ return NO;
+ }
+ return [obj1 isEqual:obj2];
+}
+
+@implementation GRPCCallOptions {
+ @protected
+ NSString *_serverAuthority;
+ NSTimeInterval _timeout;
+ NSString *_oauth2AccessToken;
+ id<GRPCAuthorizationProtocol> _authTokenProvider;
+ NSDictionary *_initialMetadata;
+ NSString *_userAgentPrefix;
+ NSUInteger _responseSizeLimit;
+ GRPCCompressionAlgorithm _compressionAlgorithm;
+ BOOL _retryEnabled;
+ NSTimeInterval _keepaliveInterval;
+ NSTimeInterval _keepaliveTimeout;
+ NSTimeInterval _connectMinTimeout;
+ NSTimeInterval _connectInitialBackoff;
+ NSTimeInterval _connectMaxBackoff;
+ NSDictionary *_additionalChannelArgs;
+ NSString *_PEMRootCertificates;
+ NSString *_PEMPrivateKey;
+ NSString *_PEMCertificateChain;
+ GRPCTransportType _transportType;
+ NSString *_hostNameOverride;
+ id<NSObject> _logContext;
+ NSString *_channelPoolDomain;
+ NSUInteger _channelID;
+}
+
+@synthesize serverAuthority = _serverAuthority;
+@synthesize timeout = _timeout;
+@synthesize oauth2AccessToken = _oauth2AccessToken;
+@synthesize authTokenProvider = _authTokenProvider;
+@synthesize initialMetadata = _initialMetadata;
+@synthesize userAgentPrefix = _userAgentPrefix;
+@synthesize responseSizeLimit = _responseSizeLimit;
+@synthesize compressionAlgorithm = _compressionAlgorithm;
+@synthesize retryEnabled = _retryEnabled;
+@synthesize keepaliveInterval = _keepaliveInterval;
+@synthesize keepaliveTimeout = _keepaliveTimeout;
+@synthesize connectMinTimeout = _connectMinTimeout;
+@synthesize connectInitialBackoff = _connectInitialBackoff;
+@synthesize connectMaxBackoff = _connectMaxBackoff;
+@synthesize additionalChannelArgs = _additionalChannelArgs;
+@synthesize PEMRootCertificates = _PEMRootCertificates;
+@synthesize PEMPrivateKey = _PEMPrivateKey;
+@synthesize PEMCertificateChain = _PEMCertificateChain;
+@synthesize transportType = _transportType;
+@synthesize hostNameOverride = _hostNameOverride;
+@synthesize logContext = _logContext;
+@synthesize channelPoolDomain = _channelPoolDomain;
+@synthesize channelID = _channelID;
+
+- (instancetype)init {
+ return [self initWithServerAuthority:kDefaultServerAuthority
+ timeout:kDefaultTimeout
+ oauth2AccessToken:kDefaultOauth2AccessToken
+ authTokenProvider:kDefaultAuthTokenProvider
+ initialMetadata:kDefaultInitialMetadata
+ userAgentPrefix:kDefaultUserAgentPrefix
+ responseSizeLimit:kDefaultResponseSizeLimit
+ compressionAlgorithm:kDefaultCompressionAlgorithm
+ retryEnabled:kDefaultRetryEnabled
+ keepaliveInterval:kDefaultKeepaliveInterval
+ keepaliveTimeout:kDefaultKeepaliveTimeout
+ connectMinTimeout:kDefaultConnectMinTimeout
+ connectInitialBackoff:kDefaultConnectInitialBackoff
+ connectMaxBackoff:kDefaultConnectMaxBackoff
+ additionalChannelArgs:kDefaultAdditionalChannelArgs
+ PEMRootCertificates:kDefaultPEMRootCertificates
+ PEMPrivateKey:kDefaultPEMPrivateKey
+ PEMCertificateChain:kDefaultPEMCertificateChain
+ transportType:kDefaultTransportType
+ hostNameOverride:kDefaultHostNameOverride
+ logContext:kDefaultLogContext
+ channelPoolDomain:kDefaultChannelPoolDomain
+ channelID:kDefaultChannelID];
+}
+
+- (instancetype)initWithServerAuthority:(NSString *)serverAuthority
+ timeout:(NSTimeInterval)timeout
+ oauth2AccessToken:(NSString *)oauth2AccessToken
+ authTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider
+ initialMetadata:(NSDictionary *)initialMetadata
+ userAgentPrefix:(NSString *)userAgentPrefix
+ responseSizeLimit:(NSUInteger)responseSizeLimit
+ compressionAlgorithm:(GRPCCompressionAlgorithm)compressionAlgorithm
+ retryEnabled:(BOOL)retryEnabled
+ keepaliveInterval:(NSTimeInterval)keepaliveInterval
+ keepaliveTimeout:(NSTimeInterval)keepaliveTimeout
+ connectMinTimeout:(NSTimeInterval)connectMinTimeout
+ connectInitialBackoff:(NSTimeInterval)connectInitialBackoff
+ connectMaxBackoff:(NSTimeInterval)connectMaxBackoff
+ additionalChannelArgs:(NSDictionary *)additionalChannelArgs
+ PEMRootCertificates:(NSString *)PEMRootCertificates
+ PEMPrivateKey:(NSString *)PEMPrivateKey
+ PEMCertificateChain:(NSString *)PEMCertificateChain
+ transportType:(GRPCTransportType)transportType
+ hostNameOverride:(NSString *)hostNameOverride
+ logContext:(id)logContext
+ channelPoolDomain:(NSString *)channelPoolDomain
+ channelID:(NSUInteger)channelID {
+ if ((self = [super init])) {
+ _serverAuthority = [serverAuthority copy];
+ _timeout = timeout < 0 ? 0 : timeout;
+ _oauth2AccessToken = [oauth2AccessToken copy];
+ _authTokenProvider = authTokenProvider;
+ _initialMetadata =
+ initialMetadata == nil
+ ? nil
+ : [[NSDictionary alloc] initWithDictionary:initialMetadata copyItems:YES];
+ _userAgentPrefix = [userAgentPrefix copy];
+ _responseSizeLimit = responseSizeLimit;
+ _compressionAlgorithm = compressionAlgorithm;
+ _retryEnabled = retryEnabled;
+ _keepaliveInterval = keepaliveInterval < 0 ? 0 : keepaliveInterval;
+ _keepaliveTimeout = keepaliveTimeout < 0 ? 0 : keepaliveTimeout;
+ _connectMinTimeout = connectMinTimeout < 0 ? 0 : connectMinTimeout;
+ _connectInitialBackoff = connectInitialBackoff < 0 ? 0 : connectInitialBackoff;
+ _connectMaxBackoff = connectMaxBackoff < 0 ? 0 : connectMaxBackoff;
+ _additionalChannelArgs =
+ additionalChannelArgs == nil
+ ? nil
+ : [[NSDictionary alloc] initWithDictionary:additionalChannelArgs copyItems:YES];
+ _PEMRootCertificates = [PEMRootCertificates copy];
+ _PEMPrivateKey = [PEMPrivateKey copy];
+ _PEMCertificateChain = [PEMCertificateChain copy];
+ _transportType = transportType;
+ _hostNameOverride = [hostNameOverride copy];
+ _logContext = logContext;
+ _channelPoolDomain = [channelPoolDomain copy];
+ _channelID = channelID;
+ }
+ return self;
+}
+
+- (nonnull id)copyWithZone:(NSZone *)zone {
+ GRPCCallOptions *newOptions =
+ [[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority
+ timeout:_timeout
+ oauth2AccessToken:_oauth2AccessToken
+ authTokenProvider:_authTokenProvider
+ initialMetadata:_initialMetadata
+ userAgentPrefix:_userAgentPrefix
+ responseSizeLimit:_responseSizeLimit
+ compressionAlgorithm:_compressionAlgorithm
+ retryEnabled:_retryEnabled
+ keepaliveInterval:_keepaliveInterval
+ keepaliveTimeout:_keepaliveTimeout
+ connectMinTimeout:_connectMinTimeout
+ connectInitialBackoff:_connectInitialBackoff
+ connectMaxBackoff:_connectMaxBackoff
+ additionalChannelArgs:_additionalChannelArgs
+ PEMRootCertificates:_PEMRootCertificates
+ PEMPrivateKey:_PEMPrivateKey
+ PEMCertificateChain:_PEMCertificateChain
+ transportType:_transportType
+ hostNameOverride:_hostNameOverride
+ logContext:_logContext
+ channelPoolDomain:_channelPoolDomain
+ channelID:_channelID];
+ return newOptions;
+}
+
+- (nonnull id)mutableCopyWithZone:(NSZone *)zone {
+ GRPCMutableCallOptions *newOptions = [[GRPCMutableCallOptions allocWithZone:zone]
+ initWithServerAuthority:[_serverAuthority copy]
+ timeout:_timeout
+ oauth2AccessToken:[_oauth2AccessToken copy]
+ authTokenProvider:_authTokenProvider
+ initialMetadata:[[NSDictionary alloc] initWithDictionary:_initialMetadata
+ copyItems:YES]
+ userAgentPrefix:[_userAgentPrefix copy]
+ responseSizeLimit:_responseSizeLimit
+ compressionAlgorithm:_compressionAlgorithm
+ retryEnabled:_retryEnabled
+ keepaliveInterval:_keepaliveInterval
+ keepaliveTimeout:_keepaliveTimeout
+ connectMinTimeout:_connectMinTimeout
+ connectInitialBackoff:_connectInitialBackoff
+ connectMaxBackoff:_connectMaxBackoff
+ additionalChannelArgs:[[NSDictionary alloc] initWithDictionary:_additionalChannelArgs
+ copyItems:YES]
+ PEMRootCertificates:[_PEMRootCertificates copy]
+ PEMPrivateKey:[_PEMPrivateKey copy]
+ PEMCertificateChain:[_PEMCertificateChain copy]
+ transportType:_transportType
+ hostNameOverride:[_hostNameOverride copy]
+ logContext:_logContext
+ channelPoolDomain:[_channelPoolDomain copy]
+ channelID:_channelID];
+ return newOptions;
+}
+
+- (BOOL)hasChannelOptionsEqualTo:(GRPCCallOptions *)callOptions {
+ if (callOptions == nil) return NO;
+ if (!areObjectsEqual(callOptions.userAgentPrefix, _userAgentPrefix)) return NO;
+ if (!(callOptions.responseSizeLimit == _responseSizeLimit)) return NO;
+ if (!(callOptions.compressionAlgorithm == _compressionAlgorithm)) return NO;
+ if (!(callOptions.retryEnabled == _retryEnabled)) return NO;
+ if (!(callOptions.keepaliveInterval == _keepaliveInterval)) return NO;
+ if (!(callOptions.keepaliveTimeout == _keepaliveTimeout)) return NO;
+ if (!(callOptions.connectMinTimeout == _connectMinTimeout)) return NO;
+ if (!(callOptions.connectInitialBackoff == _connectInitialBackoff)) return NO;
+ if (!(callOptions.connectMaxBackoff == _connectMaxBackoff)) return NO;
+ if (!areObjectsEqual(callOptions.additionalChannelArgs, _additionalChannelArgs)) return NO;
+ if (!areObjectsEqual(callOptions.PEMRootCertificates, _PEMRootCertificates)) return NO;
+ if (!areObjectsEqual(callOptions.PEMPrivateKey, _PEMPrivateKey)) return NO;
+ if (!areObjectsEqual(callOptions.PEMCertificateChain, _PEMCertificateChain)) return NO;
+ if (!areObjectsEqual(callOptions.hostNameOverride, _hostNameOverride)) return NO;
+ if (!(callOptions.transportType == _transportType)) return NO;
+ if (!areObjectsEqual(callOptions.logContext, _logContext)) return NO;
+ if (!areObjectsEqual(callOptions.channelPoolDomain, _channelPoolDomain)) return NO;
+ if (!(callOptions.channelID == _channelID)) return NO;
+
+ return YES;
+}
+
+- (NSUInteger)channelOptionsHash {
+ NSUInteger result = 0;
+ result ^= _userAgentPrefix.hash;
+ result ^= _responseSizeLimit;
+ result ^= _compressionAlgorithm;
+ result ^= _retryEnabled;
+ result ^= (unsigned int)(_keepaliveInterval * 1000);
+ result ^= (unsigned int)(_keepaliveTimeout * 1000);
+ result ^= (unsigned int)(_connectMinTimeout * 1000);
+ result ^= (unsigned int)(_connectInitialBackoff * 1000);
+ result ^= (unsigned int)(_connectMaxBackoff * 1000);
+ result ^= _additionalChannelArgs.hash;
+ result ^= _PEMRootCertificates.hash;
+ result ^= _PEMPrivateKey.hash;
+ result ^= _PEMCertificateChain.hash;
+ result ^= _hostNameOverride.hash;
+ result ^= _transportType;
+ result ^= _logContext.hash;
+ result ^= _channelPoolDomain.hash;
+ result ^= _channelID;
+
+ return result;
+}
+
+@end
+
+@implementation GRPCMutableCallOptions
+
+@dynamic serverAuthority;
+@dynamic timeout;
+@dynamic oauth2AccessToken;
+@dynamic authTokenProvider;
+@dynamic initialMetadata;
+@dynamic userAgentPrefix;
+@dynamic responseSizeLimit;
+@dynamic compressionAlgorithm;
+@dynamic retryEnabled;
+@dynamic keepaliveInterval;
+@dynamic keepaliveTimeout;
+@dynamic connectMinTimeout;
+@dynamic connectInitialBackoff;
+@dynamic connectMaxBackoff;
+@dynamic additionalChannelArgs;
+@dynamic PEMRootCertificates;
+@dynamic PEMPrivateKey;
+@dynamic PEMCertificateChain;
+@dynamic transportType;
+@dynamic hostNameOverride;
+@dynamic logContext;
+@dynamic channelPoolDomain;
+@dynamic channelID;
+
+- (instancetype)init {
+ return [self initWithServerAuthority:kDefaultServerAuthority
+ timeout:kDefaultTimeout
+ oauth2AccessToken:kDefaultOauth2AccessToken
+ authTokenProvider:kDefaultAuthTokenProvider
+ initialMetadata:kDefaultInitialMetadata
+ userAgentPrefix:kDefaultUserAgentPrefix
+ responseSizeLimit:kDefaultResponseSizeLimit
+ compressionAlgorithm:kDefaultCompressionAlgorithm
+ retryEnabled:kDefaultRetryEnabled
+ keepaliveInterval:kDefaultKeepaliveInterval
+ keepaliveTimeout:kDefaultKeepaliveTimeout
+ connectMinTimeout:kDefaultConnectMinTimeout
+ connectInitialBackoff:kDefaultConnectInitialBackoff
+ connectMaxBackoff:kDefaultConnectMaxBackoff
+ additionalChannelArgs:kDefaultAdditionalChannelArgs
+ PEMRootCertificates:kDefaultPEMRootCertificates
+ PEMPrivateKey:kDefaultPEMPrivateKey
+ PEMCertificateChain:kDefaultPEMCertificateChain
+ transportType:kDefaultTransportType
+ hostNameOverride:kDefaultHostNameOverride
+ logContext:kDefaultLogContext
+ channelPoolDomain:kDefaultChannelPoolDomain
+ channelID:kDefaultChannelID];
+}
+
+- (nonnull id)copyWithZone:(NSZone *)zone {
+ GRPCCallOptions *newOptions =
+ [[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority
+ timeout:_timeout
+ oauth2AccessToken:_oauth2AccessToken
+ authTokenProvider:_authTokenProvider
+ initialMetadata:_initialMetadata
+ userAgentPrefix:_userAgentPrefix
+ responseSizeLimit:_responseSizeLimit
+ compressionAlgorithm:_compressionAlgorithm
+ retryEnabled:_retryEnabled
+ keepaliveInterval:_keepaliveInterval
+ keepaliveTimeout:_keepaliveTimeout
+ connectMinTimeout:_connectMinTimeout
+ connectInitialBackoff:_connectInitialBackoff
+ connectMaxBackoff:_connectMaxBackoff
+ additionalChannelArgs:_additionalChannelArgs
+ PEMRootCertificates:_PEMRootCertificates
+ PEMPrivateKey:_PEMPrivateKey
+ PEMCertificateChain:_PEMCertificateChain
+ transportType:_transportType
+ hostNameOverride:_hostNameOverride
+ logContext:_logContext
+ channelPoolDomain:_channelPoolDomain
+ channelID:_channelID];
+ return newOptions;
+}
+
+- (nonnull id)mutableCopyWithZone:(NSZone *)zone {
+ GRPCMutableCallOptions *newOptions = [[GRPCMutableCallOptions allocWithZone:zone]
+ initWithServerAuthority:_serverAuthority
+ timeout:_timeout
+ oauth2AccessToken:_oauth2AccessToken
+ authTokenProvider:_authTokenProvider
+ initialMetadata:_initialMetadata
+ userAgentPrefix:_userAgentPrefix
+ responseSizeLimit:_responseSizeLimit
+ compressionAlgorithm:_compressionAlgorithm
+ retryEnabled:_retryEnabled
+ keepaliveInterval:_keepaliveInterval
+ keepaliveTimeout:_keepaliveTimeout
+ connectMinTimeout:_connectMinTimeout
+ connectInitialBackoff:_connectInitialBackoff
+ connectMaxBackoff:_connectMaxBackoff
+ additionalChannelArgs:[_additionalChannelArgs copy]
+ PEMRootCertificates:_PEMRootCertificates
+ PEMPrivateKey:_PEMPrivateKey
+ PEMCertificateChain:_PEMCertificateChain
+ transportType:_transportType
+ hostNameOverride:_hostNameOverride
+ logContext:_logContext
+ channelPoolDomain:_channelPoolDomain
+ channelID:_channelID];
+ return newOptions;
+}
+
+- (void)setServerAuthority:(NSString *)serverAuthority {
+ _serverAuthority = [serverAuthority copy];
+}
+
+- (void)setTimeout:(NSTimeInterval)timeout {
+ if (timeout < 0) {
+ _timeout = 0;
+ } else {
+ _timeout = timeout;
+ }
+}
+
+- (void)setOauth2AccessToken:(NSString *)oauth2AccessToken {
+ _oauth2AccessToken = [oauth2AccessToken copy];
+}
+
+- (void)setAuthTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider {
+ _authTokenProvider = authTokenProvider;
+}
+
+- (void)setInitialMetadata:(NSDictionary *)initialMetadata {
+ _initialMetadata = [[NSDictionary alloc] initWithDictionary:initialMetadata copyItems:YES];
+}
+
+- (void)setUserAgentPrefix:(NSString *)userAgentPrefix {
+ _userAgentPrefix = [userAgentPrefix copy];
+}
+
+- (void)setResponseSizeLimit:(NSUInteger)responseSizeLimit {
+ _responseSizeLimit = responseSizeLimit;
+}
+
+- (void)setCompressionAlgorithm:(GRPCCompressionAlgorithm)compressionAlgorithm {
+ _compressionAlgorithm = compressionAlgorithm;
+}
+
+- (void)setRetryEnabled:(BOOL)retryEnabled {
+ _retryEnabled = retryEnabled;
+}
+
+- (void)setKeepaliveInterval:(NSTimeInterval)keepaliveInterval {
+ if (keepaliveInterval < 0) {
+ _keepaliveInterval = 0;
+ } else {
+ _keepaliveInterval = keepaliveInterval;
+ }
+}
+
+- (void)setKeepaliveTimeout:(NSTimeInterval)keepaliveTimeout {
+ if (keepaliveTimeout < 0) {
+ _keepaliveTimeout = 0;
+ } else {
+ _keepaliveTimeout = keepaliveTimeout;
+ }
+}
+
+- (void)setConnectMinTimeout:(NSTimeInterval)connectMinTimeout {
+ if (connectMinTimeout < 0) {
+ _connectMinTimeout = 0;
+ } else {
+ _connectMinTimeout = connectMinTimeout;
+ }
+}
+
+- (void)setConnectInitialBackoff:(NSTimeInterval)connectInitialBackoff {
+ if (connectInitialBackoff < 0) {
+ _connectInitialBackoff = 0;
+ } else {
+ _connectInitialBackoff = connectInitialBackoff;
+ }
+}
+
+- (void)setConnectMaxBackoff:(NSTimeInterval)connectMaxBackoff {
+ if (connectMaxBackoff < 0) {
+ _connectMaxBackoff = 0;
+ } else {
+ _connectMaxBackoff = connectMaxBackoff;
+ }
+}
+
+- (void)setAdditionalChannelArgs:(NSDictionary *)additionalChannelArgs {
+ _additionalChannelArgs =
+ [[NSDictionary alloc] initWithDictionary:additionalChannelArgs copyItems:YES];
+}
+
+- (void)setPEMRootCertificates:(NSString *)PEMRootCertificates {
+ _PEMRootCertificates = [PEMRootCertificates copy];
+}
+
+- (void)setPEMPrivateKey:(NSString *)PEMPrivateKey {
+ _PEMPrivateKey = [PEMPrivateKey copy];
+}
+
+- (void)setPEMCertificateChain:(NSString *)PEMCertificateChain {
+ _PEMCertificateChain = [PEMCertificateChain copy];
+}
+
+- (void)setTransportType:(GRPCTransportType)transportType {
+ _transportType = transportType;
+}
+
+- (void)setHostNameOverride:(NSString *)hostNameOverride {
+ _hostNameOverride = [hostNameOverride copy];
+}
+
+- (void)setLogContext:(id)logContext {
+ _logContext = logContext;
+}
+
+- (void)setChannelPoolDomain:(NSString *)channelPoolDomain {
+ _channelPoolDomain = [channelPoolDomain copy];
+}
+
+- (void)setChannelID:(NSUInteger)channelID {
+ _channelID = channelID;
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/internal/GRPCCallOptions+Internal.h b/src/objective-c/GRPCClient/internal/GRPCCallOptions+Internal.h
new file mode 100644
index 0000000000..eb691b3acb
--- /dev/null
+++ b/src/objective-c/GRPCClient/internal/GRPCCallOptions+Internal.h
@@ -0,0 +1,39 @@
+/*
+ *
+ * Copyright 2018 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 "../GRPCCallOptions.h"
+
+@interface GRPCCallOptions ()
+
+/**
+ * Parameter used for internal logging.
+ */
+@property(readonly) id logContext;
+
+@end
+
+@interface GRPCMutableCallOptions ()
+
+/**
+ * Parameter used for internal logging.
+ */
+@property(readwrite) id logContext;
+
+@end
diff --git a/src/objective-c/GRPCClient/private/ChannelArgsUtil.h b/src/objective-c/GRPCClient/private/ChannelArgsUtil.h
new file mode 100644
index 0000000000..f271e846f0
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/ChannelArgsUtil.h
@@ -0,0 +1,38 @@
+/*
+ *
+ * Copyright 2018 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>
+
+#include <grpc/impl/codegen/grpc_types.h>
+
+/** Free resources in the grpc core struct grpc_channel_args */
+void GRPCFreeChannelArgs(grpc_channel_args* channel_args);
+
+/**
+ * Allocates a @c grpc_channel_args and populates it with the options specified
+ * in the
+ * @c dictionary. Keys must be @c NSString, @c NSNumber, or a pointer. If the
+ * value responds to
+ * @c @selector(UTF8String) then it will be mapped to @c GRPC_ARG_STRING. If the
+ * value responds to
+ * @c @selector(intValue), it will be mapped to @c GRPC_ARG_INTEGER. Otherwise,
+ * if the value is not nil, it is mapped as a pointer. The caller of this
+ * function is responsible for calling
+ * @c GRPCFreeChannelArgs to free the @c grpc_channel_args struct.
+ */
+grpc_channel_args* GRPCBuildChannelArgs(NSDictionary* dictionary);
diff --git a/src/objective-c/GRPCClient/private/ChannelArgsUtil.m b/src/objective-c/GRPCClient/private/ChannelArgsUtil.m
new file mode 100644
index 0000000000..c1c65c3384
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/ChannelArgsUtil.m
@@ -0,0 +1,94 @@
+/*
+ *
+ * Copyright 2018 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 "ChannelArgsUtil.h"
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/string_util.h>
+
+#include <limits.h>
+
+static void *copy_pointer_arg(void *p) {
+ // Add ref count to the object when making copy
+ id obj = (__bridge id)p;
+ return (__bridge_retained void *)obj;
+}
+
+static void destroy_pointer_arg(void *p) {
+ // Decrease ref count to the object when destroying
+ CFRelease((CFTypeRef)p);
+}
+
+static int cmp_pointer_arg(void *p, void *q) { return p == q; }
+
+static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg,
+ cmp_pointer_arg};
+
+void GRPCFreeChannelArgs(grpc_channel_args *channel_args) {
+ for (size_t i = 0; i < channel_args->num_args; ++i) {
+ grpc_arg *arg = &channel_args->args[i];
+ gpr_free(arg->key);
+ if (arg->type == GRPC_ARG_STRING) {
+ gpr_free(arg->value.string);
+ }
+ }
+ gpr_free(channel_args->args);
+ gpr_free(channel_args);
+}
+
+grpc_channel_args *GRPCBuildChannelArgs(NSDictionary *dictionary) {
+ if (dictionary.count == 0) {
+ return NULL;
+ }
+
+ NSArray *keys = [dictionary allKeys];
+ NSUInteger argCount = [keys count];
+
+ grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args));
+ channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg));
+
+ // TODO(kriswuollett) Check that keys adhere to GRPC core library requirements
+
+ NSUInteger j = 0;
+ for (NSUInteger i = 0; i < argCount; ++i) {
+ grpc_arg *arg = &channelArgs->args[j];
+ arg->key = gpr_strdup([keys[i] UTF8String]);
+
+ id value = dictionary[keys[i]];
+ if ([value respondsToSelector:@selector(UTF8String)]) {
+ arg->type = GRPC_ARG_STRING;
+ arg->value.string = gpr_strdup([value UTF8String]);
+ j++;
+ } else if ([value respondsToSelector:@selector(intValue)]) {
+ int64_t value64 = [value longLongValue];
+ if (value64 <= INT_MAX || value64 >= INT_MIN) {
+ arg->type = GRPC_ARG_INTEGER;
+ arg->value.integer = (int)value64;
+ j++;
+ }
+ } else if (value != nil) {
+ arg->type = GRPC_ARG_POINTER;
+ arg->value.pointer.p = (__bridge_retained void *)value;
+ arg->value.pointer.vtable = &objc_arg_vtable;
+ j++;
+ }
+ }
+ channelArgs->num_args = j;
+
+ return channelArgs;
+}
diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.h b/src/objective-c/GRPCClient/private/GRPCChannel.h
index 6499d4398c..bbada0d8cb 100644
--- a/src/objective-c/GRPCClient/private/GRPCChannel.h
+++ b/src/objective-c/GRPCClient/private/GRPCChannel.h
@@ -20,49 +20,68 @@
#include <grpc/grpc.h>
+@protocol GRPCChannelFactory;
+
@class GRPCCompletionQueue;
+@class GRPCCallOptions;
+@class GRPCChannelConfiguration;
struct grpc_channel_credentials;
+NS_ASSUME_NONNULL_BEGIN
+
/**
- * Each separate instance of this class represents at least one TCP connection to the provided host.
+ * Signature for the channel. If two channel's signatures are the same and connect to the same
+ * remote, they share the same underlying \a GRPCChannel object.
*/
-@interface GRPCChannel : NSObject
+@interface GRPCChannelConfiguration : NSObject<NSCopying>
-@property(nonatomic, readonly, nonnull) struct grpc_channel *unmanagedChannel;
+- (instancetype)init NS_UNAVAILABLE;
-- (nullable instancetype)init NS_UNAVAILABLE;
++ (instancetype) new NS_UNAVAILABLE;
+
+/** The host that this channel is connected to. */
+@property(copy, readonly) NSString *host;
/**
- * Creates a secure channel to the specified @c host using default credentials and channel
- * arguments. If certificates could not be found to create a secure channel, then @c nil is
- * returned.
+ * Options of the corresponding call. Note that only the channel-related options are of interest to
+ * this class.
*/
-+ (nullable GRPCChannel *)secureChannelWithHost:(nonnull NSString *)host;
+@property(readonly) GRPCCallOptions *callOptions;
+
+/** Acquire the factory to generate a new channel with current configurations. */
+@property(readonly) id<GRPCChannelFactory> channelFactory;
+
+/** Acquire the dictionary of channel args with current configurations. */
+@property(copy, readonly) NSDictionary *channelArgs;
+
+- (nullable instancetype)initWithHost:(NSString *)host
+ callOptions:(GRPCCallOptions *)callOptions NS_DESIGNATED_INITIALIZER;
+
+@end
/**
- * Creates a secure channel to the specified @c host using Cronet as a transport mechanism.
+ * Each separate instance of this class represents at least one TCP connection to the provided host.
*/
-#ifdef GRPC_COMPILE_WITH_CRONET
-+ (nullable GRPCChannel *)secureCronetChannelWithHost:(nonnull NSString *)host
- channelArgs:(nonnull NSDictionary *)channelArgs;
-#endif
+@interface GRPCChannel : NSObject
+
+- (nullable instancetype)init NS_UNAVAILABLE;
+
++ (nullable instancetype) new NS_UNAVAILABLE;
+
/**
- * Creates a secure channel to the specified @c host using the specified @c credentials and
- * @c channelArgs. Only in tests should @c GRPC_SSL_TARGET_NAME_OVERRIDE_ARG channel arg be set.
+ * Create a channel with remote \a host and signature \a channelConfigurations.
*/
-+ (nonnull GRPCChannel *)secureChannelWithHost:(nonnull NSString *)host
- credentials:
- (nonnull struct grpc_channel_credentials *)credentials
- channelArgs:(nullable NSDictionary *)channelArgs;
+- (nullable instancetype)initWithChannelConfiguration:
+ (GRPCChannelConfiguration *)channelConfiguration NS_DESIGNATED_INITIALIZER;
/**
- * Creates an insecure channel to the specified @c host using the specified @c channelArgs.
+ * Create a grpc core call object (grpc_call) from this channel. If no call is created, NULL is
+ * returned.
*/
-+ (nonnull GRPCChannel *)insecureChannelWithHost:(nonnull NSString *)host
- channelArgs:(nullable NSDictionary *)channelArgs;
+- (nullable grpc_call *)unmanagedCallWithPath:(NSString *)path
+ completionQueue:(GRPCCompletionQueue *)queue
+ callOptions:(GRPCCallOptions *)callOptions;
-- (nullable grpc_call *)unmanagedCallWithPath:(nonnull NSString *)path
- serverName:(nonnull NSString *)serverName
- timeout:(NSTimeInterval)timeout
- completionQueue:(nonnull GRPCCompletionQueue *)queue;
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.m b/src/objective-c/GRPCClient/private/GRPCChannel.m
index b1f6ea270e..1a79fb04a0 100644
--- a/src/objective-c/GRPCClient/private/GRPCChannel.m
+++ b/src/objective-c/GRPCClient/private/GRPCChannel.m
@@ -18,206 +18,243 @@
#import "GRPCChannel.h"
-#include <grpc/grpc_security.h>
-#ifdef GRPC_COMPILE_WITH_CRONET
-#include <grpc/grpc_cronet.h>
-#endif
-#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
-#include <grpc/support/string_util.h>
-#ifdef GRPC_COMPILE_WITH_CRONET
-#import <Cronet/Cronet.h>
-#import <GRPCClient/GRPCCall+Cronet.h>
-#endif
+#import "../internal/GRPCCallOptions+Internal.h"
+#import "ChannelArgsUtil.h"
+#import "GRPCChannelFactory.h"
+#import "GRPCChannelPool.h"
#import "GRPCCompletionQueue.h"
+#import "GRPCCronetChannelFactory.h"
+#import "GRPCInsecureChannelFactory.h"
+#import "GRPCSecureChannelFactory.h"
+#import "version.h"
-static void *copy_pointer_arg(void *p) {
- // Add ref count to the object when making copy
- id obj = (__bridge id)p;
- return (__bridge_retained void *)obj;
-}
+#import <GRPCClient/GRPCCall+Cronet.h>
+#import <GRPCClient/GRPCCallOptions.h>
-static void destroy_pointer_arg(void *p) {
- // Decrease ref count to the object when destroying
- CFRelease((CFTreeRef)p);
-}
+@implementation GRPCChannelConfiguration
-static int cmp_pointer_arg(void *p, void *q) { return p == q; }
+- (instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions {
+ NSAssert(host.length > 0, @"Host must not be empty.");
+ NSAssert(callOptions != nil, @"callOptions must not be empty.");
+ if (host.length == 0 || callOptions == nil) {
+ return nil;
+ }
-static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg,
- cmp_pointer_arg};
+ if ((self = [super init])) {
+ _host = [host copy];
+ _callOptions = [callOptions copy];
+ }
+ return self;
+}
-static void FreeChannelArgs(grpc_channel_args *channel_args) {
- for (size_t i = 0; i < channel_args->num_args; ++i) {
- grpc_arg *arg = &channel_args->args[i];
- gpr_free(arg->key);
- if (arg->type == GRPC_ARG_STRING) {
- gpr_free(arg->value.string);
- }
+- (id<GRPCChannelFactory>)channelFactory {
+ GRPCTransportType type = _callOptions.transportType;
+ switch (type) {
+ case GRPCTransportTypeChttp2BoringSSL:
+ // TODO (mxyan): Remove when the API is deprecated
+#ifdef GRPC_COMPILE_WITH_CRONET
+ if (![GRPCCall isUsingCronet]) {
+#else
+ {
+#endif
+ NSError *error;
+ id<GRPCChannelFactory> factory = [GRPCSecureChannelFactory
+ factoryWithPEMRootCertificates:_callOptions.PEMRootCertificates
+ privateKey:_callOptions.PEMPrivateKey
+ certChain:_callOptions.PEMCertificateChain
+ error:&error];
+ NSAssert(factory != nil, @"Failed to create secure channel factory");
+ if (factory == nil) {
+ NSLog(@"Error creating secure channel factory: %@", error);
+ }
+ return factory;
+ }
+ // fallthrough
+ case GRPCTransportTypeCronet:
+ return [GRPCCronetChannelFactory sharedInstance];
+ case GRPCTransportTypeInsecure:
+ return [GRPCInsecureChannelFactory sharedInstance];
}
- gpr_free(channel_args->args);
- gpr_free(channel_args);
}
-/**
- * Allocates a @c grpc_channel_args and populates it with the options specified in the
- * @c dictionary. Keys must be @c NSString. If the value responds to @c @selector(UTF8String) then
- * it will be mapped to @c GRPC_ARG_STRING. If not, it will be mapped to @c GRPC_ARG_INTEGER if the
- * value responds to @c @selector(intValue). Otherwise, an exception will be raised. The caller of
- * this function is responsible for calling @c freeChannelArgs on a non-NULL returned value.
- */
-static grpc_channel_args *BuildChannelArgs(NSDictionary *dictionary) {
- if (!dictionary) {
- return NULL;
- }
-
- NSArray *keys = [dictionary allKeys];
- NSUInteger argCount = [keys count];
-
- grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args));
- channelArgs->num_args = argCount;
- channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg));
-
- // TODO(kriswuollett) Check that keys adhere to GRPC core library requirements
-
- for (NSUInteger i = 0; i < argCount; ++i) {
- grpc_arg *arg = &channelArgs->args[i];
- arg->key = gpr_strdup([keys[i] UTF8String]);
-
- id value = dictionary[keys[i]];
- if ([value respondsToSelector:@selector(UTF8String)]) {
- arg->type = GRPC_ARG_STRING;
- arg->value.string = gpr_strdup([value UTF8String]);
- } else if ([value respondsToSelector:@selector(intValue)]) {
- arg->type = GRPC_ARG_INTEGER;
- arg->value.integer = [value intValue];
- } else if (value != nil) {
- arg->type = GRPC_ARG_POINTER;
- arg->value.pointer.p = (__bridge_retained void *)value;
- arg->value.pointer.vtable = &objc_arg_vtable;
- } else {
- [NSException raise:NSInvalidArgumentException
- format:@"Invalid value type: %@", [value class]];
- }
+- (NSDictionary *)channelArgs {
+ NSMutableDictionary *args = [NSMutableDictionary new];
+
+ NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING;
+ NSString *userAgentPrefix = _callOptions.userAgentPrefix;
+ if (userAgentPrefix.length != 0) {
+ args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] =
+ [_callOptions.userAgentPrefix stringByAppendingFormat:@" %@", userAgent];
+ } else {
+ args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent;
}
- return channelArgs;
-}
+ NSString *hostNameOverride = _callOptions.hostNameOverride;
+ if (hostNameOverride) {
+ args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = hostNameOverride;
+ }
-@implementation GRPCChannel {
- // Retain arguments to channel_create because they may not be used on the thread that invoked
- // the channel_create function.
- NSString *_host;
- grpc_channel_args *_channelArgs;
-}
+ if (_callOptions.responseSizeLimit) {
+ args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] =
+ [NSNumber numberWithUnsignedInteger:_callOptions.responseSizeLimit];
+ }
-#ifdef GRPC_COMPILE_WITH_CRONET
-- (instancetype)initWithHost:(NSString *)host
- cronetEngine:(stream_engine *)cronetEngine
- channelArgs:(NSDictionary *)channelArgs {
- if (!host) {
- [NSException raise:NSInvalidArgumentException format:@"host argument missing"];
+ if (_callOptions.compressionAlgorithm != GRPC_COMPRESS_NONE) {
+ args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] =
+ [NSNumber numberWithInt:_callOptions.compressionAlgorithm];
}
- if (self = [super init]) {
- _channelArgs = BuildChannelArgs(channelArgs);
- _host = [host copy];
- _unmanagedChannel =
- grpc_cronet_secure_channel_create(cronetEngine, _host.UTF8String, _channelArgs, NULL);
+ if (_callOptions.keepaliveInterval != 0) {
+ args[@GRPC_ARG_KEEPALIVE_TIME_MS] =
+ [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveInterval * 1000)];
+ args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] =
+ [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.keepaliveTimeout * 1000)];
}
- return self;
-}
-#endif
+ if (!_callOptions.retryEnabled) {
+ args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:_callOptions.retryEnabled ? 1 : 0];
+ }
-- (instancetype)initWithHost:(NSString *)host
- secure:(BOOL)secure
- credentials:(struct grpc_channel_credentials *)credentials
- channelArgs:(NSDictionary *)channelArgs {
- if (!host) {
- [NSException raise:NSInvalidArgumentException format:@"host argument missing"];
+ if (_callOptions.connectMinTimeout > 0) {
+ args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] =
+ [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectMinTimeout * 1000)];
+ }
+ if (_callOptions.connectInitialBackoff > 0) {
+ args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber
+ numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectInitialBackoff * 1000)];
+ }
+ if (_callOptions.connectMaxBackoff > 0) {
+ args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] =
+ [NSNumber numberWithUnsignedInteger:(NSUInteger)(_callOptions.connectMaxBackoff * 1000)];
}
- if (secure && !credentials) {
- return nil;
+ if (_callOptions.logContext != nil) {
+ args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = _callOptions.logContext;
}
- if (self = [super init]) {
- _channelArgs = BuildChannelArgs(channelArgs);
- _host = [host copy];
- if (secure) {
- _unmanagedChannel =
- grpc_secure_channel_create(credentials, _host.UTF8String, _channelArgs, NULL);
- } else {
- _unmanagedChannel = grpc_insecure_channel_create(_host.UTF8String, _channelArgs, NULL);
- }
+ if (_callOptions.channelPoolDomain.length != 0) {
+ args[@GRPC_ARG_CHANNEL_POOL_DOMAIN] = _callOptions.channelPoolDomain;
}
- return self;
+ [args addEntriesFromDictionary:_callOptions.additionalChannelArgs];
+
+ return args;
}
-- (void)dealloc {
- // TODO(jcanizales): Be sure to add a test with a server that closes the connection prematurely,
- // as in the past that made this call to crash.
- grpc_channel_destroy(_unmanagedChannel);
- FreeChannelArgs(_channelArgs);
+- (id)copyWithZone:(NSZone *)zone {
+ GRPCChannelConfiguration *newConfig =
+ [[GRPCChannelConfiguration alloc] initWithHost:_host callOptions:_callOptions];
+
+ return newConfig;
}
-#ifdef GRPC_COMPILE_WITH_CRONET
-+ (GRPCChannel *)secureCronetChannelWithHost:(NSString *)host
- channelArgs:(NSDictionary *)channelArgs {
- stream_engine *engine = [GRPCCall cronetEngine];
- if (!engine) {
- [NSException raise:NSInvalidArgumentException format:@"cronet_engine is NULL. Set it first."];
- return nil;
+- (BOOL)isEqual:(id)object {
+ if (![object isKindOfClass:[GRPCChannelConfiguration class]]) {
+ return NO;
}
- return [[GRPCChannel alloc] initWithHost:host cronetEngine:engine channelArgs:channelArgs];
-}
-#endif
+ GRPCChannelConfiguration *obj = (GRPCChannelConfiguration *)object;
+ if (!(obj.host == _host || (_host != nil && [obj.host isEqualToString:_host]))) return NO;
+ if (!(obj.callOptions == _callOptions || [obj.callOptions hasChannelOptionsEqualTo:_callOptions]))
+ return NO;
-+ (GRPCChannel *)secureChannelWithHost:(NSString *)host {
- return [[GRPCChannel alloc] initWithHost:host secure:YES credentials:NULL channelArgs:NULL];
+ return YES;
}
-+ (GRPCChannel *)secureChannelWithHost:(NSString *)host
- credentials:(struct grpc_channel_credentials *)credentials
- channelArgs:(NSDictionary *)channelArgs {
- return [[GRPCChannel alloc] initWithHost:host
- secure:YES
- credentials:credentials
- channelArgs:channelArgs];
+- (NSUInteger)hash {
+ NSUInteger result = 31;
+ result ^= _host.hash;
+ result ^= _callOptions.channelOptionsHash;
+
+ return result;
}
-+ (GRPCChannel *)insecureChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)channelArgs {
- return [[GRPCChannel alloc] initWithHost:host secure:NO credentials:NULL channelArgs:channelArgs];
+@end
+
+@implementation GRPCChannel {
+ GRPCChannelConfiguration *_configuration;
+
+ grpc_channel *_unmanagedChannel;
}
-- (grpc_call *)unmanagedCallWithPath:(NSString *)path
- serverName:(NSString *)serverName
- timeout:(NSTimeInterval)timeout
- completionQueue:(GRPCCompletionQueue *)queue {
- GPR_ASSERT(timeout >= 0);
- if (timeout < 0) {
- timeout = 0;
+- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration {
+ NSAssert(channelConfiguration != nil, @"channelConfiguration must not be empty.");
+ if (channelConfiguration == nil) {
+ return nil;
}
- grpc_slice host_slice = grpc_empty_slice();
- if (serverName) {
- host_slice = grpc_slice_from_copied_string(serverName.UTF8String);
+
+ if ((self = [super init])) {
+ _configuration = [channelConfiguration copy];
+
+ // Create gRPC core channel object.
+ NSString *host = channelConfiguration.host;
+ NSAssert(host.length != 0, @"host cannot be nil");
+ NSDictionary *channelArgs;
+ if (channelConfiguration.callOptions.additionalChannelArgs.count != 0) {
+ NSMutableDictionary *args = [channelConfiguration.channelArgs mutableCopy];
+ [args addEntriesFromDictionary:channelConfiguration.callOptions.additionalChannelArgs];
+ channelArgs = args;
+ } else {
+ channelArgs = channelConfiguration.channelArgs;
+ }
+ id<GRPCChannelFactory> factory = channelConfiguration.channelFactory;
+ _unmanagedChannel = [factory createChannelWithHost:host channelArgs:channelArgs];
+ NSAssert(_unmanagedChannel != NULL, @"Failed to create channel");
+ if (_unmanagedChannel == NULL) {
+ NSLog(@"Unable to create channel.");
+ return nil;
+ }
}
+ return self;
+}
+
+- (grpc_call *)unmanagedCallWithPath:(NSString *)path
+ completionQueue:(GRPCCompletionQueue *)queue
+ callOptions:(GRPCCallOptions *)callOptions {
+ NSAssert(path.length > 0, @"path must not be empty.");
+ NSAssert(queue != nil, @"completionQueue must not be empty.");
+ NSAssert(callOptions != nil, @"callOptions must not be empty.");
+ if (path.length == 0) return NULL;
+ if (queue == nil) return NULL;
+ if (callOptions == nil) return NULL;
+
+ grpc_call *call = NULL;
+ // No need to lock here since _unmanagedChannel is only changed in _dealloc
+ NSAssert(_unmanagedChannel != NULL, @"Channel should have valid unmanaged channel.");
+ if (_unmanagedChannel == NULL) return NULL;
+
+ NSString *serverAuthority =
+ callOptions.transportType == GRPCTransportTypeCronet ? nil : callOptions.serverAuthority;
+ NSTimeInterval timeout = callOptions.timeout;
+ NSAssert(timeout >= 0, @"Invalid timeout");
+ if (timeout < 0) return NULL;
+ grpc_slice host_slice = serverAuthority
+ ? grpc_slice_from_copied_string(serverAuthority.UTF8String)
+ : grpc_empty_slice();
grpc_slice path_slice = grpc_slice_from_copied_string(path.UTF8String);
gpr_timespec deadline_ms =
timeout == 0 ? gpr_inf_future(GPR_CLOCK_REALTIME)
: gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
gpr_time_from_millis((int64_t)(timeout * 1000), GPR_TIMESPAN));
- grpc_call *call = grpc_channel_create_call(_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS,
- queue.unmanagedQueue, path_slice,
- serverName ? &host_slice : NULL, deadline_ms, NULL);
- if (serverName) {
+ call = grpc_channel_create_call(_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS,
+ queue.unmanagedQueue, path_slice,
+ serverAuthority ? &host_slice : NULL, deadline_ms, NULL);
+ if (serverAuthority) {
grpc_slice_unref(host_slice);
}
grpc_slice_unref(path_slice);
+ NSAssert(call != nil, @"Unable to create call.");
+ if (call == NULL) {
+ NSLog(@"Unable to create call.");
+ }
return call;
}
+- (void)dealloc {
+ if (_unmanagedChannel) {
+ grpc_channel_destroy(_unmanagedChannel);
+ }
+}
+
@end
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCChannelFactory.h
new file mode 100644
index 0000000000..a934e966e9
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCChannelFactory.h
@@ -0,0 +1,34 @@
+/*
+ *
+ * Copyright 2018 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>
+
+#include <grpc/impl/codegen/grpc_types.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** A factory interface which generates new channel. */
+@protocol GRPCChannelFactory
+
+/** Create a channel with specific channel args to a specific host. */
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSDictionary *)args;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool+Test.h b/src/objective-c/GRPCClient/private/GRPCChannelPool+Test.h
new file mode 100644
index 0000000000..e2c3aee3d9
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCChannelPool+Test.h
@@ -0,0 +1,51 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCChannelPool.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** Test-only interface for \a GRPCPooledChannel. */
+@interface GRPCPooledChannel (Test)
+
+/**
+ * Initialize a pooled channel with non-default destroy delay for testing purpose.
+ */
+- (nullable instancetype)initWithChannelConfiguration:
+ (GRPCChannelConfiguration *)channelConfiguration
+ destroyDelay:(NSTimeInterval)destroyDelay;
+
+/**
+ * Return the pointer to the raw channel wrapped.
+ */
+@property(atomic, readonly, nullable) GRPCChannel *wrappedChannel;
+
+@end
+
+/** Test-only interface for \a GRPCChannelPool. */
+@interface GRPCChannelPool (Test)
+
+/**
+ * Get an instance of pool isolated from the global shared pool with channels' destroy delay being
+ * \a destroyDelay.
+ */
+- (nullable instancetype)initTestPool;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool.h b/src/objective-c/GRPCClient/private/GRPCChannelPool.h
new file mode 100644
index 0000000000..e00ee69e63
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCChannelPool.h
@@ -0,0 +1,101 @@
+/*
+ *
+ * Copyright 2018 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 <GRPCClient/GRPCCallOptions.h>
+
+#import "GRPCChannelFactory.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol GRPCChannel;
+@class GRPCChannel;
+@class GRPCChannelPool;
+@class GRPCCompletionQueue;
+@class GRPCChannelConfiguration;
+@class GRPCWrappedCall;
+
+/**
+ * A proxied channel object that can be retained and used to create GRPCWrappedCall object
+ * regardless of the current connection status. If a connection is not established when a
+ * GRPCWrappedCall object is requested, it issues a connection/reconnection. This behavior is to
+ * follow that of gRPC core's channel object.
+ */
+@interface GRPCPooledChannel : NSObject
+
+- (nullable instancetype)init NS_UNAVAILABLE;
+
++ (nullable instancetype) new NS_UNAVAILABLE;
+
+/**
+ * Initialize with an actual channel object \a channel and a reference to the channel pool.
+ */
+- (nullable instancetype)initWithChannelConfiguration:
+ (GRPCChannelConfiguration *)channelConfiguration;
+
+/**
+ * Create a GRPCWrappedCall object (grpc_call) from this channel. If channel is disconnected, get a
+ * new channel object from the channel pool.
+ */
+- (nullable GRPCWrappedCall *)wrappedCallWithPath:(NSString *)path
+ completionQueue:(GRPCCompletionQueue *)queue
+ callOptions:(GRPCCallOptions *)callOptions;
+
+/**
+ * Notify the pooled channel that a wrapped call object is no longer referenced and will be
+ * dealloc'ed.
+ */
+- (void)notifyWrappedCallDealloc:(GRPCWrappedCall *)wrappedCall;
+
+/**
+ * Force the channel to disconnect immediately. GRPCWrappedCall objects previously created with
+ * \a wrappedCallWithPath are failed if not already finished. Subsequent calls to
+ * unmanagedCallWithPath: will attempt to reconnect to the remote channel.
+ */
+- (void)disconnect;
+
+@end
+
+/**
+ * Manage the pool of connected channels. When a channel is no longer referenced by any call,
+ * destroy the channel after a certain period of time elapsed.
+ */
+@interface GRPCChannelPool : NSObject
+
+- (nullable instancetype)init NS_UNAVAILABLE;
+
++ (nullable instancetype) new NS_UNAVAILABLE;
+
+/**
+ * Get the global channel pool.
+ */
++ (nullable instancetype)sharedInstance;
+
+/**
+ * Return a channel with a particular configuration. The channel may be a cached channel.
+ */
+- (nullable GRPCPooledChannel *)channelWithHost:(NSString *)host
+ callOptions:(GRPCCallOptions *)callOptions;
+
+/**
+ * Disconnect all channels in this pool.
+ */
+- (void)disconnectAllChannels;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool.m b/src/objective-c/GRPCClient/private/GRPCChannelPool.m
new file mode 100644
index 0000000000..a323f0490c
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCChannelPool.m
@@ -0,0 +1,276 @@
+/*
+ *
+ * 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 "../internal/GRPCCallOptions+Internal.h"
+#import "GRPCChannel.h"
+#import "GRPCChannelFactory.h"
+#import "GRPCChannelPool+Test.h"
+#import "GRPCChannelPool.h"
+#import "GRPCCompletionQueue.h"
+#import "GRPCConnectivityMonitor.h"
+#import "GRPCCronetChannelFactory.h"
+#import "GRPCInsecureChannelFactory.h"
+#import "GRPCSecureChannelFactory.h"
+#import "GRPCWrappedCall.h"
+#import "version.h"
+
+#import <GRPCClient/GRPCCall+Cronet.h>
+#include <grpc/support/log.h>
+
+extern const char *kCFStreamVarName;
+
+static GRPCChannelPool *gChannelPool;
+static dispatch_once_t gInitChannelPool;
+
+/** When all calls of a channel are destroyed, destroy the channel after this much seconds. */
+static const NSTimeInterval kDefaultChannelDestroyDelay = 30;
+
+@implementation GRPCPooledChannel {
+ GRPCChannelConfiguration *_channelConfiguration;
+ NSTimeInterval _destroyDelay;
+
+ NSHashTable<GRPCWrappedCall *> *_wrappedCalls;
+ GRPCChannel *_wrappedChannel;
+ NSDate *_lastTimedDestroy;
+ dispatch_queue_t _timerQueue;
+}
+
+- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration {
+ return [self initWithChannelConfiguration:channelConfiguration
+ destroyDelay:kDefaultChannelDestroyDelay];
+}
+
+- (nullable instancetype)initWithChannelConfiguration:
+ (GRPCChannelConfiguration *)channelConfiguration
+ destroyDelay:(NSTimeInterval)destroyDelay {
+ NSAssert(channelConfiguration != nil, @"channelConfiguration cannot be empty.");
+ if (channelConfiguration == nil) {
+ return nil;
+ }
+
+ if ((self = [super init])) {
+ _channelConfiguration = [channelConfiguration copy];
+ _destroyDelay = destroyDelay;
+ _wrappedCalls = [NSHashTable weakObjectsHashTable];
+ _wrappedChannel = nil;
+ _lastTimedDestroy = nil;
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
+ if (@available(iOS 8.0, macOS 10.10, *)) {
+ _timerQueue = dispatch_queue_create(NULL, dispatch_queue_attr_make_with_qos_class(
+ DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
+ } else {
+#else
+ {
+#endif
+ _timerQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+ }
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ // Disconnect GRPCWrappedCall objects created but not yet removed
+ if (_wrappedCalls.allObjects.count != 0) {
+ for (GRPCWrappedCall *wrappedCall in _wrappedCalls.allObjects) {
+ [wrappedCall channelDisconnected];
+ };
+ }
+}
+
+- (GRPCWrappedCall *)wrappedCallWithPath:(NSString *)path
+ completionQueue:(GRPCCompletionQueue *)queue
+ callOptions:(GRPCCallOptions *)callOptions {
+ NSAssert(path.length > 0, @"path must not be empty.");
+ NSAssert(queue != nil, @"completionQueue must not be empty.");
+ NSAssert(callOptions, @"callOptions must not be empty.");
+ if (path.length == 0 || queue == nil || callOptions == nil) {
+ return nil;
+ }
+
+ GRPCWrappedCall *call = nil;
+
+ @synchronized(self) {
+ if (_wrappedChannel == nil) {
+ _wrappedChannel = [[GRPCChannel alloc] initWithChannelConfiguration:_channelConfiguration];
+ if (_wrappedChannel == nil) {
+ NSAssert(_wrappedChannel != nil, @"Unable to get a raw channel for proxy.");
+ return nil;
+ }
+ }
+ _lastTimedDestroy = nil;
+
+ grpc_call *unmanagedCall =
+ [_wrappedChannel unmanagedCallWithPath:path
+ completionQueue:[GRPCCompletionQueue completionQueue]
+ callOptions:callOptions];
+ if (unmanagedCall == NULL) {
+ NSAssert(unmanagedCall != NULL, @"Unable to create grpc_call object");
+ return nil;
+ }
+
+ call = [[GRPCWrappedCall alloc] initWithUnmanagedCall:unmanagedCall pooledChannel:self];
+ if (call == nil) {
+ NSAssert(call != nil, @"Unable to create GRPCWrappedCall object");
+ grpc_call_unref(unmanagedCall);
+ return nil;
+ }
+
+ [_wrappedCalls addObject:call];
+ }
+ return call;
+}
+
+- (void)notifyWrappedCallDealloc:(GRPCWrappedCall *)wrappedCall {
+ NSAssert(wrappedCall != nil, @"wrappedCall cannot be empty.");
+ if (wrappedCall == nil) {
+ return;
+ }
+ @synchronized(self) {
+ // Detect if all objects weakly referenced in _wrappedCalls are (implicitly) removed.
+ // _wrappedCalls.count does not work here since the hash table may include deallocated weak
+ // references. _wrappedCalls.allObjects forces removal of those objects.
+ if (_wrappedCalls.allObjects.count == 0) {
+ // No more call has reference to this channel. We may start the timer for destroying the
+ // channel now.
+ NSDate *now = [NSDate date];
+ NSAssert(now != nil, @"Unable to create NSDate object 'now'.");
+ _lastTimedDestroy = now;
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)_destroyDelay * NSEC_PER_SEC),
+ _timerQueue, ^{
+ @synchronized(self) {
+ // Check _lastTimedDestroy against now in case more calls are created (and
+ // maybe destroyed) after this dispatch_async. In that case the current
+ // dispatch_after block should be discarded; the channel should be
+ // destroyed in a later dispatch_after block.
+ if (now != nil && self->_lastTimedDestroy == now) {
+ self->_wrappedChannel = nil;
+ self->_lastTimedDestroy = nil;
+ }
+ }
+ });
+ }
+ }
+}
+
+- (void)disconnect {
+ NSArray<GRPCWrappedCall *> *copiedWrappedCalls = nil;
+ @synchronized(self) {
+ if (_wrappedChannel != nil) {
+ _wrappedChannel = nil;
+ copiedWrappedCalls = _wrappedCalls.allObjects;
+ [_wrappedCalls removeAllObjects];
+ }
+ }
+ for (GRPCWrappedCall *wrappedCall in copiedWrappedCalls) {
+ [wrappedCall channelDisconnected];
+ }
+}
+
+- (GRPCChannel *)wrappedChannel {
+ GRPCChannel *channel = nil;
+ @synchronized(self) {
+ channel = _wrappedChannel;
+ }
+ return channel;
+}
+
+@end
+
+@interface GRPCChannelPool ()
+
+- (instancetype)initPrivate NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation GRPCChannelPool {
+ NSMutableDictionary<GRPCChannelConfiguration *, GRPCPooledChannel *> *_channelPool;
+}
+
++ (instancetype)sharedInstance {
+ dispatch_once(&gInitChannelPool, ^{
+ gChannelPool = [[GRPCChannelPool alloc] initPrivate];
+ NSAssert(gChannelPool != nil, @"Cannot initialize global channel pool.");
+ });
+ return gChannelPool;
+}
+
+- (instancetype)initPrivate {
+ 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];
+}
+
+- (GRPCPooledChannel *)channelWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions {
+ NSAssert(host.length > 0, @"Host must not be empty.");
+ NSAssert(callOptions != nil, @"callOptions must not be empty.");
+ if (host.length == 0 || callOptions == nil) {
+ return nil;
+ }
+
+ GRPCPooledChannel *pooledChannel = nil;
+ GRPCChannelConfiguration *configuration =
+ [[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions];
+ @synchronized(self) {
+ pooledChannel = _channelPool[configuration];
+ if (pooledChannel == nil) {
+ pooledChannel = [[GRPCPooledChannel alloc] initWithChannelConfiguration:configuration];
+ _channelPool[configuration] = pooledChannel;
+ }
+ }
+ return pooledChannel;
+}
+
+- (void)disconnectAllChannels {
+ NSArray<GRPCPooledChannel *> *copiedPooledChannels;
+ @synchronized(self) {
+ copiedPooledChannels = _channelPool.allValues;
+ }
+
+ // Disconnect pooled channels.
+ for (GRPCPooledChannel *pooledChannel in copiedPooledChannels) {
+ [pooledChannel disconnect];
+ }
+}
+
+- (void)connectivityChange:(NSNotification *)note {
+ [self disconnectAllChannels];
+}
+
+@end
+
+@implementation GRPCChannelPool (Test)
+
+- (instancetype)initTestPool {
+ return [self initPrivate];
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m
index a36788b35a..bb8618ff33 100644
--- a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m
+++ b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m
@@ -76,14 +76,14 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
}
}
-+ (void)registerObserver:(_Nonnull id)observer selector:(SEL)selector {
++ (void)registerObserver:(id)observer selector:(SEL)selector {
[[NSNotificationCenter defaultCenter] addObserver:observer
selector:selector
name:kGRPCConnectivityNotification
object:nil];
}
-+ (void)unregisterObserver:(_Nonnull id)observer {
++ (void)unregisterObserver:(id)observer {
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}
diff --git a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h
new file mode 100644
index 0000000000..738dfdb737
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h
@@ -0,0 +1,36 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCChannelFactory.h"
+
+@class GRPCChannel;
+typedef struct stream_engine stream_engine;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface GRPCCronetChannelFactory : NSObject<GRPCChannelFactory>
+
++ (nullable instancetype)sharedInstance;
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSDictionary *)args;
+
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m
new file mode 100644
index 0000000000..5bcb021dc4
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m
@@ -0,0 +1,79 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCCronetChannelFactory.h"
+
+#import "ChannelArgsUtil.h"
+#import "GRPCChannel.h"
+
+#ifdef GRPC_COMPILE_WITH_CRONET
+
+#import <Cronet/Cronet.h>
+#include <grpc/grpc_cronet.h>
+
+@implementation GRPCCronetChannelFactory {
+ stream_engine *_cronetEngine;
+}
+
++ (instancetype)sharedInstance {
+ static GRPCCronetChannelFactory *instance;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ instance = [[self alloc] initWithEngine:[Cronet getGlobalEngine]];
+ });
+ return instance;
+}
+
+- (instancetype)initWithEngine:(stream_engine *)engine {
+ NSAssert(engine != NULL, @"Cronet engine cannot be empty.");
+ if (!engine) {
+ return nil;
+ }
+ if ((self = [super init])) {
+ _cronetEngine = engine;
+ }
+ return self;
+}
+
+- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args {
+ grpc_channel_args *channelArgs = GRPCBuildChannelArgs(args);
+ grpc_channel *unmanagedChannel =
+ grpc_cronet_secure_channel_create(_cronetEngine, host.UTF8String, channelArgs, NULL);
+ GRPCFreeChannelArgs(channelArgs);
+ return unmanagedChannel;
+}
+
+@end
+
+#else
+
+@implementation GRPCCronetChannelFactory
+
++ (instancetype)sharedInstance {
+ NSAssert(NO, @"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel.");
+ return nil;
+}
+
+- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args {
+ NSAssert(NO, @"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel.");
+ return NULL;
+}
+
+@end
+
+#endif
diff --git a/src/objective-c/GRPCClient/private/GRPCHost.h b/src/objective-c/GRPCClient/private/GRPCHost.h
index 291b07df37..ca3c52ea17 100644
--- a/src/objective-c/GRPCClient/private/GRPCHost.h
+++ b/src/objective-c/GRPCClient/private/GRPCHost.h
@@ -20,6 +20,10 @@
#import <grpc/impl/codegen/compression_types.h>
+#import "GRPCChannelFactory.h"
+
+#import <GRPCClient/GRPCCallOptions.h>
+
NS_ASSUME_NONNULL_BEGIN
@class GRPCCompletionQueue;
@@ -28,12 +32,10 @@ struct grpc_channel_credentials;
@interface GRPCHost : NSObject
-+ (void)flushChannelCache;
+ (void)resetAllHostSettings;
@property(nonatomic, readonly) NSString *address;
@property(nonatomic, copy, nullable) NSString *userAgentPrefix;
-@property(nonatomic, nullable) struct grpc_channel_credentials *channelCreds;
@property(nonatomic) grpc_compression_algorithm compressAlgorithm;
@property(nonatomic) int keepaliveInterval;
@property(nonatomic) int keepaliveTimeout;
@@ -44,14 +46,14 @@ struct grpc_channel_credentials;
@property(nonatomic) unsigned int initialConnectBackoff;
@property(nonatomic) unsigned int maxConnectBackoff;
-/** The following properties should only be modified for testing: */
+@property(nonatomic) id<GRPCChannelFactory> channelFactory;
-@property(nonatomic, getter=isSecure) BOOL secure;
+/** The following properties should only be modified for testing: */
@property(nonatomic, copy, nullable) NSString *hostNameOverride;
/** The default response size limit is 4MB. Set this to override that default. */
-@property(nonatomic, strong, nullable) NSNumber *responseSizeLimitOverride;
+@property(nonatomic) NSUInteger responseSizeLimitOverride;
- (nullable instancetype)init NS_UNAVAILABLE;
/** Host objects initialized with the same address are the same. */
@@ -62,19 +64,10 @@ struct grpc_channel_credentials;
withCertChain:(nullable NSString *)pemCertChain
error:(NSError **)errorPtr;
-/** Create a grpc_call object to the provided path on this host. */
-- (nullable struct grpc_call *)unmanagedCallWithPath:(NSString *)path
- serverName:(NSString *)serverName
- timeout:(NSTimeInterval)timeout
- completionQueue:(GRPCCompletionQueue *)queue;
-
-// TODO: There's a race when a new RPC is coming through just as an existing one is getting
-// notified that there's no connectivity. If connectivity comes back at that moment, the new RPC
-// will have its channel destroyed by the other RPC, and will never get notified of a problem, so
-// it'll hang (the C layer logs a timeout, with exponential back off). One solution could be to pass
-// the GRPCChannel to the GRPCCall, renaming this as "disconnectChannel:channel", which would only
-// act on that specific channel.
-- (void)disconnect;
+@property(atomic) GRPCTransportType transportType;
+
++ (GRPCCallOptions *)callOptionsForHost:(NSString *)host;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCHost.m
index 85b95dee91..24348c3aed 100644
--- a/src/objective-c/GRPCClient/private/GRPCHost.m
+++ b/src/objective-c/GRPCClient/private/GRPCHost.m
@@ -18,46 +18,36 @@
#import "GRPCHost.h"
+#import <GRPCClient/GRPCCall+Cronet.h>
#import <GRPCClient/GRPCCall.h>
+#import <GRPCClient/GRPCCallOptions.h>
+
#include <grpc/grpc.h>
#include <grpc/grpc_security.h>
-#ifdef GRPC_COMPILE_WITH_CRONET
-#import <GRPCClient/GRPCCall+ChannelArg.h>
-#import <GRPCClient/GRPCCall+Cronet.h>
-#endif
-#import "GRPCChannel.h"
+#import "../internal/GRPCCallOptions+Internal.h"
+#import "GRPCChannelFactory.h"
#import "GRPCCompletionQueue.h"
#import "GRPCConnectivityMonitor.h"
+#import "GRPCCronetChannelFactory.h"
+#import "GRPCSecureChannelFactory.h"
#import "NSDictionary+GRPC.h"
#import "version.h"
NS_ASSUME_NONNULL_BEGIN
-extern const char *kCFStreamVarName;
-
-static NSMutableDictionary *kHostCache;
+static NSMutableDictionary *gHostCache;
@implementation GRPCHost {
- // TODO(mlumish): Investigate whether caching channels with strong links is a good idea.
- GRPCChannel *_channel;
+ NSString *_PEMRootCertificates;
+ NSString *_PEMPrivateKey;
+ NSString *_PEMCertificateChain;
}
+ (nullable instancetype)hostWithAddress:(NSString *)address {
return [[self alloc] initWithAddress:address];
}
-- (void)dealloc {
- if (_channelCreds != nil) {
- grpc_channel_credentials_release(_channelCreds);
- }
- // Connectivity monitor is not required for CFStream
- char *enableCFStream = getenv(kCFStreamVarName);
- if (enableCFStream == nil || enableCFStream[0] != '1') {
- [GRPCConnectivityMonitor unregisterObserver:self];
- }
-}
-
// Default initializer.
- (nullable instancetype)initWithAddress:(NSString *)address {
if (!address) {
@@ -76,241 +66,89 @@ static NSMutableDictionary *kHostCache;
// Look up the GRPCHost in the cache.
static dispatch_once_t cacheInitialization;
dispatch_once(&cacheInitialization, ^{
- kHostCache = [NSMutableDictionary dictionary];
+ gHostCache = [NSMutableDictionary dictionary];
});
- @synchronized(kHostCache) {
- GRPCHost *cachedHost = kHostCache[address];
+ @synchronized(gHostCache) {
+ GRPCHost *cachedHost = gHostCache[address];
if (cachedHost) {
return cachedHost;
}
if ((self = [super init])) {
- _address = address;
- _secure = YES;
- kHostCache[address] = self;
- _compressAlgorithm = GRPC_COMPRESS_NONE;
+ _address = [address copy];
_retryEnabled = YES;
- }
-
- // Connectivity monitor is not required for CFStream
- char *enableCFStream = getenv(kCFStreamVarName);
- if (enableCFStream == nil || enableCFStream[0] != '1') {
- [GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChange:)];
+ gHostCache[address] = self;
}
}
return self;
}
-+ (void)flushChannelCache {
- @synchronized(kHostCache) {
- [kHostCache enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, GRPCHost *_Nonnull host,
- BOOL *_Nonnull stop) {
- [host disconnect];
- }];
- }
-}
-
+ (void)resetAllHostSettings {
- @synchronized(kHostCache) {
- kHostCache = [NSMutableDictionary dictionary];
- }
-}
-
-- (nullable grpc_call *)unmanagedCallWithPath:(NSString *)path
- serverName:(NSString *)serverName
- timeout:(NSTimeInterval)timeout
- completionQueue:(GRPCCompletionQueue *)queue {
- // The __block attribute is to allow channel take refcount inside @synchronized block. Without
- // this attribute, retain of channel object happens after objc_sync_exit in release builds, which
- // may result in channel released before used. See grpc/#15033.
- __block GRPCChannel *channel;
- // This is racing -[GRPCHost disconnect].
- @synchronized(self) {
- if (!_channel) {
- _channel = [self newChannel];
- }
- channel = _channel;
+ @synchronized(gHostCache) {
+ gHostCache = [NSMutableDictionary dictionary];
}
- return [channel unmanagedCallWithPath:path
- serverName:serverName
- timeout:timeout
- completionQueue:queue];
-}
-
-- (NSData *)nullTerminatedDataWithString:(NSString *)string {
- // dataUsingEncoding: does not return a null-terminated string.
- NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
- NSMutableData *nullTerminated = [NSMutableData dataWithData:data];
- [nullTerminated appendBytes:"\0" length:1];
- return nullTerminated;
}
- (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCerts
withPrivateKey:(nullable NSString *)pemPrivateKey
withCertChain:(nullable NSString *)pemCertChain
error:(NSError **)errorPtr {
- static NSData *kDefaultRootsASCII;
- static NSError *kDefaultRootsError;
- static dispatch_once_t loading;
- dispatch_once(&loading, ^{
- NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem
- // Do not use NSBundle.mainBundle, as it's nil for tests of library projects.
- NSBundle *bundle = [NSBundle bundleForClass:self.class];
- NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"];
- NSError *error;
- // Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the
- // issuer). Load them as UTF8 and produce an ASCII equivalent.
- NSString *contentInUTF8 =
- [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
- if (contentInUTF8 == nil) {
- kDefaultRootsError = error;
- return;
- }
- kDefaultRootsASCII = [self nullTerminatedDataWithString:contentInUTF8];
- });
-
- NSData *rootsASCII;
- if (pemRootCerts != nil) {
- rootsASCII = [self nullTerminatedDataWithString:pemRootCerts];
- } else {
- if (kDefaultRootsASCII == nil) {
- if (errorPtr) {
- *errorPtr = kDefaultRootsError;
- }
- NSAssert(
- kDefaultRootsASCII,
- @"Could not read gRPCCertificates.bundle/roots.pem. This file, "
- "with the root certificates, is needed to establish secure (TLS) connections. "
- "Because the file is distributed with the gRPC library, this error is usually a sign "
- "that the library wasn't configured correctly for your project. Error: %@",
- kDefaultRootsError);
- return NO;
- }
- rootsASCII = kDefaultRootsASCII;
- }
-
- grpc_channel_credentials *creds;
- if (pemPrivateKey == nil && pemCertChain == nil) {
- creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL);
- } else {
- assert(pemPrivateKey != nil && pemCertChain != nil);
- grpc_ssl_pem_key_cert_pair key_cert_pair;
- NSData *privateKeyASCII = [self nullTerminatedDataWithString:pemPrivateKey];
- NSData *certChainASCII = [self nullTerminatedDataWithString:pemCertChain];
- key_cert_pair.private_key = privateKeyASCII.bytes;
- key_cert_pair.cert_chain = certChainASCII.bytes;
- creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL, NULL);
- }
-
- @synchronized(self) {
- if (_channelCreds != nil) {
- grpc_channel_credentials_release(_channelCreds);
- }
- _channelCreds = creds;
- }
-
+ _PEMRootCertificates = [pemRootCerts copy];
+ _PEMPrivateKey = [pemPrivateKey copy];
+ _PEMCertificateChain = [pemCertChain copy];
return YES;
}
-- (NSDictionary *)channelArgsUsingCronet:(BOOL)useCronet {
- NSMutableDictionary *args = [NSMutableDictionary dictionary];
-
- // TODO(jcanizales): Add OS and device information (see
- // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents ).
- NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING;
- if (_userAgentPrefix) {
- userAgent = [_userAgentPrefix stringByAppendingFormat:@" %@", userAgent];
- }
- args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent;
-
- if (_secure && _hostNameOverride) {
- args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = _hostNameOverride;
- }
-
- if (_responseSizeLimitOverride != nil) {
- args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] = _responseSizeLimitOverride;
- }
-
- if (_compressAlgorithm != GRPC_COMPRESS_NONE) {
- args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] = [NSNumber numberWithInt:_compressAlgorithm];
- }
-
- if (_keepaliveInterval != 0) {
- args[@GRPC_ARG_KEEPALIVE_TIME_MS] = [NSNumber numberWithInt:_keepaliveInterval];
- args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] = [NSNumber numberWithInt:_keepaliveTimeout];
- }
-
- id logContext = self.logContext;
- if (logContext != nil) {
- args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = logContext;
- }
-
- if (useCronet) {
- args[@GRPC_ARG_DISABLE_CLIENT_AUTHORITY_FILTER] = [NSNumber numberWithInt:1];
- }
-
- if (_retryEnabled == NO) {
- args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:0];
- }
-
- if (_minConnectTimeout > 0) {
- args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_minConnectTimeout];
- }
- if (_initialConnectBackoff > 0) {
- args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_initialConnectBackoff];
- }
- if (_maxConnectBackoff > 0) {
- args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_maxConnectBackoff];
- }
-
- return args;
-}
-
-- (GRPCChannel *)newChannel {
- BOOL useCronet = NO;
-#ifdef GRPC_COMPILE_WITH_CRONET
- useCronet = [GRPCCall isUsingCronet];
-#endif
- NSDictionary *args = [self channelArgsUsingCronet:useCronet];
- if (_secure) {
- GRPCChannel *channel;
- @synchronized(self) {
- if (_channelCreds == nil) {
- [self setTLSPEMRootCerts:nil withPrivateKey:nil withCertChain:nil error:nil];
- }
+- (GRPCCallOptions *)callOptions {
+ GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+ options.userAgentPrefix = _userAgentPrefix;
+ options.responseSizeLimit = _responseSizeLimitOverride;
+ options.compressionAlgorithm = (GRPCCompressionAlgorithm)_compressAlgorithm;
+ options.retryEnabled = _retryEnabled;
+ options.keepaliveInterval = (NSTimeInterval)_keepaliveInterval / 1000;
+ options.keepaliveTimeout = (NSTimeInterval)_keepaliveTimeout / 1000;
+ options.connectMinTimeout = (NSTimeInterval)_minConnectTimeout / 1000;
+ options.connectInitialBackoff = (NSTimeInterval)_initialConnectBackoff / 1000;
+ options.connectMaxBackoff = (NSTimeInterval)_maxConnectBackoff / 1000;
+ options.PEMRootCertificates = _PEMRootCertificates;
+ options.PEMPrivateKey = _PEMPrivateKey;
+ options.PEMCertificateChain = _PEMCertificateChain;
+ options.hostNameOverride = _hostNameOverride;
#ifdef GRPC_COMPILE_WITH_CRONET
- if (useCronet) {
- channel = [GRPCChannel secureCronetChannelWithHost:_address channelArgs:args];
- } else
-#endif
- {
- channel =
- [GRPCChannel secureChannelWithHost:_address credentials:_channelCreds channelArgs:args];
- }
+ // By old API logic, insecure channel precedes Cronet channel; Cronet channel preceeds default
+ // channel.
+ if ([GRPCCall isUsingCronet]) {
+ if (_transportType == GRPCTransportTypeInsecure) {
+ options.transportType = GRPCTransportTypeInsecure;
+ } else {
+ NSAssert(_transportType == GRPCTransportTypeDefault, @"Invalid transport type");
+ options.transportType = GRPCTransportTypeCronet;
}
- return channel;
- } else {
- return [GRPCChannel insecureChannelWithHost:_address channelArgs:args];
+ } else
+#endif
+ {
+ options.transportType = _transportType;
}
-}
+ options.logContext = _logContext;
-- (NSString *)hostName {
- // TODO(jcanizales): Default to nil instead of _address when Issue #2635 is clarified.
- return _hostNameOverride ?: _address;
+ return options;
}
-- (void)disconnect {
- // This is racing -[GRPCHost unmanagedCallWithPath:completionQueue:].
- @synchronized(self) {
- _channel = nil;
++ (GRPCCallOptions *)callOptionsForHost:(NSString *)host {
+ // TODO (mxyan): Remove when old API is deprecated
+ NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:host]];
+ if (hostURL.host && hostURL.port == nil) {
+ host = [hostURL.host stringByAppendingString:@":443"];
}
-}
-// Flushes the host cache when connectivity status changes or when connection switch between Wifi
-// and Cellular data, so that a new call will use a new channel. Otherwise, a new call will still
-// use the cached channel which is no longer available and will cause gRPC to hang.
-- (void)connectivityChange:(NSNotification *)note {
- [self disconnect];
+ GRPCCallOptions *callOptions = nil;
+ @synchronized(gHostCache) {
+ callOptions = [gHostCache[host] callOptions];
+ }
+ if (callOptions == nil) {
+ callOptions = [[GRPCCallOptions alloc] init];
+ }
+ return callOptions;
}
@end
diff --git a/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h
new file mode 100644
index 0000000000..2d471aebed
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h
@@ -0,0 +1,35 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCChannelFactory.h"
+
+@class GRPCChannel;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface GRPCInsecureChannelFactory : NSObject<GRPCChannelFactory>
+
++ (nullable instancetype)sharedInstance;
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSDictionary *)args;
+
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m
new file mode 100644
index 0000000000..8ad1e848f5
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m
@@ -0,0 +1,43 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCInsecureChannelFactory.h"
+
+#import "ChannelArgsUtil.h"
+#import "GRPCChannel.h"
+
+@implementation GRPCInsecureChannelFactory
+
++ (instancetype)sharedInstance {
+ static GRPCInsecureChannelFactory *instance;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ instance = [[self alloc] init];
+ });
+ return instance;
+}
+
+- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args {
+ grpc_channel_args *coreChannelArgs = GRPCBuildChannelArgs(args);
+ grpc_channel *unmanagedChannel =
+ grpc_insecure_channel_create(host.UTF8String, coreChannelArgs, NULL);
+ GRPCFreeChannelArgs(coreChannelArgs);
+ return unmanagedChannel;
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m
index fa4f022ff0..5f117f0607 100644
--- a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m
+++ b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m
@@ -36,7 +36,7 @@ static void CheckIsNonNilASCII(NSString *name, NSString *value) {
// Precondition: key isn't nil.
static void CheckKeyValuePairIsValid(NSString *key, id value) {
if ([key hasSuffix:@"-bin"]) {
- if (![value isKindOfClass:NSData.class]) {
+ if (![value isKindOfClass:[NSData class]]) {
[NSException raise:NSInvalidArgumentException
format:
@"Expected NSData value for header %@ ending in \"-bin\", "
@@ -44,7 +44,7 @@ static void CheckKeyValuePairIsValid(NSString *key, id value) {
key, value];
}
} else {
- if (![value isKindOfClass:NSString.class]) {
+ if (![value isKindOfClass:[NSString class]]) {
[NSException raise:NSInvalidArgumentException
format:
@"Expected NSString value for header %@ not ending in \"-bin\", "
diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h
new file mode 100644
index 0000000000..588239b706
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h
@@ -0,0 +1,38 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCChannelFactory.h"
+
+@class GRPCChannel;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface GRPCSecureChannelFactory : NSObject<GRPCChannelFactory>
+
++ (nullable instancetype)factoryWithPEMRootCertificates:(nullable NSString *)rootCerts
+ privateKey:(nullable NSString *)privateKey
+ certChain:(nullable NSString *)certChain
+ error:(NSError **)errorPtr;
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSDictionary *)args;
+
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m
new file mode 100644
index 0000000000..9699889536
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m
@@ -0,0 +1,135 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCSecureChannelFactory.h"
+
+#include <grpc/grpc_security.h>
+
+#import "ChannelArgsUtil.h"
+#import "GRPCChannel.h"
+
+@implementation GRPCSecureChannelFactory {
+ grpc_channel_credentials *_channelCreds;
+}
+
++ (instancetype)factoryWithPEMRootCertificates:(NSString *)rootCerts
+ privateKey:(NSString *)privateKey
+ certChain:(NSString *)certChain
+ error:(NSError **)errorPtr {
+ return [[self alloc] initWithPEMRootCerts:rootCerts
+ privateKey:privateKey
+ certChain:certChain
+ error:errorPtr];
+}
+
+- (NSData *)nullTerminatedDataWithString:(NSString *)string {
+ // dataUsingEncoding: does not return a null-terminated string.
+ NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
+ if (data == nil) {
+ return nil;
+ }
+ NSMutableData *nullTerminated = [NSMutableData dataWithData:data];
+ [nullTerminated appendBytes:"\0" length:1];
+ return nullTerminated;
+}
+
+- (instancetype)initWithPEMRootCerts:(NSString *)rootCerts
+ privateKey:(NSString *)privateKey
+ certChain:(NSString *)certChain
+ error:(NSError **)errorPtr {
+ static NSData *defaultRootsASCII;
+ static NSError *defaultRootsError;
+ static dispatch_once_t loading;
+ dispatch_once(&loading, ^{
+ NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem
+ // Do not use NSBundle.mainBundle, as it's nil for tests of library projects.
+ NSBundle *bundle = [NSBundle bundleForClass:[self class]];
+ NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"];
+ NSError *error;
+ // Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the
+ // issuer). Load them as UTF8 and produce an ASCII equivalent.
+ NSString *contentInUTF8 =
+ [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
+ if (contentInUTF8 == nil) {
+ defaultRootsError = error;
+ return;
+ }
+ defaultRootsASCII = [self nullTerminatedDataWithString:contentInUTF8];
+ });
+
+ NSData *rootsASCII;
+ if (rootCerts != nil) {
+ rootsASCII = [self nullTerminatedDataWithString:rootCerts];
+ } else {
+ if (defaultRootsASCII == nil) {
+ if (errorPtr) {
+ *errorPtr = defaultRootsError;
+ }
+ NSAssert(
+ defaultRootsASCII, NSObjectNotAvailableException,
+ @"Could not read gRPCCertificates.bundle/roots.pem. This file, "
+ "with the root certificates, is needed to establish secure (TLS) connections. "
+ "Because the file is distributed with the gRPC library, this error is usually a sign "
+ "that the library wasn't configured correctly for your project. Error: %@",
+ defaultRootsError);
+ return nil;
+ }
+ rootsASCII = defaultRootsASCII;
+ }
+
+ grpc_channel_credentials *creds = NULL;
+ if (privateKey.length == 0 && certChain.length == 0) {
+ creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL);
+ } else {
+ grpc_ssl_pem_key_cert_pair key_cert_pair;
+ NSData *privateKeyASCII = [self nullTerminatedDataWithString:privateKey];
+ NSData *certChainASCII = [self nullTerminatedDataWithString:certChain];
+ key_cert_pair.private_key = privateKeyASCII.bytes;
+ key_cert_pair.cert_chain = certChainASCII.bytes;
+ if (key_cert_pair.private_key == NULL || key_cert_pair.cert_chain == NULL) {
+ creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL);
+ } else {
+ creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL, NULL);
+ }
+ }
+
+ if ((self = [super init])) {
+ _channelCreds = creds;
+ }
+ return self;
+}
+
+- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args {
+ NSAssert(host.length != 0, @"host cannot be empty");
+ if (host.length == 0) {
+ return NULL;
+ }
+ grpc_channel_args *coreChannelArgs = GRPCBuildChannelArgs(args);
+ grpc_channel *unmanagedChannel =
+ grpc_secure_channel_create(_channelCreds, host.UTF8String, coreChannelArgs, NULL);
+ GRPCFreeChannelArgs(coreChannelArgs);
+ return unmanagedChannel;
+}
+
+- (void)dealloc {
+ if (_channelCreds != NULL) {
+ grpc_channel_credentials_release(_channelCreds);
+ }
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h
index f711850c2f..92bd1be257 100644
--- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h
+++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h
@@ -71,12 +71,16 @@
#pragma mark GRPCWrappedCall
+@class GRPCPooledChannel;
+
@interface GRPCWrappedCall : NSObject
-- (instancetype)initWithHost:(NSString *)host
- serverName:(NSString *)serverName
- path:(NSString *)path
- timeout:(NSTimeInterval)timeout NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype) new NS_UNAVAILABLE;
+
+- (instancetype)initWithUnmanagedCall:(grpc_call *)unmanagedCall
+ pooledChannel:(GRPCPooledChannel *)pooledChannel NS_DESIGNATED_INITIALIZER;
- (void)startBatchWithOperations:(NSArray *)ops errorHandler:(void (^)(void))errorHandler;
@@ -84,4 +88,6 @@
- (void)cancel;
+- (void)channelDisconnected;
+
@end
diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m
index 7781d27ca4..7d7e77f6ba 100644
--- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m
+++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m
@@ -23,6 +23,8 @@
#include <grpc/grpc.h>
#include <grpc/support/alloc.h>
+#import "GRPCChannel.h"
+#import "GRPCChannelPool.h"
#import "GRPCCompletionQueue.h"
#import "GRPCHost.h"
#import "NSData+GRPC.h"
@@ -234,35 +236,22 @@
#pragma mark GRPCWrappedCall
@implementation GRPCWrappedCall {
- GRPCCompletionQueue *_queue;
+ // pooledChannel holds weak reference to this object so this is ok
+ GRPCPooledChannel *_pooledChannel;
grpc_call *_call;
}
-- (instancetype)init {
- return [self initWithHost:nil serverName:nil path:nil timeout:0];
-}
-
-- (instancetype)initWithHost:(NSString *)host
- serverName:(NSString *)serverName
- path:(NSString *)path
- timeout:(NSTimeInterval)timeout {
- if (!path || !host) {
- [NSException raise:NSInvalidArgumentException format:@"path and host cannot be nil."];
+- (instancetype)initWithUnmanagedCall:(grpc_call *)unmanagedCall
+ pooledChannel:(GRPCPooledChannel *)pooledChannel {
+ NSAssert(unmanagedCall != NULL, @"unmanagedCall cannot be empty.");
+ NSAssert(pooledChannel != nil, @"pooledChannel cannot be empty.");
+ if (unmanagedCall == NULL || pooledChannel == nil) {
+ return nil;
}
- if (self = [super init]) {
- // Each completion queue consumes one thread. There's a trade to be made between creating and
- // consuming too many threads and having contention of multiple calls in a single completion
- // queue. Currently we use a singleton queue.
- _queue = [GRPCCompletionQueue completionQueue];
-
- _call = [[GRPCHost hostWithAddress:host] unmanagedCallWithPath:path
- serverName:serverName
- timeout:timeout
- completionQueue:_queue];
- if (_call == NULL) {
- return nil;
- }
+ if ((self = [super init])) {
+ _call = unmanagedCall;
+ _pooledChannel = pooledChannel;
}
return self;
}
@@ -278,41 +267,70 @@
[GRPCOpBatchLog addOpBatchToLog:operations];
#endif
- size_t nops = operations.count;
- grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op));
- size_t i = 0;
- for (GRPCOperation *operation in operations) {
- ops_array[i++] = operation.op;
- }
- grpc_call_error error =
- grpc_call_start_batch(_call, ops_array, nops, (__bridge_retained void *)(^(bool success) {
- if (!success) {
- if (errorHandler) {
- errorHandler();
- } else {
- return;
- }
- }
- for (GRPCOperation *operation in operations) {
- [operation finish];
- }
- }),
- NULL);
- gpr_free(ops_array);
-
- if (error != GRPC_CALL_OK) {
- [NSException
- raise:NSInternalInconsistencyException
- format:@"A precondition for calling grpc_call_start_batch wasn't met. Error %i", error];
+ @synchronized(self) {
+ if (_call != NULL) {
+ size_t nops = operations.count;
+ grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op));
+ size_t i = 0;
+ for (GRPCOperation *operation in operations) {
+ ops_array[i++] = operation.op;
+ }
+ grpc_call_error error =
+ grpc_call_start_batch(_call, ops_array, nops, (__bridge_retained void *)(^(bool success) {
+ if (!success) {
+ if (errorHandler) {
+ errorHandler();
+ } else {
+ return;
+ }
+ }
+ for (GRPCOperation *operation in operations) {
+ [operation finish];
+ }
+ }),
+ NULL);
+ gpr_free(ops_array);
+
+ NSAssert(error == GRPC_CALL_OK, @"Error starting a batch of operations: %i", error);
+ // To avoid compiler complaint when NSAssert is disabled.
+ if (error != GRPC_CALL_OK) {
+ return;
+ }
+ }
}
}
- (void)cancel {
- grpc_call_cancel(_call, NULL);
+ @synchronized(self) {
+ if (_call != NULL) {
+ grpc_call_cancel(_call, NULL);
+ }
+ }
+}
+
+- (void)channelDisconnected {
+ @synchronized(self) {
+ if (_call != NULL) {
+ // Unreference the call will lead to its cancellation in the core. Note that since
+ // this function is only called with a network state change, any existing GRPCCall object will
+ // also receive the same notification and cancel themselves with GRPCErrorCodeUnavailable, so
+ // the user gets GRPCErrorCodeUnavailable in this case.
+ grpc_call_unref(_call);
+ _call = NULL;
+ }
+ }
}
- (void)dealloc {
- grpc_call_unref(_call);
+ @synchronized(self) {
+ if (_call != NULL) {
+ grpc_call_unref(_call);
+ _call = NULL;
+ }
+ }
+ // Explicitly converting weak reference _pooledChannel to strong.
+ __strong GRPCPooledChannel *channel = _pooledChannel;
+ [channel notifyWrappedCallDealloc:self];
}
@end
diff --git a/src/objective-c/GRPCClient/private/version.h b/src/objective-c/GRPCClient/private/version.h
index 0be0e3c9a0..5e089fde31 100644
--- a/src/objective-c/GRPCClient/private/version.h
+++ b/src/objective-c/GRPCClient/private/version.h
@@ -22,4 +22,4 @@
// instead. This file can be regenerated from the template by running
// `tools/buildgen/generate_projects.sh`.
-#define GRPC_OBJC_VERSION_STRING @"1.18.0-dev"
+#define GRPC_OBJC_VERSION_STRING @"1.19.0-dev"