aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar Craig Tiller <craig.tiller@gmail.com>2015-09-03 16:36:23 -0700
committerGravatar Craig Tiller <craig.tiller@gmail.com>2015-09-03 16:36:23 -0700
commitfa62267c78a86f6fee39477d6424e20ed5245fa5 (patch)
treedbf14d185bf8620daa782c2654711f78e116e55a /src
parentb5391e1ae68fefd2caa72ca694152da48282174e (diff)
parent51bfda063a903c15429cede9ab9dab4c355b7c9a (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.c1
-rw-r--r--src/core/transport/chttp2_transport.c4
-rw-r--r--src/objective-c/GRPCClient/GRPCCall.h107
-rw-r--r--src/objective-c/GRPCClient/GRPCCall.m21
-rw-r--r--src/objective-c/GRPCClient/private/GRPCRequestHeaders.h52
-rw-r--r--src/objective-c/GRPCClient/private/GRPCRequestHeaders.m119
-rw-r--r--src/objective-c/GRPCClient/private/GRPCWrappedCall.h3
-rw-r--r--src/objective-c/GRPCClient/private/GRPCWrappedCall.m2
-rw-r--r--src/objective-c/GRPCClient/private/NSDictionary+GRPC.m47
-rw-r--r--src/objective-c/GRPCClient/private/NSError+GRPC.h23
-rw-r--r--src/objective-c/ProtoRPC/ProtoRPC.m33
-rw-r--r--src/objective-c/examples/RemoteTestClient/RemoteTest.podspec4
-rw-r--r--src/objective-c/examples/SwiftSample/Bridging-Header.h1
-rw-r--r--src/objective-c/examples/SwiftSample/Podfile1
-rw-r--r--src/objective-c/examples/SwiftSample/ViewController.swift42
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)")
+ })
}
}