diff --git a/src/objective-c/ProtoRPC/ProtoRPC.h b/src/objective-c/ProtoRPC/ProtoRPC.h index c91adc7b7cd..849351720f1 100644 --- a/src/objective-c/ProtoRPC/ProtoRPC.h +++ b/src/objective-c/ProtoRPC/ProtoRPC.h @@ -70,6 +70,30 @@ NS_ASSUME_NONNULL_BEGIN @end +/** + * A convenience class of objects that act as response handlers of calls. Issues + * response to a single handler when the response is completed. + */ +@interface GRPCUnaryResponseHandler : NSObject + +/** + * Creates a responsehandler object with a unary call handler. + * + * responseHandler: The unary handler to be called when the call is completed. + * responseDispatchQueue: the dispatch queue on which the response handler + * should be issued. + */ +- (nullable instancetype)initWithResponseHandler:(void (^)(GPBMessage *, NSError *))handler + responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue; + +/** Response headers received during the call. */ +@property(readonly, nullable) NSDictionary *responseHeaders; + +/** Response trailers received during the call. */ +@property(readonly, nullable) NSDictionary *responseTrailers; + +@end + /** A unary-request RPC call with Protobuf. */ @interface GRPCUnaryProtoCall : NSObject diff --git a/src/objective-c/ProtoRPC/ProtoRPC.m b/src/objective-c/ProtoRPC/ProtoRPC.m index 9c0eb13d9d6..bd0e7d1d9c6 100644 --- a/src/objective-c/ProtoRPC/ProtoRPC.m +++ b/src/objective-c/ProtoRPC/ProtoRPC.m @@ -27,6 +27,48 @@ #import #import +@implementation GRPCUnaryResponseHandler { + void (^_responseHandler)(GPBMessage *, NSError *); + dispatch_queue_t _responseDispatchQueue; + + GPBMessage *_message; +} + +- (nullable instancetype)initWithResponseHandler:(void (^)(GPBMessage *, NSError *))handler + responseDispatchQueue:(dispatch_queue_t)dispatchQueue { + if ((self = [super init])) { + _responseHandler = handler; + _responseDispatchQueue = dispatchQueue; + } + return self; +} + +// Implements GRPCProtoResponseHandler +- (dispatch_queue_t)dispatchQueue { + return _responseDispatchQueue; +} + +- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata { + _responseHeaders = [initialMetadata copy]; +} + +- (void)didReceiveProtoMessage:(GPBMessage *)message { + _message = message; +} + +- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { + _responseTrailers = [trailingMetadata copy]; + GPBMessage *message = _message; + _message = nil; + _responseHandler(message, error); +} + +// Intentional no-op since flow control is N/A in a unary call +- (void)didWriteMessage { +} + +@end + @implementation GRPCUnaryProtoCall { GRPCStreamingProtoCall *_call; GPBMessage *_message; diff --git a/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m b/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m index fe04c6bee24..2861311b229 100644 --- a/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m +++ b/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m @@ -60,4 +60,8 @@ static int32_t kRemoteInteropServerOverhead = 12; return kRemoteInteropServerOverhead; // bytes } ++ (BOOL)isRemoteTest { + return YES; +} + @end diff --git a/src/objective-c/tests/InteropTests/InteropTests.m b/src/objective-c/tests/InteropTests/InteropTests.m index 9a10cccecd4..55c2ea7753c 100644 --- a/src/objective-c/tests/InteropTests/InteropTests.m +++ b/src/objective-c/tests/InteropTests/InteropTests.m @@ -563,6 +563,47 @@ static dispatch_once_t initGlobalInterceptorFactory; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testUnaryResponseHandler { + XCTAssertNotNil([[self class] host]); + // The test does not work on a remote server since it does not echo a trailer + if ([[self class] isRemoteTest]) return; + __weak XCTestExpectation *expectComplete = [self expectationWithDescription:@"received complete"]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.responseType = RMTPayloadType_Compressable; + request.responseSize = 314159; + request.payload.body = [NSMutableData dataWithLength:271828]; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + // For backwards compatibility + options.transportType = [[self class] transportType]; + options.transport = [[self class] transport]; + options.PEMRootCertificates = [[self class] PEMRootCertificates]; + options.hostNameOverride = [[self class] hostNameOverride]; + const unsigned char raw_bytes[] = {1, 2, 3, 4}; + NSData *trailer_data = [NSData dataWithBytes:raw_bytes length:sizeof(raw_bytes)]; + options.initialMetadata = @{ + @"x-grpc-test-echo-trailing-bin" : trailer_data, + @"x-grpc-test-echo-initial" : @"test-header" + }; + + __block GRPCUnaryResponseHandler *handler = [[GRPCUnaryResponseHandler alloc] + initWithResponseHandler:^(GPBMessage *response, NSError *error) { + XCTAssertNil(error, @"Unexpected error: %@", error); + RMTSimpleResponse *expectedResponse = [RMTSimpleResponse message]; + expectedResponse.payload.type = RMTPayloadType_Compressable; + expectedResponse.payload.body = [NSMutableData dataWithLength:314159]; + XCTAssertEqualObjects(response, expectedResponse); + XCTAssertEqualObjects(handler.responseHeaders[@"x-grpc-test-echo-initial"], @"test-header"); + XCTAssertEqualObjects(handler.responseTrailers[@"x-grpc-test-echo-trailing-bin"], + trailer_data); + [expectComplete fulfill]; + } + responseDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)]; + [[_service unaryCallWithMessage:request responseHandler:handler callOptions:options] start]; + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (void)testLargeUnaryRPCWithV2API { XCTAssertNotNil([[self class] host]); __weak XCTestExpectation *expectReceive = diff --git a/src/objective-c/tests/InteropTests/InteropTestsRemote.m b/src/objective-c/tests/InteropTests/InteropTestsRemote.m index 2dd8f0aed89..6f9cca10e7e 100644 --- a/src/objective-c/tests/InteropTests/InteropTestsRemote.m +++ b/src/objective-c/tests/InteropTests/InteropTestsRemote.m @@ -57,4 +57,8 @@ static int32_t kRemoteInteropServerOverhead = 12; return GRPCTransportTypeChttp2BoringSSL; } ++ (BOOL)isRemoteTest { + return YES; +} + @end diff --git a/src/objective-c/tests/TestBase.h b/src/objective-c/tests/TestBase.h index d6a6a56c69f..dff2001cde7 100644 --- a/src/objective-c/tests/TestBase.h +++ b/src/objective-c/tests/TestBase.h @@ -57,9 +57,13 @@ + (NSString *)PEMRootCertificates; /** - * The root certificates to be used. The base implementation returns nil. Subclasses should override - * to appropriate settings. + * The host name to be used for TLS verification in the tests. */ + (NSString *)hostNameOverride; +/** + * Indication of whether the test is connecting to a remote server. + */ ++ (BOOL)isRemoteTest; + @end diff --git a/src/objective-c/tests/TestBase.m b/src/objective-c/tests/TestBase.m index aa143389751..5f90c642c5e 100644 --- a/src/objective-c/tests/TestBase.m +++ b/src/objective-c/tests/TestBase.m @@ -47,4 +47,8 @@ return nil; } ++ (BOOL)isRemoteTest { + return NO; +} + @end