diff options
author | Craig Tiller <craig.tiller@gmail.com> | 2015-09-03 16:36:23 -0700 |
---|---|---|
committer | Craig Tiller <craig.tiller@gmail.com> | 2015-09-03 16:36:23 -0700 |
commit | fa62267c78a86f6fee39477d6424e20ed5245fa5 (patch) | |
tree | dbf14d185bf8620daa782c2654711f78e116e55a /src | |
parent | b5391e1ae68fefd2caa72ca694152da48282174e (diff) | |
parent | 51bfda063a903c15429cede9ab9dab4c355b7c9a (diff) |
Merge branch 'release-0_11' of github.com:grpc/grpc into insert-branch-name
Diffstat (limited to 'src')
-rw-r--r-- | src/core/census/grpc_filter.c | 1 | ||||
-rw-r--r-- | src/core/transport/chttp2_transport.c | 4 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/GRPCCall.h | 107 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/GRPCCall.m | 21 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCRequestHeaders.h | 52 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCRequestHeaders.m | 119 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCWrappedCall.h | 3 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCWrappedCall.m | 2 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/NSDictionary+GRPC.m | 47 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/NSError+GRPC.h | 23 | ||||
-rw-r--r-- | src/objective-c/ProtoRPC/ProtoRPC.m | 33 | ||||
-rw-r--r-- | src/objective-c/examples/RemoteTestClient/RemoteTest.podspec | 4 | ||||
-rw-r--r-- | src/objective-c/examples/SwiftSample/Bridging-Header.h | 1 | ||||
-rw-r--r-- | src/objective-c/examples/SwiftSample/Podfile | 1 | ||||
-rw-r--r-- | src/objective-c/examples/SwiftSample/ViewController.swift | 42 |
15 files changed, 366 insertions, 94 deletions
diff --git a/src/core/census/grpc_filter.c b/src/core/census/grpc_filter.c index e01c9a2ad4..8b6ba1d472 100644 --- a/src/core/census/grpc_filter.c +++ b/src/core/census/grpc_filter.c @@ -36,7 +36,6 @@ #include <stdio.h> #include <string.h> -#include "include/grpc/census.h" #include "src/core/channel/channel_stack.h" #include "src/core/channel/noop_filter.h" #include "src/core/statistics/census_interface.h" diff --git a/src/core/transport/chttp2_transport.c b/src/core/transport/chttp2_transport.c index aa6a860c67..9e3d7dd551 100644 --- a/src/core/transport/chttp2_transport.c +++ b/src/core/transport/chttp2_transport.c @@ -695,9 +695,9 @@ static void perform_stream_op_locked( } grpc_chttp2_incoming_metadata_live_op_buffer_end( &stream_global->outstanding_metadata); + grpc_chttp2_list_add_read_write_state_changed(transport_global, + stream_global); if (stream_global->id != 0) { - grpc_chttp2_list_add_read_write_state_changed(transport_global, - stream_global); grpc_chttp2_list_add_writable_stream(transport_global, stream_global); } } diff --git a/src/objective-c/GRPCClient/GRPCCall.h b/src/objective-c/GRPCClient/GRPCCall.h index 4eda499b1a..35f7e16af7 100644 --- a/src/objective-c/GRPCClient/GRPCCall.h +++ b/src/objective-c/GRPCClient/GRPCCall.h @@ -48,11 +48,112 @@ #import <Foundation/Foundation.h> #import <RxLibrary/GRXWriter.h> +#pragma mark gRPC errors + +// Domain of NSError objects produced by gRPC. +extern NSString *const kGRPCErrorDomain; + +// gRPC error codes. +// Note that a few of these are never produced by the gRPC libraries, but are of general utility for +// server applications to produce. +typedef NS_ENUM(NSUInteger, GRPCErrorCode) { + // The operation was cancelled (typically by the caller). + GRPCErrorCodeCancelled = 1, + + // Unknown error. Errors raised by APIs that do not return enough error information may be + // converted to this error. + GRPCErrorCodeUnknown = 2, + + // The client specified an invalid argument. Note that this differs from FAILED_PRECONDITION. + // INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the + // server (e.g., a malformed file name). + GRPCErrorCodeInvalidArgument = 3, + + // Deadline expired before operation could complete. For operations that change the state of the + // server, this error may be returned even if the operation has completed successfully. For + // example, a successful response from the server could have been delayed long enough for the + // deadline to expire. + GRPCErrorCodeDeadlineExceeded = 4, + + // Some requested entity (e.g., file or directory) was not found. + GRPCErrorCodeNotFound = 5, + + // Some entity that we attempted to create (e.g., file or directory) already exists. + GRPCErrorCodeAlreadyExists = 6, + + // The caller does not have permission to execute the specified operation. PERMISSION_DENIED isn't + // used for rejections caused by exhausting some resource (RESOURCE_EXHAUSTED is used instead for + // those errors). PERMISSION_DENIED doesn't indicate a failure to identify the caller + // (UNAUTHENTICATED is used instead for those errors). + GRPCErrorCodePermissionDenied = 7, + + // The request does not have valid authentication credentials for the operation (e.g. the caller's + // identity can't be verified). + GRPCErrorCodeUnauthenticated = 16, + + // Some resource has been exhausted, perhaps a per-user quota. + GRPCErrorCodeResourceExhausted = 8, + + // The RPC was rejected because the server is not in a state required for the procedure's + // execution. For example, a directory to be deleted may be non-empty, etc. + // The client should not retry until the server state has been explicitly fixed (e.g. by + // performing another RPC). The details depend on the service being called, and should be found in + // the NSError's userInfo. + GRPCErrorCodeFailedPrecondition = 9, + + // The RPC was aborted, typically due to a concurrency issue like sequencer check failures, + // transaction aborts, etc. The client should retry at a higher-level (e.g., restarting a read- + // modify-write sequence). + GRPCErrorCodeAborted = 10, + + // The RPC was attempted past the valid range. E.g., enumerating past the end of a list. + // Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system state + // changes. For example, an RPC to get elements of a list will generate INVALID_ARGUMENT if asked + // to return the element at a negative index, but it will generate OUT_OF_RANGE if asked to return + // the element at an index past the current size of the list. + GRPCErrorCodeOutOfRange = 11, + + // The procedure is not implemented or not supported/enabled in this server. + GRPCErrorCodeUnimplemented = 12, + + // Internal error. Means some invariant expected by the server application or the gRPC library has + // been broken. + GRPCErrorCodeInternal = 13, + + // The server is currently unavailable. This is most likely a transient condition and may be + // corrected by retrying with a backoff. + GRPCErrorCodeUnavailable = 14, + + // Unrecoverable data loss or corruption. + GRPCErrorCodeDataLoss = 15, +}; + // 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; +#pragma mark GRPCCall + +// The container of the request headers of an RPC conforms to this protocol, which is a subset of +// NSMutableDictionary's interface. It will become a NSMutableDictionary later on. +// The keys of this container are the header names, which per the HTTP standard are case- +// insensitive. They are stored in lowercase (which is how HTTP/2 mandates them on the wire), and +// can only consist of ASCII characters. +// A header value is a NSString object (with only ASCII characters), unless the header name has the +// suffix "-bin", in which case the value has to be a NSData object. +@protocol GRPCRequestHeaders <NSObject> + +@property(nonatomic, readonly) NSUInteger count; + +- (id)objectForKeyedSubscript:(NSString *)key; +- (void)setObject:(id)obj forKeyedSubscript:(NSString *)key; + +- (void)removeAllObjects; +- (void)removeObjectForKey:(NSString *)key; + +@end + // Represents a single gRPC remote call. @interface GRPCCall : GRXWriter @@ -68,10 +169,8 @@ extern id const kGRPCTrailersKey; // // After the call is started, trying to modify this property is an error. // -// For convenience, the property is initialized to an empty NSMutableDictionary, and the setter -// accepts (and copies) both mutable and immutable dictionaries. -- (NSMutableDictionary *)requestHeaders; // nonatomic -- (void)setRequestHeaders:(NSDictionary *)requestHeaders; // nonatomic, copy +// The property is initialized to an empty NSMutableDictionary. +@property(atomic, readonly) id<GRPCRequestHeaders> requestHeaders; // This dictionary is populated with the HTTP headers received from the server. This happens before // any response message is received from the server. It has the same structure as the request diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index ff5d1c5aaf..b6986bf59c 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -37,6 +37,7 @@ #include <grpc/support/time.h> #import <RxLibrary/GRXConcurrentWriteable.h> +#import "private/GRPCRequestHeaders.h" #import "private/GRPCWrappedCall.h" #import "private/NSData+GRPC.h" #import "private/NSDictionary+GRPC.h" @@ -93,7 +94,7 @@ NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; // the response arrives. GRPCCall *_retainSelf; - NSMutableDictionary *_requestHeaders; + GRPCRequestHeaders *_requestHeaders; } @synthesize state = _state; @@ -124,21 +125,11 @@ NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; _requestWriter = requestWriter; - _requestHeaders = [NSMutableDictionary dictionary]; + _requestHeaders = [[GRPCRequestHeaders alloc] initWithCall:self]; } return self; } -#pragma mark Metadata - -- (NSMutableDictionary *)requestHeaders { - return _requestHeaders; -} - -- (void)setRequestHeaders:(NSDictionary *)requestHeaders { - _requestHeaders = [NSMutableDictionary dictionaryWithDictionary:requestHeaders]; -} - #pragma mark Finish - (void)finishWithError:(NSError *)errorOrNil { @@ -230,10 +221,10 @@ NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; #pragma mark Send headers -- (void)sendHeaders:(NSDictionary *)headers { +- (void)sendHeaders:(id<GRPCRequestHeaders>)headers { // TODO(jcanizales): Add error handlers for async failures - [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc] - initWithMetadata:headers ?: @{} handler:nil]]]; + [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc] initWithMetadata:headers + handler:nil]]]; } #pragma mark GRXWriteable implementation diff --git a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.h b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.h new file mode 100644 index 0000000000..1391b5725f --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.h @@ -0,0 +1,52 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#import <Foundation/Foundation.h> +#include <grpc/grpc.h> + +#import "GRPCCall.h" + +@interface GRPCRequestHeaders : NSObject<GRPCRequestHeaders> + +@property(nonatomic, readonly) NSUInteger count; +@property(nonatomic, readonly) grpc_metadata *grpc_metadataArray; + +- (instancetype)initWithCall:(GRPCCall *)call; + +- (id)objectForKeyedSubscript:(NSString *)key; +- (void)setObject:(id)obj forKeyedSubscript:(NSString *)key; + +- (void)removeAllObjects; +- (void)removeObjectForKey:(NSString *)key; + +@end diff --git a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m new file mode 100644 index 0000000000..761677ce50 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m @@ -0,0 +1,119 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#import "GRPCRequestHeaders.h" + +#import <Foundation/Foundation.h> + +#import "../GRPCCall.h" +#import "NSDictionary+GRPC.h" + +// Used by the setter. +static void CheckIsNonNilASCII(NSString *name, NSString* value) { + if (!value) { + [NSException raise:NSInvalidArgumentException format:@"%@ cannot be nil", name]; + } + if (![value canBeConvertedToEncoding:NSASCIIStringEncoding]) { + [NSException raise:NSInvalidArgumentException + format:@"%@ %@ contains non-ASCII characters", name, value]; + } +} + +// Precondition: key isn't nil. +static void CheckKeyValuePairIsValid(NSString *key, id value) { + if ([key hasSuffix:@"-bin"]) { + if (![value isKindOfClass:NSData.class]) { + [NSException raise:NSInvalidArgumentException + format:@"Expected NSData value for header %@ ending in \"-bin\", " + @"instead got %@", key, value]; + } + } else { + if (![value isKindOfClass:NSString.class]) { + [NSException raise:NSInvalidArgumentException + format:@"Expected NSString value for header %@ not ending in \"-bin\", " + @"instead got %@", key, value]; + } + CheckIsNonNilASCII(@"Text header value", (NSString *)value); + } +} + +@implementation GRPCRequestHeaders { + __weak GRPCCall *_call; + NSMutableDictionary *_delegate; +} + +- (instancetype)initWithCall:(GRPCCall *)call { + if ((self = [super init])) { + _call = call; + _delegate = [NSMutableDictionary dictionary]; + } + return self; +} + +- (void)checkCallIsNotStarted { + if (_call.state != GRXWriterStateNotStarted) { + [NSException raise:@"Invalid modification" + format:@"Cannot modify request headers after call is started"]; + } +} + +- (id)objectForKeyedSubscript:(NSString *)key { + return _delegate[key.lowercaseString]; +} + +- (void)setObject:(id)obj forKeyedSubscript:(NSString *)key { + [self checkCallIsNotStarted]; + CheckIsNonNilASCII(@"Header name", key); + key = key.lowercaseString; + CheckKeyValuePairIsValid(key, obj); + _delegate[key] = obj; +} + +- (void)removeObjectForKey:(NSString *)key { + [self checkCallIsNotStarted]; + [_delegate removeObjectForKey:key.lowercaseString]; +} + +- (void)removeAllObjects { + [self checkCallIsNotStarted]; + [_delegate removeAllObjects]; +} + +- (NSUInteger)count { + return _delegate.count; +} + +- (grpc_metadata *)grpc_metadataArray { + return _delegate.grpc_metadataArray; +} +@end diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h index da11cbb761..4ca2766147 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h @@ -35,6 +35,7 @@ #include <grpc/grpc.h> #import "GRPCChannel.h" +#import "GRPCRequestHeaders.h" @interface GRPCOperation : NSObject @property(nonatomic, readonly) grpc_op op; @@ -44,7 +45,7 @@ @interface GRPCOpSendMetadata : GRPCOperation -- (instancetype)initWithMetadata:(NSDictionary *)metadata +- (instancetype)initWithMetadata:(GRPCRequestHeaders *)metadata handler:(void(^)())handler NS_DESIGNATED_INITIALIZER; @end diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m index fe3d51da53..cea7c479e0 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m @@ -65,7 +65,7 @@ return [self initWithMetadata:nil handler:nil]; } -- (instancetype)initWithMetadata:(NSDictionary *)metadata handler:(void (^)())handler { +- (instancetype)initWithMetadata:(GRPCRequestHeaders *)metadata handler:(void (^)())handler { if (self = [super init]) { _op.op = GRPC_OP_SEND_INITIAL_METADATA; _op.data.send_initial_metadata.count = metadata.count; diff --git a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m b/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m index 99c890e4ee..7477da7619 100644 --- a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m +++ b/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m @@ -40,8 +40,8 @@ @interface NSData (GRPCMetadata) + (instancetype)grpc_dataFromMetadataValue:(grpc_metadata *)metadata; -// Fill a metadata object with the binary value in this NSData and the given key. -- (void)grpc_initMetadata:(grpc_metadata *)metadata withKey:(NSString *)key; +// Fill a metadata object with the binary value in this NSData. +- (void)grpc_initMetadata:(grpc_metadata *)metadata; @end @implementation NSData (GRPCMetadata) @@ -50,9 +50,7 @@ return [self dataWithBytes:metadata->value length:metadata->value_length]; } -- (void)grpc_initMetadata:(grpc_metadata *)metadata withKey:(NSString *)key { - // TODO(jcanizales): Encode Unicode chars as ASCII. - metadata->key = [key stringByAppendingString:@"-bin"].UTF8String; +- (void)grpc_initMetadata:(grpc_metadata *)metadata { metadata->value = self.bytes; metadata->value_length = self.length; } @@ -63,8 +61,8 @@ @interface NSString (GRPCMetadata) + (instancetype)grpc_stringFromMetadataValue:(grpc_metadata *)metadata; -// Fill a metadata object with the textual value in this NSString and the given key. -- (void)grpc_initMetadata:(grpc_metadata *)metadata withKey:(NSString *)key; +// Fill a metadata object with the textual value in this NSString. +- (void)grpc_initMetadata:(grpc_metadata *)metadata; @end @implementation NSString (GRPCMetadata) @@ -74,22 +72,8 @@ encoding:NSASCIIStringEncoding]; } -- (void)grpc_initMetadata:(grpc_metadata *)metadata withKey:(NSString *)key { - if ([key hasSuffix:@"-bin"]) { - // Disallow this, as at best it will confuse the server. If the app really needs to send a - // textual header with a name ending in "-bin", it can be done by removing the suffix and - // encoding the NSString as a NSData object. - // - // Why raise an exception: In the most common case, the developer knows this won't happen in - // their code, so the exception isn't triggered. In the rare cases when the developer can't - // tell, it's easy enough to add a sanitizing filter before the header is set. There, the - // developer can choose whether to drop such a header, or trim its name. Doing either ourselves, - // silently, would be very unintuitive for the user. - [NSException raise:NSInvalidArgumentException - format:@"Metadata keys ending in '-bin' are reserved for NSData values."]; - } - // TODO(jcanizales): Encode Unicode chars as ASCII. - metadata->key = key.UTF8String; +// Precondition: This object contains only ASCII characters. +- (void)grpc_initMetadata:(grpc_metadata *)metadata { metadata->value = self.UTF8String; metadata->value_length = self.length; } @@ -105,8 +89,6 @@ + (instancetype)grpc_dictionaryFromMetadata:(grpc_metadata *)entries count:(size_t)count { NSMutableDictionary *metadata = [NSMutableDictionary dictionaryWithCapacity:count]; for (grpc_metadata *entry = entries; entry < entries + count; entry++) { - // TODO(jcanizales): Verify in a C library test that it's converting header names to lower case - // automatically. NSString *name = [NSString stringWithCString:entry->key encoding:NSASCIIStringEncoding]; if (!name || metadata[name]) { // Log if name is nil? @@ -114,7 +96,6 @@ } id value; if ([name hasSuffix:@"-bin"]) { - name = [name substringToIndex:name.length - 4]; value = [NSData grpc_dataFromMetadataValue:entry]; } else { value = [NSString grpc_stringFromMetadataValue:entry]; @@ -124,19 +105,21 @@ return metadata; } +// Preconditions: All keys are ASCII strings. Keys ending in -bin have NSData values; the others +// have NSString values. - (grpc_metadata *)grpc_metadataArray { grpc_metadata *metadata = gpr_malloc([self count] * sizeof(grpc_metadata)); - int i = 0; - for (id key in self) { + grpc_metadata *current = metadata; + for (NSString* key in self) { id value = self[key]; - grpc_metadata *current = &metadata[i]; - if ([value respondsToSelector:@selector(grpc_initMetadata:withKey:)]) { - [value grpc_initMetadata:current withKey:key]; + current->key = key.UTF8String; + if ([value respondsToSelector:@selector(grpc_initMetadata:)]) { + [value grpc_initMetadata:current]; } else { [NSException raise:NSInvalidArgumentException format:@"Metadata values must be NSString or NSData."]; } - i += 1; + ++current; } return metadata; } diff --git a/src/objective-c/GRPCClient/private/NSError+GRPC.h b/src/objective-c/GRPCClient/private/NSError+GRPC.h index e712791271..f4729dc8a1 100644 --- a/src/objective-c/GRPCClient/private/NSError+GRPC.h +++ b/src/objective-c/GRPCClient/private/NSError+GRPC.h @@ -34,29 +34,6 @@ #import <Foundation/Foundation.h> #include <grpc/grpc.h> -// TODO(jcanizales): Make the domain string public. -extern NSString *const kGRPCErrorDomain; - -// TODO(jcanizales): Make this public and document each code. -typedef NS_ENUM(NSInteger, GRPCErrorCode) { - GRPCErrorCodeCancelled = 1, - GRPCErrorCodeUnknown = 2, - GRPCErrorCodeInvalidArgument = 3, - GRPCErrorCodeDeadlineExceeded = 4, - GRPCErrorCodeNotFound = 5, - GRPCErrorCodeAlreadyExists = 6, - GRPCErrorCodePermissionDenied = 7, - GRPCErrorCodeUnauthenticated = 16, - GRPCErrorCodeResourceExhausted = 8, - GRPCErrorCodeFailedPrecondition = 9, - GRPCErrorCodeAborted = 10, - GRPCErrorCodeOutOfRange = 11, - GRPCErrorCodeUnimplemented = 12, - GRPCErrorCodeInternal = 13, - GRPCErrorCodeUnavailable = 14, - GRPCErrorCodeDataLoss = 15 -}; - @interface NSError (GRPC) // Returns nil if the status code is OK. Otherwise, a NSError whose code is one of |GRPCErrorCode| // and whose domain is |kGRPCErrorDomain|. diff --git a/src/objective-c/ProtoRPC/ProtoRPC.m b/src/objective-c/ProtoRPC/ProtoRPC.m index 889d71a308..9bf66f347a 100644 --- a/src/objective-c/ProtoRPC/ProtoRPC.m +++ b/src/objective-c/ProtoRPC/ProtoRPC.m @@ -37,6 +37,22 @@ #import <RxLibrary/GRXWriteable.h> #import <RxLibrary/GRXWriter+Transformations.h> +static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsingError) { + NSDictionary *info = @{ + NSLocalizedDescriptionKey: @"Unable to parse response from the server", + NSLocalizedRecoverySuggestionErrorKey: @"If this RPC is idempotent, retry " + @"with exponential backoff. Otherwise, query the server status before " + @"retrying.", + NSUnderlyingErrorKey: parsingError, + @"Expected class": expectedClass, + @"Received value": proto, + }; + // TODO(jcanizales): Use kGRPCErrorDomain and GRPCErrorCodeInternal when they're public. + return [NSError errorWithDomain:@"io.grpc" + code:13 + userInfo:info]; +} + @implementation ProtoRPC { id<GRXWriteable> _responseWriteable; } @@ -65,14 +81,25 @@ } // A writer that serializes the proto messages to send. GRXWriter *bytesWriter = [requestsWriter map:^id(GPBMessage *proto) { - // TODO(jcanizales): Fail with an understandable error message if the requestsWriter isn't - // sending GPBMessages. + if (![proto isKindOfClass:GPBMessage.class]) { + [NSException raise:NSInvalidArgumentException + format:@"Request must be a proto message: %@", proto]; + } return [proto data]; }]; if ((self = [super initWithHost:host path:method.HTTPPath requestsWriter:bytesWriter])) { + __weak ProtoRPC *weakSelf = self; + // A writeable that parses the proto messages received. _responseWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { - [responsesWriteable writeValue:[responseClass parseFromData:value error:NULL]]; + // TODO(jcanizales): This is done in the main thread, and needs to happen in another thread. + NSError *error = nil; + id parsed = [responseClass parseFromData:value error:&error]; + if (parsed) { + [responsesWriteable writeValue:parsed]; + } else { + [weakSelf finishWithError:ErrorForBadProto(value, responseClass, error)]; + } } completionHandler:^(NSError *errorOrNil) { [responsesWriteable writesFinishedWithError:errorOrNil]; }]; diff --git a/src/objective-c/examples/RemoteTestClient/RemoteTest.podspec b/src/objective-c/examples/RemoteTestClient/RemoteTest.podspec index dcb0c4e500..d4f8084cb5 100644 --- a/src/objective-c/examples/RemoteTestClient/RemoteTest.podspec +++ b/src/objective-c/examples/RemoteTestClient/RemoteTest.podspec @@ -15,14 +15,14 @@ Pod::Spec.new do |s| ms.source_files = "*.pbobjc.{h,m}" ms.header_mappings_dir = "." ms.requires_arc = false - ms.dependency "Protobuf", "~> 3.0.0-alpha-3" + ms.dependency "Protobuf", "~> 3.0.0-alpha-4" end s.subspec "Services" do |ss| ss.source_files = "*.pbrpc.{h,m}" ss.header_mappings_dir = "." ss.requires_arc = true - ss.dependency "gRPC", "~> 0.5" + ss.dependency "gRPC", "~> 0.7" ss.dependency "#{s.name}/Messages" end end diff --git a/src/objective-c/examples/SwiftSample/Bridging-Header.h b/src/objective-c/examples/SwiftSample/Bridging-Header.h index 33db2dd1cb..65f768a760 100644 --- a/src/objective-c/examples/SwiftSample/Bridging-Header.h +++ b/src/objective-c/examples/SwiftSample/Bridging-Header.h @@ -39,6 +39,7 @@ #import <RxLibrary/GRXWriter+Immediate.h> #import <GRPCClient/GRPCCall.h> #import <ProtoRPC/ProtoMethod.h> +#import <ProtoRPC/ProtoRPC.h> #import <RemoteTest/Test.pbrpc.h> #endif diff --git a/src/objective-c/examples/SwiftSample/Podfile b/src/objective-c/examples/SwiftSample/Podfile index 7b5941eef7..3611b00863 100644 --- a/src/objective-c/examples/SwiftSample/Podfile +++ b/src/objective-c/examples/SwiftSample/Podfile @@ -1,6 +1,7 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' +pod 'Protobuf', :path => "../../../../third_party/protobuf" pod 'gRPC', :path => "../../../.." pod 'RemoteTest', :path => "../RemoteTestClient" diff --git a/src/objective-c/examples/SwiftSample/ViewController.swift b/src/objective-c/examples/SwiftSample/ViewController.swift index e4e7aeae49..76dad9e132 100644 --- a/src/objective-c/examples/SwiftSample/ViewController.swift +++ b/src/objective-c/examples/SwiftSample/ViewController.swift @@ -45,17 +45,37 @@ class ViewController: UIViewController { request.fillUsername = true request.fillOauthScope = true + // Example gRPC call using a generated proto client library: let service = RMTTestService(host: RemoteHost) - service.unaryCallWithRequest(request) { (response: RMTSimpleResponse?, error: NSError?) in + service.unaryCallWithRequest(request) { response, error in + if let response = response { + NSLog("1. Finished successfully with response:\n\(response)") + } else { + NSLog("1. Finished with error: \(error!)") + } + } + + + // Same but manipulating headers: + + var RPC : ProtoRPC! // Needed to convince Swift to capture by reference (__block) + RPC = service.RPCToUnaryCallWithRequest(request) { response, error in if let response = response { - NSLog("Finished successfully with response:\n\(response)") + NSLog("2. Finished successfully with response:\n\(response)") } else { - NSLog("Finished with error: \(error!)") + NSLog("2. Finished with error: \(error!)") } + NSLog("2. Response headers: \(RPC.responseHeaders)") + NSLog("2. Response trailers: \(RPC.responseTrailers)") } + RPC.requestHeaders["My-Header"] = "My value" + + RPC.start() + + // Same example call using the generic gRPC client library: let method = ProtoMethod(package: "grpc.testing", service: "TestService", method: "UnaryCall") @@ -64,14 +84,16 @@ class ViewController: UIViewController { let call = GRPCCall(host: RemoteHost, path: method.HTTPPath, requestsWriter: requestsWriter) - let responsesWriteable = GRXWriteable { (value: AnyObject?, error: NSError?) in - if let value = value as? NSData { - NSLog("Received response:\n\(RMTSimpleResponse(data: value, error: nil))") + call.requestHeaders["My-Header"] = "My value" + + call.startWithWriteable(GRXWriteable { response, error in + if let response = response as? NSData { + NSLog("3. Received response:\n\(RMTSimpleResponse(data: response, error: nil))") } else { - NSLog("Finished with error: \(error!)") + NSLog("3. Finished with error: \(error!)") } - } - - call.startWithWriteable(responsesWriteable) + NSLog("3. Response headers: \(call.responseHeaders)") + NSLog("3. Response trailers: \(call.responseTrailers)") + }) } } |