From 35a1f0a9634baea573b343edf77e63f09dbff039 Mon Sep 17 00:00:00 2001 From: Muxi Yan Date: Mon, 2 Sep 2019 21:10:11 -0700 Subject: [PATCH] Add transport interceptor --- src/objective-c/GRPCClient/GRPCCall.m | 11 + src/objective-c/GRPCClient/GRPCTransport.h | 6 +- .../GRPCCoreCronet/GRPCCoreCronetFactory.m | 4 + .../private/GRPCCore/GRPCCoreFactory.m | 8 + .../tests/Tests.xcodeproj/project.pbxproj | 4 + .../tests/UnitTests/TransportTests.m | 232 ++++++++++++++++++ 6 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 src/objective-c/tests/UnitTests/TransportTests.m diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index 87c2768187e..0cfa38aa3e5 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -22,6 +22,7 @@ #import "GRPCCallOptions.h" #import "GRPCInterceptor.h" +#import "GRPCTransport.h" #import "private/GRPCTransport+Private.h" NSString *const kGRPCHeadersKey = @"io.grpc.HeadersKey"; @@ -183,6 +184,16 @@ NSString *const kGRPCErrorDomain = @"io.grpc"; if (globalInterceptorFactory != nil) { [interceptorFactories addObject:globalInterceptorFactory]; } + if (_actualCallOptions.transport != NULL) { + id transportFactory = [[GRPCTransportRegistry sharedInstance] + getTransportFactoryWithID:_actualCallOptions.transport]; + + NSArray> *transportInterceptorFactories = + transportFactory.transportInterceptorFactories; + if (transportInterceptorFactories != nil) { + [interceptorFactories addObjectsFromArray:transportInterceptorFactories]; + } + } // continuously create interceptor until one is successfully created while (_firstInterceptor == nil) { if (interceptorFactories.count == 0) { diff --git a/src/objective-c/GRPCClient/GRPCTransport.h b/src/objective-c/GRPCClient/GRPCTransport.h index a29dbd20e4d..400cf46e705 100644 --- a/src/objective-c/GRPCClient/GRPCTransport.h +++ b/src/objective-c/GRPCClient/GRPCTransport.h @@ -48,11 +48,15 @@ NSUInteger TransportIDHash(GRPCTransportID); @class GRPCCallOptions; @class GRPCTransport; -/** The factory method to create a transport. */ +/** The factory to create a transport. */ @protocol GRPCTransportFactory +/** Create a transport implementation. */ - (GRPCTransport *)createTransportWithManager:(GRPCTransportManager *)transportManager; +/** Get a list of factories for transport inteceptors. */ +@property(nonatomic, readonly) NSArray> *transportInterceptorFactories; + @end /** The registry of transport implementations. */ diff --git a/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCoreCronetFactory.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCoreCronetFactory.m index 2205d167d82..a595f4b7e63 100644 --- a/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCoreCronetFactory.m +++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCoreCronetFactory.m @@ -47,6 +47,10 @@ static dispatch_once_t gInitGRPCCoreCronetFactory; return [[GRPCCall2Internal alloc] initWithTransportManager:transportManager]; } +- (NSArray> *)transportInterceptorFactories { + return nil; +} + - (id)createCoreChannelFactoryWithCallOptions:(GRPCCallOptions *)callOptions { return [GRPCCronetChannelFactory sharedInstance]; } diff --git a/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreFactory.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreFactory.m index 928625f1275..c3cefb7b649 100644 --- a/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreFactory.m +++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreFactory.m @@ -48,6 +48,10 @@ static dispatch_once_t gInitGRPCCoreInsecureFactory; return [[GRPCCall2Internal alloc] initWithTransportManager:transportManager]; } +- (NSArray> *)transportInterceptorFactories { + return nil; +} + - (id)createCoreChannelFactoryWithCallOptions:(GRPCCallOptions *)callOptions { NSError *error; id factory = @@ -83,6 +87,10 @@ static dispatch_once_t gInitGRPCCoreInsecureFactory; return [[GRPCCall2Internal alloc] initWithTransportManager:transportManager]; } +- (NSArray> *)transportInterceptorFactories { + return nil; +} + - (id)createCoreChannelFactoryWithCallOptions:(GRPCCallOptions *)callOptions { return [GRPCInsecureChannelFactory sharedInstance]; } diff --git a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj index 2ce32d3916d..927a7b64c3e 100644 --- a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj +++ b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 5E7F489022778C95006656AD /* RxLibraryUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E7F488A22778B5D006656AD /* RxLibraryUnitTests.m */; }; 5E9F1C332321AB1700837469 /* TransportRegistryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E9F1C322321AB1700837469 /* TransportRegistryTests.m */; }; 5E9F1C352321C9B200837469 /* TransportRegistryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E9F1C342321C9B200837469 /* TransportRegistryTests.m */; }; + 5E9F1C59232302E200837469 /* TransportTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E9F1C58232302E200837469 /* TransportTests.m */; }; 5EA4770322736178000F72FC /* InteropTestsLocalSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */; }; 5EA477042273617B000F72FC /* InteropTestsLocalCleartext.m in Sources */ = {isa = PBXBuildFile; fileRef = 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */; }; 5EA4770522736AC4000F72FC /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; }; @@ -125,6 +126,7 @@ 5E7F488A22778B5D006656AD /* RxLibraryUnitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RxLibraryUnitTests.m; sourceTree = ""; }; 5E9F1C322321AB1700837469 /* TransportRegistryTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TransportRegistryTests.m; sourceTree = ""; }; 5E9F1C342321C9B200837469 /* TransportRegistryTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TransportRegistryTests.m; sourceTree = ""; }; + 5E9F1C58232302E200837469 /* TransportTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TransportTests.m; sourceTree = ""; }; 5EA476F42272816A000F72FC /* InteropTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InteropTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 5EA908CF4CDA4CE218352A06 /* Pods-InteropTestsLocalSSLCFStream.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSLCFStream.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSLCFStream/Pods-InteropTestsLocalSSLCFStream.release.xcconfig"; sourceTree = ""; }; 5EAD6D261E27047400002378 /* CronetUnitTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = CronetUnitTests.mm; path = CronetTests/CronetUnitTests.mm; sourceTree = SOURCE_ROOT; }; @@ -366,6 +368,7 @@ 5E0282E7215AA697007AC99D /* UnitTests */ = { isa = PBXGroup; children = ( + 5E9F1C58232302E200837469 /* TransportTests.m */, 5E9F1C322321AB1700837469 /* TransportRegistryTests.m */, 5E7F488A22778B5D006656AD /* RxLibraryUnitTests.m */, 5E7F488622778AEA006656AD /* GRPCClientTests.m */, @@ -874,6 +877,7 @@ 5E0282E9215AA697007AC99D /* NSErrorUnitTests.m in Sources */, 5E7F4880227782C1006656AD /* APIv2Tests.m in Sources */, 5E7F487D22778256006656AD /* ChannelPoolTest.m in Sources */, + 5E9F1C59232302E200837469 /* TransportTests.m in Sources */, 5E9F1C332321AB1700837469 /* TransportRegistryTests.m in Sources */, 5E7F488722778AEA006656AD /* GRPCClientTests.m in Sources */, 5E7F487E22778256006656AD /* ChannelTests.m in Sources */, diff --git a/src/objective-c/tests/UnitTests/TransportTests.m b/src/objective-c/tests/UnitTests/TransportTests.m new file mode 100644 index 00000000000..5f4db26258e --- /dev/null +++ b/src/objective-c/tests/UnitTests/TransportTests.m @@ -0,0 +1,232 @@ +/* + * + * Copyright 2019 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 +#import +#import +#import + +#define TEST_TIMEOUT (8.0) + +static NSString *const kRemoteHost = @"grpc-test.sandbox.googleapis.com:443"; + +static const GRPCTransportID kFakeTransportID = "io.grpc.transport.unittest.fake"; + +@class GRPCFakeTransportFactory; +dispatch_once_t initFakeTransportFactory; +static GRPCFakeTransportFactory *fakeTransportFactory; + +@interface GRPCFakeTransportFactory : NSObject + +@property(atomic) GRPCTransport *nextTransportInstance; +- (void)setTransportInterceptorFactories:(NSArray> *)factories; + +@end + +@implementation GRPCFakeTransportFactory { + NSArray> *_interceptorFactories; +} + ++ (instancetype)sharedInstance { + dispatch_once(&initFakeTransportFactory, ^{ + fakeTransportFactory = [[GRPCFakeTransportFactory alloc] init]; + }); + return fakeTransportFactory; +} + ++ (void)load { + [[GRPCTransportRegistry sharedInstance] registerTransportWithID:kFakeTransportID + factory:[self sharedInstance]]; +} + +- (GRPCTransport *)createTransportWithManager:(GRPCTransportManager *)transportManager { + return _nextTransportInstance; +} + +- (void)setTransportInterceptorFactories:(NSArray> *)factories { + _interceptorFactories = [NSArray arrayWithArray:factories]; +} + +- (NSArray> *)transportInterceptorFactories { + return _interceptorFactories; +} + +@end + +@interface DummyInterceptor : GRPCInterceptor + +@property(atomic) BOOL hit; + +@end + +@implementation DummyInterceptor { + GRPCInterceptorManager *_manager; +} + +- (instancetype)initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager + dispatchQueue:(dispatch_queue_t)dispatchQueue { + if (dispatchQueue == nil) { + dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + } + if ((self = [super initWithInterceptorManager:interceptorManager dispatchQueue:dispatchQueue])) { + _manager = interceptorManager; + } + return self; +} + +- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions + callOptions:(GRPCCallOptions *)callOptions { + self.hit = YES; + [_manager + forwardPreviousInterceptorCloseWithTrailingMetadata:nil + error:[NSError + errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeCancelled + userInfo:@{ + NSLocalizedDescriptionKey : + @"Canceled." + }]]; + [_manager shutDown]; +} + +@end + +@interface DummyInterceptorFactory : NSObject + ++ (instancetype)sharedInstance; + +@end + +static DummyInterceptorFactory *dummyInterceptorFactory; +static dispatch_once_t initDummyInterceptorFactory; + +@implementation DummyInterceptorFactory + ++ (instancetype)sharedInstance { + dispatch_once(&initDummyInterceptorFactory, ^{ + dummyInterceptorFactory = [[DummyInterceptorFactory alloc] init]; + }); + return dummyInterceptorFactory; +} + +- (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager { + return [[DummyInterceptor alloc] + initWithInterceptorManager:interceptorManager + dispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)]; +} + +@end + +@interface TestsBlockCallbacks : NSObject + +- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback + dataCallback:(void (^)(id))dataCallback + closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback + writeMessageCallback:(void (^)(void))writeMessageCallback; + +@end + +@implementation TestsBlockCallbacks { + void (^_initialMetadataCallback)(NSDictionary *); + void (^_dataCallback)(id); + void (^_closeCallback)(NSDictionary *, NSError *); + void (^_writeMessageCallback)(void); + dispatch_queue_t _dispatchQueue; +} + +- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback + dataCallback:(void (^)(id))dataCallback + closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback + writeMessageCallback:(void (^)(void))writeMessageCallback { + if ((self = [super init])) { + _initialMetadataCallback = initialMetadataCallback; + _dataCallback = dataCallback; + _closeCallback = closeCallback; + _writeMessageCallback = writeMessageCallback; + _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL); + } + return self; +} + +- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata { + if (_initialMetadataCallback) { + _initialMetadataCallback(initialMetadata); + } +} + +- (void)didReceiveProtoMessage:(id)message { + if (_dataCallback) { + _dataCallback(message); + } +} + +- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { + if (_closeCallback) { + _closeCallback(trailingMetadata, error); + } +} + +- (void)didWriteMessage { + if (_writeMessageCallback) { + _writeMessageCallback(); + } +} + +- (dispatch_queue_t)dispatchQueue { + return _dispatchQueue; +} +@end + +@interface TransportTests : XCTestCase + +@end + +@implementation TransportTests + +- (void)testTransportInterceptors { + __weak XCTestExpectation *expectComplete = + [self expectationWithDescription:@"Expect call complete"]; + [GRPCFakeTransportFactory sharedInstance].nextTransportInstance = nil; + + [[GRPCFakeTransportFactory sharedInstance] + setTransportInterceptorFactories:@[ [DummyInterceptorFactory sharedInstance] ]]; + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kRemoteHost + path:@"/UnaryCall" + safety:GRPCCallSafetyDefault]; + GRPCMutableCallOptions *callOptions = [[GRPCMutableCallOptions alloc] init]; + callOptions.transport = kFakeTransportID; + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + responseHandler:[[TestsBlockCallbacks alloc] + initWithInitialMetadataCallback:nil + dataCallback:nil + closeCallback:^(NSDictionary *trailingMetadata, + NSError *error) { + XCTAssertNotNil(error); + XCTAssertEqual(error.code, + GRPCErrorCodeCancelled); + [expectComplete fulfill]; + } + writeMessageCallback:nil] + callOptions:callOptions]; + [call start]; + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + +@end