diff options
-rw-r--r-- | include/grpc/impl/codegen/grpc_types.h | 4 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/GRPCCall.h | 145 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/GRPCCall.m | 253 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/GRPCCallOptions.h | 317 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/GRPCCallOptions.m | 431 |
5 files changed, 1105 insertions, 45 deletions
diff --git a/include/grpc/impl/codegen/grpc_types.h b/include/grpc/impl/codegen/grpc_types.h index 3ce88a8264..4cf13edca9 100644 --- a/include/grpc/impl/codegen/grpc_types.h +++ b/include/grpc/impl/codegen/grpc_types.h @@ -347,6 +347,10 @@ typedef struct { /** If set to non zero, surfaces the user agent string to the server. User agent is surfaced by default. */ #define GRPC_ARG_SURFACE_USER_AGENT "grpc.surface_user_agent" +/** gRPC Objective-C channel pooling domain string. */ +#define GRPC_ARG_CHANNEL_POOL_DOMAIN "grpc.channel_pooling_domain" +/** gRPC Objective-C channel pooling id. */ +#define GRPC_ARG_CHANNEL_ID "grpc.channel_id" /** \} */ /** Result of a grpc call. If the caller satisfies the prerequisites of a diff --git a/src/objective-c/GRPCClient/GRPCCall.h b/src/objective-c/GRPCClient/GRPCCall.h index e0ef8b1391..3f6ec75c04 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" + +@class GRPCCallOptions; + #pragma mark gRPC errors /** Domain of NSError objects produced by gRPC. */ @@ -140,42 +144,123 @@ typedef NS_ENUM(NSUInteger, GRPCErrorCode) { }; /** - * 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, -}; - -/** * Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by * the server. */ extern id const kGRPCHeadersKey; extern id const kGRPCTrailersKey; +/** An object can implement this protocol to receive responses from server from a call. */ +@protocol GRPCResponseHandler +@optional +/** Issued when initial metadata is received from the server. */ +- (void)receivedInitialMetadata:(NSDictionary *)initialMetadata; +/** + * Issued when a message is received from the server. The message may be raw data from the server + * (when using \a GRPCCall2 directly) or deserialized proto object (when using \a ProtoRPC). + */ +- (void)receivedMessage:(id)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)closedWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error; + +/** + * 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; + +@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; + +/** Initialize with all properties. */ +- (instancetype)initWithHost:(NSString *)host path:(NSString *)path safety:(GRPCCallSafety)safety; + +/** 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. + */ +@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; /** - * 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 handler The object to which responses should be issed. + * \param callOptions Options for the call. */ -@property(atomic, copy, readwrite) NSString *serverName; +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + handler:(id<GRPCResponseHandler>)handler + callOptions:(GRPCCallOptions *)callOptions NS_DESIGNATED_INITIALIZER; +/** + * Convevience initializer for a call that uses default call options. + */ +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + handler:(id<GRPCResponseHandler>)handler; /** - * 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. Can only be called once. */ -@property NSTimeInterval timeout; +- (void)start; + +/** + * Cancel the request of this call at best effort; notifies the server that the RPC should be + * cancelled, and issue callback to the user with an error code CANCELED if the call is not + * finished. + */ +- (void)cancel; + +/** + * Send a message to the server. Data are sent as raw bytes in gRPC message frames. + */ +- (void)writeWithData:(NSData *)data; + +/** + * Finish the RPC request and half-close the call. The server may still send messages and/or + * trailers to the client. + */ -(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 + +/** + * This interface is deprecated. Please use \a GRPCcall2. + * + * Represents a single gRPC remote call. + */ +@interface GRPCCall : GRXWriter /** * The container of the request headers of an RPC conforms to this protocol, which is a subset of @@ -236,7 +321,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,21 +330,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. - * - * 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 diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index 084fbdeb49..519f91e522 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -20,13 +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 "private/GRPCConnectivityMonitor.h" +#import "GRPCCallOptions.h" #import "private/GRPCHost.h" +#import "private/GRPCConnectivityMonitor.h" #import "private/GRPCRequestHeaders.h" #import "private/GRPCWrappedCall.h" #import "private/NSData+GRPC.h" @@ -52,6 +55,165 @@ 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 { + if ((self = [super init])) { + _host = host; + _path = path; + _safety = safety; + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + GRPCRequestOptions *request = + [[GRPCRequestOptions alloc] initWithHost:[_host copy] path:[_path copy] safety:_safety]; + + return request; +} + +@end + +@implementation GRPCCall2 { + GRPCCallOptions *_callOptions; + id<GRPCResponseHandler> _handler; + + GRPCCall *_call; + BOOL _initialMetadataPublished; + GRXBufferedPipe *_pipe; + dispatch_queue_t _dispatchQueue; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + handler:(id<GRPCResponseHandler>)handler + callOptions:(GRPCCallOptions *)callOptions { + if (!requestOptions || !requestOptions.host || !requestOptions.path) { + [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."]; + } + + if ((self = [super init])) { + _requestOptions = [requestOptions copy]; + _callOptions = [callOptions copy]; + _handler = handler; + _initialMetadataPublished = NO; + _pipe = [GRXBufferedPipe pipe]; + _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + } + + return self; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + handler:(id<GRPCResponseHandler>)handler { + return [self initWithRequestOptions:requestOptions handler:handler callOptions:nil]; +} + +- (void)start { + dispatch_async(_dispatchQueue, ^{ + if (!self->_callOptions) { + self->_callOptions = [[GRPCCallOptions alloc] init]; + } + + self->_call = [[GRPCCall alloc] initWithHost:self->_requestOptions.host + path:self->_requestOptions.path + callSafety:self->_requestOptions.safety + requestsWriter:self->_pipe + callOptions:self->_callOptions]; + if (self->_callOptions.initialMetadata) { + [self->_call.requestHeaders addEntriesFromDictionary:self->_callOptions.initialMetadata]; + } + id<GRXWriteable> responseWriteable = [[GRXWriteable alloc] initWithValueHandler:^(id value) { + dispatch_async(self->_dispatchQueue, ^{ + if (self->_handler) { + id<GRPCResponseHandler> handler = self->_handler; + NSDictionary *headers = nil; + if (!self->_initialMetadataPublished) { + headers = self->_call.responseHeaders; + self->_initialMetadataPublished = YES; + } + if (headers) { + dispatch_async(handler.dispatchQueue, ^{ + [handler receivedInitialMetadata:headers]; + }); + } + if (value) { + dispatch_async(handler.dispatchQueue, ^{ + [handler receivedMessage:value]; + }); + } + } + }); + } + completionHandler:^(NSError *errorOrNil) { + dispatch_async(self->_dispatchQueue, ^{ + if (self->_handler) { + id<GRPCResponseHandler> handler = self->_handler; + NSDictionary *headers = nil; + if (!self->_initialMetadataPublished) { + headers = self->_call.responseHeaders; + self->_initialMetadataPublished = YES; + } + if (headers) { + dispatch_async(handler.dispatchQueue, ^{ + [handler receivedInitialMetadata:headers]; + }); + } + dispatch_async(handler.dispatchQueue, ^{ + [handler closedWithTrailingMetadata:self->_call.responseTrailers error:errorOrNil]; + }); + } + }); + }]; + [self->_call startWithWriteable:responseWriteable]; + }); +} + +- (void)cancel { + dispatch_async(_dispatchQueue, ^{ + if (self->_call) { + [self->_call cancel]; + self->_call = nil; + } + if (self->_handler) { + id<GRPCResponseHandler> handler = self->_handler; + dispatch_async(handler.dispatchQueue, ^{ + [handler closedWithTrailingMetadata:nil + error:[NSError errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeCancelled + userInfo:@{ + NSLocalizedDescriptionKey : + @"Canceled by app" + }]]; + }); + self->_handler = nil; + } + }); +} + +- (void)writeWithData:(NSData *)data { + dispatch_async(_dispatchQueue, ^{ + [self->_pipe writeValue:data]; + }); +} + +- (void)finish { + dispatch_async(_dispatchQueue, ^{ + if (self->_call) { + [self->_pipe writesFinishedWithError:nil]; + } + }); +} + @end // The following methods of a C gRPC call object aren't reentrant, and thus @@ -75,6 +237,8 @@ const char *kCFStreamVarName = "grpc_cfstream"; NSString *_host; NSString *_path; + GRPCCallSafety _callSafety; + GRPCCallOptions *_callOptions; GRPCWrappedCall *_wrappedCall; GRPCConnectivityMonitor *_connectivityMonitor; @@ -113,6 +277,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; @@ -156,6 +323,18 @@ const char *kCFStreamVarName = "grpc_cfstream"; - (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 { if (!host || !path) { [NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."]; } @@ -166,6 +345,8 @@ const char *kCFStreamVarName = "grpc_cfstream"; 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); @@ -317,11 +498,36 @@ 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; + 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; + default: + [NSException raise:NSInvalidArgumentException format:@"Invalid call safety value."]; + } + uint32_t callFlag = callSafetyFlags; + + NSMutableDictionary *headers = _requestHeaders; + 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:callFlag handler:nil]; // No clean-up needed after SEND_INITIAL_METADATA if (!_unaryCall) { [_wrappedCall startBatchWithOperations:@[ op ]]; @@ -458,13 +664,10 @@ const char *kCFStreamVarName = "grpc_cfstream"; _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable dispatchQueue:_responseQueue]; - _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host - serverName:_serverName - path:_path - timeout:_timeout]; + _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host path:_path callOptions:_callOptions]; NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?"); - [self sendHeaders:_requestHeaders]; + [self sendHeaders]; [self invokeCall]; // Connectivity monitor is not required for CFStream @@ -486,15 +689,43 @@ const char *kCFStreamVarName = "grpc_cfstream"; // that the life of the instance is determined by this retain cycle. _retainSelf = self; - if (self.tokenProvider != nil) { + if (_callOptions == nil) { + GRPCMutableCallOptions *callOptions; + if ([GRPCHost isHostConfigured:_host]) { + GRPCHost *hostConfig = [GRPCHost hostWithAddress:_host]; + callOptions = hostConfig.callOptions; + } else { + callOptions = [[GRPCMutableCallOptions alloc] init]; + } + if (_serverName != nil) { + 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; + } + if (_callOptions.authTokenProvider != 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; + strongSelf->_fetchedOauth2AccessToken = token; } [strongSelf startCallWithWriteable:writeable]; strongSelf.isWaitingForToken = NO; diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.h b/src/objective-c/GRPCClient/GRPCCallOptions.h new file mode 100644 index 0000000000..75b320ca6d --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCallOptions.h @@ -0,0 +1,317 @@ +/* + * + * 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> + +/** + * 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(NSInteger, GRPCCompressAlgorithm) { + GRPCCompressNone = 0, + GRPCCompressDeflate, + GRPCCompressGzip, + GRPCStreamCompressGzip, +}; + +// The transport to be used by a gRPC call +typedef NS_ENUM(NSInteger, GRPCTransportType) { + // gRPC internal HTTP/2 stack with BoringSSL + GRPCTransportTypeDefault = 0, + // Cronet stack + GRPCTransportTypeCronet, + // Insecure channel. FOR TEST ONLY! + GRPCTransportTypeInsecure, +}; + +@protocol GRPCAuthorizationProtocol +- (void)getTokenWithHandler:(void (^)(NSString *token))hander; +@end + +@interface GRPCCallOptions : NSObject<NSCopying, NSMutableCopying> + +// Call parameters +/** + * The authority for the RPC. If nil, the default authority will be used. + * + * Note: This property must be nil when Cronet transport is enabled. + * 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(readonly) 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) 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) id<GRPCAuthorizationProtocol> authTokenProvider; + +/** + * Initial metadata key-value pairs that should be included in the request. + */ +@property(copy, readwrite) 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) 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) GRPCCompressAlgorithm compressAlgorithm; + +/** + * 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 enableRetry; + +/** + * 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. + */ +@property(readonly) NSTimeInterval keepaliveInterval; +@property(readonly) NSTimeInterval keepaliveTimeout; + +// Parameters for connection backoff. 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) 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) NSString *pemRootCert; + +/** + * PEM format private key for client authentication, if required by the server. + */ +@property(copy, readonly) NSString *pemPrivateKey; + +/** + * PEM format certificate chain for client authentication, if required by the server. + */ +@property(copy, readonly) NSString *pemCertChain; + +/** + * 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) NSString *hostNameOverride; + +/** + * Parameter used for internal logging. + */ +@property(readonly) id logContext; + +/** + * 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) NSString *channelPoolDomain; + +/** + * Channel id allows a call to force creating a new channel (connection) rather than using a cached + * channel. Calls using distinct channelId will not get cached to the same connection. + */ +@property(readonly) NSUInteger channelId; + +@end + +@interface GRPCMutableCallOptions : GRPCCallOptions<NSCopying, NSMutableCopying> + +// Call parameters +/** + * The authority for the RPC. If nil, the default authority will be used. + * + * Note: This property must be nil when Cronet transport is enabled. + * 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(readwrite) 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(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) 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) id<GRPCAuthorizationProtocol> authTokenProvider; + +// 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) 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) GRPCCompressAlgorithm compressAlgorithm; + +/** + * 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 enableRetry; + +/** + * 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. + */ +@property(readwrite) NSTimeInterval keepaliveInterval; +@property(readwrite) NSTimeInterval keepaliveTimeout; + +// Parameters for connection backoff. 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) 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) NSString *pemRootCert; + +/** + * PEM format private key for client authentication, if required by the server. + */ +@property(copy, readwrite) NSString *pemPrivateKey; + +/** + * PEM format certificate chain for client authentication, if required by the server. + */ +@property(copy, readwrite) NSString *pemCertChain; + +/** + * 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) NSString *hostNameOverride; + +/** + * Parameter used for internal logging. + */ +@property(copy, readwrite) id logContext; + +/** + * 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, readwrite) NSString *channelPoolDomain; + +/** + * Channel id allows a call to force creating a new channel (connection) rather than using a cached + * channel. Calls using distinct channelId will not get cached to the same connection. + */ +@property(readwrite) NSUInteger channelId; + +@end diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.m b/src/objective-c/GRPCClient/GRPCCallOptions.m new file mode 100644 index 0000000000..ee76c2a642 --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCallOptions.m @@ -0,0 +1,431 @@ +/* + * + * 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" + +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 GRPCCompressAlgorithm kDefaultCompressAlgorithm = GRPCCompressNone; +static const BOOL kDefaultEnableRetry = 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 kDefaultPemRootCert = nil; +static NSString *const kDefaultPemPrivateKey = nil; +static NSString *const kDefaultPemCertChain = nil; +static NSString *const kDefaultOauth2AccessToken = nil; +static const id<GRPCAuthorizationProtocol> kDefaultAuthTokenProvider = nil; +static const GRPCTransportType kDefaultTransportType = GRPCTransportTypeDefault; +static NSString *const kDefaultHostNameOverride = nil; +static const id kDefaultLogContext = nil; +static NSString *kDefaultChannelPoolDomain = nil; +static NSUInteger kDefaultChannelId = 0; + +@implementation GRPCCallOptions { + @protected + NSString *_serverAuthority; + NSTimeInterval _timeout; + NSString *_oauth2AccessToken; + id<GRPCAuthorizationProtocol> _authTokenProvider; + NSDictionary *_initialMetadata; + NSString *_userAgentPrefix; + NSUInteger _responseSizeLimit; + GRPCCompressAlgorithm _compressAlgorithm; + BOOL _enableRetry; + NSTimeInterval _keepaliveInterval; + NSTimeInterval _keepaliveTimeout; + NSTimeInterval _connectMinTimeout; + NSTimeInterval _connectInitialBackoff; + NSTimeInterval _connectMaxBackoff; + NSDictionary *_additionalChannelArgs; + NSString *_pemRootCert; + NSString *_pemPrivateKey; + NSString *_pemCertChain; + GRPCTransportType _transportType; + NSString *_hostNameOverride; + id _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 compressAlgorithm = _compressAlgorithm; +@synthesize enableRetry = _enableRetry; +@synthesize keepaliveInterval = _keepaliveInterval; +@synthesize keepaliveTimeout = _keepaliveTimeout; +@synthesize connectMinTimeout = _connectMinTimeout; +@synthesize connectInitialBackoff = _connectInitialBackoff; +@synthesize connectMaxBackoff = _connectMaxBackoff; +@synthesize additionalChannelArgs = _additionalChannelArgs; +@synthesize pemRootCert = _pemRootCert; +@synthesize pemPrivateKey = _pemPrivateKey; +@synthesize pemCertChain = _pemCertChain; +@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 + compressAlgorithm:kDefaultCompressAlgorithm + enableRetry:kDefaultEnableRetry + keepaliveInterval:kDefaultKeepaliveInterval + keepaliveTimeout:kDefaultKeepaliveTimeout + connectMinTimeout:kDefaultConnectMinTimeout + connectInitialBackoff:kDefaultConnectInitialBackoff + connectMaxBackoff:kDefaultConnectMaxBackoff + additionalChannelArgs:kDefaultAdditionalChannelArgs + pemRootCert:kDefaultPemRootCert + pemPrivateKey:kDefaultPemPrivateKey + pemCertChain:kDefaultPemCertChain + 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 + compressAlgorithm:(GRPCCompressAlgorithm)compressAlgorithm + enableRetry:(BOOL)enableRetry + keepaliveInterval:(NSTimeInterval)keepaliveInterval + keepaliveTimeout:(NSTimeInterval)keepaliveTimeout + connectMinTimeout:(NSTimeInterval)connectMinTimeout + connectInitialBackoff:(NSTimeInterval)connectInitialBackoff + connectMaxBackoff:(NSTimeInterval)connectMaxBackoff + additionalChannelArgs:(NSDictionary *)additionalChannelArgs + pemRootCert:(NSString *)pemRootCert + pemPrivateKey:(NSString *)pemPrivateKey + pemCertChain:(NSString *)pemCertChain + transportType:(GRPCTransportType)transportType + hostNameOverride:(NSString *)hostNameOverride + logContext:(id)logContext + channelPoolDomain:(NSString *)channelPoolDomain + channelId:(NSUInteger)channelId { + if ((self = [super init])) { + _serverAuthority = serverAuthority; + _timeout = timeout; + _oauth2AccessToken = oauth2AccessToken; + _authTokenProvider = authTokenProvider; + _initialMetadata = initialMetadata; + _userAgentPrefix = userAgentPrefix; + _responseSizeLimit = responseSizeLimit; + _compressAlgorithm = compressAlgorithm; + _enableRetry = enableRetry; + _keepaliveInterval = keepaliveInterval; + _keepaliveTimeout = keepaliveTimeout; + _connectMinTimeout = connectMinTimeout; + _connectInitialBackoff = connectInitialBackoff; + _connectMaxBackoff = connectMaxBackoff; + _additionalChannelArgs = additionalChannelArgs; + _pemRootCert = pemRootCert; + _pemPrivateKey = pemPrivateKey; + _pemCertChain = pemCertChain; + _transportType = transportType; + _hostNameOverride = hostNameOverride; + _logContext = logContext; + _channelPoolDomain = channelPoolDomain; + _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 + compressAlgorithm:_compressAlgorithm + enableRetry:_enableRetry + keepaliveInterval:_keepaliveInterval + keepaliveTimeout:_keepaliveTimeout + connectMinTimeout:_connectMinTimeout + connectInitialBackoff:_connectInitialBackoff + connectMaxBackoff:_connectMaxBackoff + additionalChannelArgs:[_additionalChannelArgs copy] + pemRootCert:_pemRootCert + pemPrivateKey:_pemPrivateKey + pemCertChain:_pemCertChain + 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 + compressAlgorithm:_compressAlgorithm + enableRetry:_enableRetry + keepaliveInterval:_keepaliveInterval + keepaliveTimeout:_keepaliveTimeout + connectMinTimeout:_connectMinTimeout + connectInitialBackoff:_connectInitialBackoff + connectMaxBackoff:_connectMaxBackoff + additionalChannelArgs:[_additionalChannelArgs copy] + pemRootCert:_pemRootCert + pemPrivateKey:_pemPrivateKey + pemCertChain:_pemCertChain + transportType:_transportType + hostNameOverride:_hostNameOverride + logContext:_logContext + channelPoolDomain:_channelPoolDomain + channelId:_channelId]; + return newOptions; +} + +@end + +@implementation GRPCMutableCallOptions + +@dynamic serverAuthority; +@dynamic timeout; +@dynamic oauth2AccessToken; +@dynamic authTokenProvider; +@dynamic initialMetadata; +@dynamic userAgentPrefix; +@dynamic responseSizeLimit; +@dynamic compressAlgorithm; +@dynamic enableRetry; +@dynamic keepaliveInterval; +@dynamic keepaliveTimeout; +@dynamic connectMinTimeout; +@dynamic connectInitialBackoff; +@dynamic connectMaxBackoff; +@dynamic additionalChannelArgs; +@dynamic pemRootCert; +@dynamic pemPrivateKey; +@dynamic pemCertChain; +@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 + compressAlgorithm:kDefaultCompressAlgorithm + enableRetry:kDefaultEnableRetry + keepaliveInterval:kDefaultKeepaliveInterval + keepaliveTimeout:kDefaultKeepaliveTimeout + connectMinTimeout:kDefaultConnectMinTimeout + connectInitialBackoff:kDefaultConnectInitialBackoff + connectMaxBackoff:kDefaultConnectMaxBackoff + additionalChannelArgs:kDefaultAdditionalChannelArgs + pemRootCert:kDefaultPemRootCert + pemPrivateKey:kDefaultPemPrivateKey + pemCertChain:kDefaultPemCertChain + 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 + compressAlgorithm:_compressAlgorithm + enableRetry:_enableRetry + keepaliveInterval:_keepaliveInterval + keepaliveTimeout:_keepaliveTimeout + connectMinTimeout:_connectMinTimeout + connectInitialBackoff:_connectInitialBackoff + connectMaxBackoff:_connectMaxBackoff + additionalChannelArgs:[_additionalChannelArgs copy] + pemRootCert:_pemRootCert + pemPrivateKey:_pemPrivateKey + pemCertChain:_pemCertChain + 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 + compressAlgorithm:_compressAlgorithm + enableRetry:_enableRetry + keepaliveInterval:_keepaliveInterval + keepaliveTimeout:_keepaliveTimeout + connectMinTimeout:_connectMinTimeout + connectInitialBackoff:_connectInitialBackoff + connectMaxBackoff:_connectMaxBackoff + additionalChannelArgs:[_additionalChannelArgs copy] + pemRootCert:_pemRootCert + pemPrivateKey:_pemPrivateKey + pemCertChain:_pemCertChain + transportType:_transportType + hostNameOverride:_hostNameOverride + logContext:_logContext + channelPoolDomain:_channelPoolDomain + channelId:_channelId]; + return newOptions; +} + +- (void)setServerAuthority:(NSString *)serverAuthority { + _serverAuthority = serverAuthority; +} + +- (void)setTimeout:(NSTimeInterval)timeout { + _timeout = timeout; +} + +- (void)setOauth2AccessToken:(NSString *)oauth2AccessToken { + _oauth2AccessToken = oauth2AccessToken; +} + +- (void)setAuthTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider { + _authTokenProvider = authTokenProvider; +} + +- (void)setInitialMetadata:(NSDictionary *)initialMetadata { + _initialMetadata = initialMetadata; +} + +- (void)setUserAgentPrefix:(NSString *)userAgentPrefix { + _userAgentPrefix = userAgentPrefix; +} + +- (void)setResponseSizeLimit:(NSUInteger)responseSizeLimit { + _responseSizeLimit = responseSizeLimit; +} + +- (void)setCompressAlgorithm:(GRPCCompressAlgorithm)compressAlgorithm { + _compressAlgorithm = compressAlgorithm; +} + +- (void)setEnableRetry:(BOOL)enableRetry { + _enableRetry = enableRetry; +} + +- (void)setKeepaliveInterval:(NSTimeInterval)keepaliveInterval { + _keepaliveInterval = keepaliveInterval; +} + +- (void)setKeepaliveTimeout:(NSTimeInterval)keepaliveTimeout { + _keepaliveTimeout = keepaliveTimeout; +} + +- (void)setConnectMinTimeout:(NSTimeInterval)connectMinTimeout { + _connectMinTimeout = connectMinTimeout; +} + +- (void)setConnectInitialBackoff:(NSTimeInterval)connectInitialBackoff { + _connectInitialBackoff = connectInitialBackoff; +} + +- (void)setConnectMaxBackoff:(NSTimeInterval)connectMaxBackoff { + _connectMaxBackoff = connectMaxBackoff; +} + +- (void)setAdditionalChannelArgs:(NSDictionary *)additionalChannelArgs { + _additionalChannelArgs = additionalChannelArgs; +} + +- (void)setPemRootCert:(NSString *)pemRootCert { + _pemRootCert = pemRootCert; +} + +- (void)setPemPrivateKey:(NSString *)pemPrivateKey { + _pemPrivateKey = pemPrivateKey; +} + +- (void)setPemCertChain:(NSString *)pemCertChain { + _pemCertChain = pemCertChain; +} + +- (void)setTransportType:(GRPCTransportType)transportType { + _transportType = transportType; +} + +- (void)setHostNameOverride:(NSString *)hostNameOverride { + _hostNameOverride = hostNameOverride; +} + +- (void)setLogContext:(id)logContext { + _logContext = logContext; +} + +- (void)setChannelPoolDomain:(NSString *)channelPoolDomain { + _channelPoolDomain = channelPoolDomain; +} + +- (void)setChannelId:(NSUInteger)channelId { + _channelId = channelId; +} + +@end |