diff options
author | Julien Boeuf <jboeuf@google.com> | 2015-08-06 15:29:45 -0700 |
---|---|---|
committer | Julien Boeuf <jboeuf@google.com> | 2015-08-06 15:29:45 -0700 |
commit | f3128e6ef3d5aab97216a23e052bba85a6829bfd (patch) | |
tree | 7c42ebf0f3c1a936cc3a751002324d4cb13a7519 /src | |
parent | 54b5018dd889cbc5059ff77ba083ff7020c63a9b (diff) | |
parent | 83f8416f4ccaf1fa50d45f80124c72342244b7bd (diff) |
Merge branch 'master' of https://github.com/grpc/grpc into server_creds_auth_md_processor
Diffstat (limited to 'src')
48 files changed, 1193 insertions, 377 deletions
diff --git a/src/compiler/csharp_generator.cc b/src/compiler/csharp_generator.cc index e0c1bcda19..9432bdda96 100644 --- a/src/compiler/csharp_generator.cc +++ b/src/compiler/csharp_generator.cc @@ -246,6 +246,8 @@ void GenerateStaticMethodField(Printer* out, const MethodDescriptor *method) { out->Indent(); out->Print("$methodtype$,\n", "methodtype", GetCSharpMethodType(GetMethodType(method))); + out->Print("$servicenamefield$,\n", "servicenamefield", + GetServiceNameFieldName()); out->Print("\"$methodname$\",\n", "methodname", method->name()); out->Print("$requestmarshaller$,\n", "requestmarshaller", GetMarshallerFieldName(method->input_type())); @@ -273,6 +275,13 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) { "methodname", method->name(), "request", GetClassName(method->input_type()), "response", GetClassName(method->output_type())); + + // overload taking CallOptions as a param + out->Print( + "$response$ $methodname$($request$ request, CallOptions options);\n", + "methodname", method->name(), "request", + GetClassName(method->input_type()), "response", + GetClassName(method->output_type())); } std::string method_name = method->name(); @@ -284,6 +293,13 @@ void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) { "methodname", method_name, "request_maybe", GetMethodRequestParamMaybe(method), "returntype", GetMethodReturnTypeClient(method)); + + // overload taking CallOptions as a param + out->Print( + "$returntype$ $methodname$($request_maybe$CallOptions options);\n", + "methodname", method_name, "request_maybe", + GetMethodRequestParamMaybe(method), "returntype", + GetMethodReturnTypeClient(method)); } out->Outdent(); out->Print("}\n"); @@ -340,10 +356,23 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) { GetClassName(method->output_type())); out->Print("{\n"); out->Indent(); - out->Print("var call = CreateCall($servicenamefield$, $methodfield$, headers, deadline);\n", - "servicenamefield", GetServiceNameFieldName(), "methodfield", - GetMethodFieldName(method)); - out->Print("return Calls.BlockingUnaryCall(call, request, cancellationToken);\n"); + out->Print("var call = CreateCall($methodfield$, new CallOptions(headers, deadline, cancellationToken));\n", + "methodfield", GetMethodFieldName(method)); + out->Print("return Calls.BlockingUnaryCall(call, request);\n"); + out->Outdent(); + out->Print("}\n"); + + // overload taking CallOptions as a param + out->Print( + "public $response$ $methodname$($request$ request, CallOptions options)\n", + "methodname", method->name(), "request", + GetClassName(method->input_type()), "response", + GetClassName(method->output_type())); + out->Print("{\n"); + out->Indent(); + out->Print("var call = CreateCall($methodfield$, options);\n", + "methodfield", GetMethodFieldName(method)); + out->Print("return Calls.BlockingUnaryCall(call, request);\n"); out->Outdent(); out->Print("}\n"); } @@ -359,26 +388,55 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor *service) { GetMethodReturnTypeClient(method)); out->Print("{\n"); out->Indent(); - out->Print("var call = CreateCall($servicenamefield$, $methodfield$, headers, deadline);\n", - "servicenamefield", GetServiceNameFieldName(), "methodfield", - GetMethodFieldName(method)); + out->Print("var call = CreateCall($methodfield$, new CallOptions(headers, deadline, cancellationToken));\n", + "methodfield", GetMethodFieldName(method)); switch (GetMethodType(method)) { case METHODTYPE_NO_STREAMING: - out->Print("return Calls.AsyncUnaryCall(call, request, cancellationToken);\n"); + out->Print("return Calls.AsyncUnaryCall(call, request);\n"); break; case METHODTYPE_CLIENT_STREAMING: - out->Print("return Calls.AsyncClientStreamingCall(call, cancellationToken);\n"); + out->Print("return Calls.AsyncClientStreamingCall(call);\n"); break; case METHODTYPE_SERVER_STREAMING: out->Print( - "return Calls.AsyncServerStreamingCall(call, request, cancellationToken);\n"); + "return Calls.AsyncServerStreamingCall(call, request);\n"); break; case METHODTYPE_BIDI_STREAMING: - out->Print("return Calls.AsyncDuplexStreamingCall(call, cancellationToken);\n"); + out->Print("return Calls.AsyncDuplexStreamingCall(call);\n"); break; default: GOOGLE_LOG(FATAL)<< "Can't get here."; - } + } + out->Outdent(); + out->Print("}\n"); + + // overload taking CallOptions as a param + out->Print( + "public $returntype$ $methodname$($request_maybe$CallOptions options)\n", + "methodname", method_name, "request_maybe", + GetMethodRequestParamMaybe(method), "returntype", + GetMethodReturnTypeClient(method)); + out->Print("{\n"); + out->Indent(); + out->Print("var call = CreateCall($methodfield$, options);\n", + "methodfield", GetMethodFieldName(method)); + switch (GetMethodType(method)) { + case METHODTYPE_NO_STREAMING: + out->Print("return Calls.AsyncUnaryCall(call, request);\n"); + break; + case METHODTYPE_CLIENT_STREAMING: + out->Print("return Calls.AsyncClientStreamingCall(call);\n"); + break; + case METHODTYPE_SERVER_STREAMING: + out->Print( + "return Calls.AsyncServerStreamingCall(call, request);\n"); + break; + case METHODTYPE_BIDI_STREAMING: + out->Print("return Calls.AsyncDuplexStreamingCall(call);\n"); + break; + default: + GOOGLE_LOG(FATAL)<< "Can't get here."; + } out->Outdent(); out->Print("}\n"); } diff --git a/src/core/channel/client_channel.c b/src/core/channel/client_channel.c index 2cfca399b6..2ee260b799 100644 --- a/src/core/channel/client_channel.c +++ b/src/core/channel/client_channel.c @@ -56,6 +56,8 @@ typedef struct { grpc_mdctx *mdctx; /** resolver for this channel */ grpc_resolver *resolver; + /** have we started resolving this channel */ + int started_resolving; /** master channel - the grpc_channel instance that ultimately owns this channel_data via its channel stack. We occasionally use this to bump the refcount on the master channel @@ -398,6 +400,12 @@ static void perform_transport_stream_op(grpc_call_element *elem, } else if (chand->resolver != NULL) { calld->state = CALL_WAITING_FOR_CONFIG; add_to_lb_policy_wait_queue_locked_state_config(elem); + if (!chand->started_resolving && chand->resolver != NULL) { + chand->started_resolving = 1; + grpc_resolver_next(chand->resolver, + &chand->incoming_configuration, + &chand->on_config_changed); + } gpr_mu_unlock(&chand->mu_config); gpr_mu_unlock(&calld->mu_state); } else { @@ -690,12 +698,18 @@ void grpc_client_channel_set_resolver(grpc_channel_stack *channel_stack, /* post construction initialization: set the transport setup pointer */ grpc_channel_element *elem = grpc_channel_stack_last_element(channel_stack); channel_data *chand = elem->channel_data; + gpr_mu_lock(&chand->mu_config); GPR_ASSERT(!chand->resolver); chand->resolver = resolver; GRPC_CHANNEL_INTERNAL_REF(chand->master, "resolver"); GRPC_RESOLVER_REF(resolver, "channel"); - grpc_resolver_next(resolver, &chand->incoming_configuration, - &chand->on_config_changed); + if (chand->waiting_for_config_closures != NULL || + chand->exit_idle_when_lb_policy_arrives) { + chand->started_resolving = 1; + grpc_resolver_next(resolver, &chand->incoming_configuration, + &chand->on_config_changed); + } + gpr_mu_unlock(&chand->mu_config); } grpc_connectivity_state grpc_client_channel_check_connectivity_state( @@ -709,6 +723,11 @@ grpc_connectivity_state grpc_client_channel_check_connectivity_state( grpc_lb_policy_exit_idle(chand->lb_policy); } else { chand->exit_idle_when_lb_policy_arrives = 1; + if (!chand->started_resolving && chand->resolver != NULL) { + chand->started_resolving = 1; + grpc_resolver_next(chand->resolver, &chand->incoming_configuration, + &chand->on_config_changed); + } } } gpr_mu_unlock(&chand->mu_config); diff --git a/src/core/surface/call.c b/src/core/surface/call.c index 327a096ffb..d3e66e9c4c 100644 --- a/src/core/surface/call.c +++ b/src/core/surface/call.c @@ -143,6 +143,8 @@ typedef enum { struct grpc_call { grpc_completion_queue *cq; grpc_channel *channel; + grpc_call *parent; + grpc_call *first_child; grpc_mdctx *metadata_context; /* TODO(ctiller): share with cq if possible? */ gpr_mu mu; @@ -176,6 +178,8 @@ struct grpc_call { gpr_uint8 cancel_alarm; /** bitmask of allocated completion events in completions */ gpr_uint8 allocated_completions; + /** flag indicating that cancellation is inherited */ + gpr_uint8 cancellation_is_inherited; /* flags with bits corresponding to write states allowing us to determine what was sent */ @@ -267,6 +271,11 @@ struct grpc_call { /** completion events - for completion queue use */ grpc_cq_completion completions[MAX_CONCURRENT_COMPLETIONS]; + + /** siblings: children of the same parent form a list, and this list is protected under + parent->mu */ + grpc_call *sibling_next; + grpc_call *sibling_prev; }; #define CALL_STACK_FROM_CALL(call) ((grpc_call_stack *)((call) + 1)) @@ -290,7 +299,9 @@ static void finished_loose_op(void *call, int success); static void lock(grpc_call *call); static void unlock(grpc_call *call); -grpc_call *grpc_call_create(grpc_channel *channel, grpc_completion_queue *cq, +grpc_call *grpc_call_create(grpc_channel *channel, grpc_call *parent_call, + gpr_uint32 propagation_mask, + grpc_completion_queue *cq, const void *server_transport_data, grpc_mdelem **add_initial_metadata, size_t add_initial_metadata_count, @@ -306,9 +317,10 @@ grpc_call *grpc_call_create(grpc_channel *channel, grpc_completion_queue *cq, gpr_mu_init(&call->completion_mu); call->channel = channel; call->cq = cq; - if (cq) { + if (cq != NULL) { GRPC_CQ_INTERNAL_REF(cq, "bind"); } + call->parent = parent_call; call->is_client = server_transport_data == NULL; for (i = 0; i < GRPC_IOREQ_OP_COUNT; i++) { call->request_set[i] = REQSET_EMPTY; @@ -347,6 +359,46 @@ grpc_call *grpc_call_create(grpc_channel *channel, grpc_completion_queue *cq, } grpc_call_stack_init(channel_stack, server_transport_data, initial_op_ptr, CALL_STACK_FROM_CALL(call)); + if (parent_call != NULL) { + GRPC_CALL_INTERNAL_REF(parent_call, "child"); + GPR_ASSERT(call->is_client); + GPR_ASSERT(!parent_call->is_client); + + gpr_mu_lock(&parent_call->mu); + + if (propagation_mask & GRPC_PROPAGATE_DEADLINE) { + send_deadline = gpr_time_min( + gpr_convert_clock_type(send_deadline, + parent_call->send_deadline.clock_type), + parent_call->send_deadline); + } + /* for now GRPC_PROPAGATE_TRACING_CONTEXT *MUST* be passed with + * GRPC_PROPAGATE_STATS_CONTEXT */ + /* TODO(ctiller): This should change to use the appropriate census start_op + * call. */ + if (propagation_mask & GRPC_PROPAGATE_CENSUS_TRACING_CONTEXT) { + GPR_ASSERT(propagation_mask & GRPC_PROPAGATE_CENSUS_STATS_CONTEXT); + grpc_call_context_set(call, GRPC_CONTEXT_TRACING, + parent_call->context[GRPC_CONTEXT_TRACING].value, + NULL); + } else { + GPR_ASSERT(propagation_mask & GRPC_PROPAGATE_CENSUS_STATS_CONTEXT); + } + if (propagation_mask & GRPC_PROPAGATE_CANCELLATION) { + call->cancellation_is_inherited = 1; + } + + if (parent_call->first_child == NULL) { + parent_call->first_child = call; + call->sibling_next = call->sibling_prev = call; + } else { + call->sibling_next = parent_call->first_child; + call->sibling_prev = parent_call->first_child->sibling_prev; + call->sibling_next->sibling_prev = call->sibling_prev->sibling_next = call; + } + + gpr_mu_unlock(&parent_call->mu); + } if (gpr_time_cmp(send_deadline, gpr_inf_future(send_deadline.clock_type)) != 0) { set_deadline_alarm(call, send_deadline); @@ -404,6 +456,20 @@ void grpc_call_internal_ref(grpc_call *c) { static void destroy_call(void *call, int ignored_success) { size_t i; grpc_call *c = call; + grpc_call *parent = c->parent; + if (parent) { + gpr_mu_lock(&parent->mu); + if (call == parent->first_child) { + parent->first_child = c->sibling_next; + if (c == parent->first_child) { + parent->first_child = NULL; + } + c->sibling_prev->sibling_next = c->sibling_next; + c->sibling_next->sibling_prev = c->sibling_prev; + } + gpr_mu_unlock(&parent->mu); + GRPC_CALL_INTERNAL_UNREF(parent, "child", 1); + } grpc_call_stack_destroy(CALL_STACK_FROM_CALL(c)); GRPC_CHANNEL_INTERNAL_UNREF(c->channel, "call"); gpr_mu_destroy(&c->mu); @@ -870,6 +936,8 @@ static int add_slice_to_message(grpc_call *call, gpr_slice slice) { static void call_on_done_recv(void *pc, int success) { grpc_call *call = pc; + grpc_call *child_call; + grpc_call *next_child_call; size_t i; GRPC_TIMER_BEGIN(GRPC_PTAG_CALL_ON_DONE_RECV, 0); lock(call); @@ -903,6 +971,19 @@ static void call_on_done_recv(void *pc, int success) { GPR_ASSERT(call->read_state <= READ_STATE_STREAM_CLOSED); call->read_state = READ_STATE_STREAM_CLOSED; call->cancel_alarm |= call->have_alarm; + /* propagate cancellation to any interested children */ + child_call = call->first_child; + if (child_call != NULL) { + do { + next_child_call = child_call->sibling_next; + if (child_call->cancellation_is_inherited) { + GRPC_CALL_INTERNAL_REF(child_call, "propagate_cancel"); + grpc_call_cancel(child_call); + GRPC_CALL_INTERNAL_UNREF(child_call, "propagate_cancel", 0); + } + child_call = next_child_call; + } while (child_call != call->first_child); + } GRPC_CALL_INTERNAL_UNREF(call, "closed", 0); } finish_read_ops(call); @@ -1283,9 +1364,9 @@ static void set_deadline_alarm(grpc_call *call, gpr_timespec deadline) { } GRPC_CALL_INTERNAL_REF(call, "alarm"); call->have_alarm = 1; - grpc_alarm_init(&call->alarm, - gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC), - call_alarm, call, gpr_now(GPR_CLOCK_MONOTONIC)); + call->send_deadline = gpr_convert_clock_type(deadline, GPR_CLOCK_MONOTONIC); + grpc_alarm_init(&call->alarm, call->send_deadline, call_alarm, call, + gpr_now(GPR_CLOCK_MONOTONIC)); } /* we offset status by a small amount when storing it into transport metadata @@ -1377,7 +1458,8 @@ static void recv_metadata(grpc_call *call, grpc_metadata_batch *md) { } } if (gpr_time_cmp(md->deadline, gpr_inf_future(md->deadline.clock_type)) != - 0) { + 0 && + !call->is_client) { set_deadline_alarm(call, md->deadline); } if (!is_trailing) { @@ -1465,6 +1547,9 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, if (!are_write_flags_valid(op->flags)) { return GRPC_CALL_ERROR_INVALID_FLAGS; } + if (op->data.send_message == NULL) { + return GRPC_CALL_ERROR_INVALID_MESSAGE; + } req = &reqs[out++]; req->op = GRPC_IOREQ_SEND_MESSAGE; req->data.send_message = op->data.send_message; @@ -1514,6 +1599,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, req = &reqs[out++]; req->op = GRPC_IOREQ_RECV_INITIAL_METADATA; req->data.recv_metadata = op->data.recv_initial_metadata; + req->data.recv_metadata->count = 0; req->flags = op->flags; break; case GRPC_OP_RECV_MESSAGE: @@ -1545,6 +1631,7 @@ grpc_call_error grpc_call_start_batch(grpc_call *call, const grpc_op *ops, req->op = GRPC_IOREQ_RECV_TRAILING_METADATA; req->data.recv_metadata = op->data.recv_status_on_client.trailing_metadata; + req->data.recv_metadata->count = 0; req = &reqs[out++]; req->op = GRPC_IOREQ_RECV_CLOSE; finish_func = finish_batch_with_close; diff --git a/src/core/surface/call.h b/src/core/surface/call.h index 265638d519..75bdbce980 100644 --- a/src/core/surface/call.h +++ b/src/core/surface/call.h @@ -85,7 +85,9 @@ typedef struct { typedef void (*grpc_ioreq_completion_func)(grpc_call *call, int success, void *user_data); -grpc_call *grpc_call_create(grpc_channel *channel, grpc_completion_queue *cq, +grpc_call *grpc_call_create(grpc_channel *channel, grpc_call *parent_call, + gpr_uint32 propagation_mask, + grpc_completion_queue *cq, const void *server_transport_data, grpc_mdelem **add_initial_metadata, size_t add_initial_metadata_count, diff --git a/src/core/surface/channel.c b/src/core/surface/channel.c index 688a586e18..8692aa3903 100644 --- a/src/core/surface/channel.c +++ b/src/core/surface/channel.c @@ -146,7 +146,8 @@ char *grpc_channel_get_target(grpc_channel *channel) { } static grpc_call *grpc_channel_create_call_internal( - grpc_channel *channel, grpc_completion_queue *cq, grpc_mdelem *path_mdelem, + grpc_channel *channel, grpc_call *parent_call, gpr_uint32 propagation_mask, + grpc_completion_queue *cq, grpc_mdelem *path_mdelem, grpc_mdelem *authority_mdelem, gpr_timespec deadline) { grpc_mdelem *send_metadata[2]; int num_metadata = 0; @@ -158,16 +159,18 @@ static grpc_call *grpc_channel_create_call_internal( send_metadata[num_metadata++] = authority_mdelem; } - return grpc_call_create(channel, cq, NULL, send_metadata, - num_metadata, deadline); + return grpc_call_create(channel, parent_call, propagation_mask, cq, NULL, + send_metadata, num_metadata, deadline); } grpc_call *grpc_channel_create_call(grpc_channel *channel, + grpc_call *parent_call, + gpr_uint32 propagation_mask, grpc_completion_queue *cq, const char *method, const char *host, gpr_timespec deadline) { return grpc_channel_create_call_internal( - channel, cq, + channel, parent_call, propagation_mask, cq, grpc_mdelem_from_metadata_strings( channel->metadata_context, GRPC_MDSTR_REF(channel->path_string), grpc_mdstr_from_string(channel->metadata_context, method, 0)), @@ -195,11 +198,13 @@ void *grpc_channel_register_call(grpc_channel *channel, const char *method, } grpc_call *grpc_channel_create_registered_call( - grpc_channel *channel, grpc_completion_queue *completion_queue, - void *registered_call_handle, gpr_timespec deadline) { + grpc_channel *channel, grpc_call *parent_call, gpr_uint32 propagation_mask, + grpc_completion_queue *completion_queue, void *registered_call_handle, + gpr_timespec deadline) { registered_call *rc = registered_call_handle; return grpc_channel_create_call_internal( - channel, completion_queue, GRPC_MDELEM_REF(rc->path), + channel, parent_call, propagation_mask, completion_queue, + GRPC_MDELEM_REF(rc->path), rc->authority ? GRPC_MDELEM_REF(rc->authority) : NULL, deadline); } diff --git a/src/core/surface/completion_queue.c b/src/core/surface/completion_queue.c index 00429fac19..36d69cfe5f 100644 --- a/src/core/surface/completion_queue.c +++ b/src/core/surface/completion_queue.c @@ -114,6 +114,11 @@ void grpc_cq_internal_unref(grpc_completion_queue *cc) { } void grpc_cq_begin_op(grpc_completion_queue *cc) { +#ifndef NDEBUG + gpr_mu_lock(GRPC_POLLSET_MU(&cc->pollset)); + GPR_ASSERT(!cc->shutdown_called); + gpr_mu_unlock(GRPC_POLLSET_MU(&cc->pollset)); +#endif gpr_ref(&cc->pending_events); } diff --git a/src/core/surface/server.c b/src/core/surface/server.c index 29d893db71..cd1dc589e1 100644 --- a/src/core/surface/server.c +++ b/src/core/surface/server.c @@ -644,8 +644,8 @@ static void accept_stream(void *cd, grpc_transport *transport, const void *transport_server_data) { channel_data *chand = cd; /* create a call */ - grpc_call_create(chand->channel, NULL, transport_server_data, NULL, 0, - gpr_inf_future(GPR_CLOCK_REALTIME)); + grpc_call_create(chand->channel, NULL, 0, NULL, transport_server_data, NULL, + 0, gpr_inf_future(GPR_CLOCK_MONOTONIC)); } static void channel_connectivity_changed(void *cd, int iomgr_status_ignored) { diff --git a/src/cpp/client/channel.cc b/src/cpp/client/channel.cc index ee143d68a0..5f54e7fcc1 100644 --- a/src/cpp/client/channel.cc +++ b/src/cpp/client/channel.cc @@ -61,16 +61,18 @@ Channel::~Channel() { grpc_channel_destroy(c_channel_); } Call Channel::CreateCall(const RpcMethod& method, ClientContext* context, CompletionQueue* cq) { const char* host_str = host_.empty() ? NULL : host_.c_str(); - auto c_call = - method.channel_tag() && context->authority().empty() - ? grpc_channel_create_registered_call(c_channel_, cq->cq(), - method.channel_tag(), - context->raw_deadline()) - : grpc_channel_create_call(c_channel_, cq->cq(), method.name(), - context->authority().empty() - ? host_str - : context->authority().c_str(), - context->raw_deadline()); + auto c_call = method.channel_tag() && context->authority().empty() + ? grpc_channel_create_registered_call( + c_channel_, context->propagate_from_call_, + context->propagation_options_.c_bitmask(), cq->cq(), + method.channel_tag(), context->raw_deadline()) + : grpc_channel_create_call( + c_channel_, context->propagate_from_call_, + context->propagation_options_.c_bitmask(), cq->cq(), + method.name(), context->authority().empty() + ? host_str + : context->authority().c_str(), + context->raw_deadline()); grpc_census_call_set_context(c_call, context->census_context()); GRPC_TIMER_MARK(GRPC_PTAG_CPP_CALL_CREATED, c_call); context->set_call(c_call, shared_from_this()); diff --git a/src/cpp/client/client_context.cc b/src/cpp/client/client_context.cc index c38d0c1df6..1ed2d38961 100644 --- a/src/cpp/client/client_context.cc +++ b/src/cpp/client/client_context.cc @@ -37,6 +37,7 @@ #include <grpc/support/alloc.h> #include <grpc/support/string_util.h> #include <grpc++/credentials.h> +#include <grpc++/server_context.h> #include <grpc++/time.h> #include "src/core/channel/compress_filter.h" @@ -48,7 +49,8 @@ ClientContext::ClientContext() : initial_metadata_received_(false), call_(nullptr), cq_(nullptr), - deadline_(gpr_inf_future(GPR_CLOCK_REALTIME)) {} + deadline_(gpr_inf_future(GPR_CLOCK_REALTIME)), + propagate_from_call_(nullptr) {} ClientContext::~ClientContext() { if (call_) { @@ -64,6 +66,14 @@ ClientContext::~ClientContext() { } } +std::unique_ptr<ClientContext> ClientContext::FromServerContext( + const ServerContext& context, PropagationOptions options) { + std::unique_ptr<ClientContext> ctx(new ClientContext); + ctx->propagate_from_call_ = context.call_; + ctx->propagation_options_ = options; + return ctx; +} + void ClientContext::AddMetadata(const grpc::string& meta_key, const grpc::string& meta_value) { send_initial_metadata_.insert(std::make_pair(meta_key, meta_value)); diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs index bf7cc3fbf3..64ea21800f 100644 --- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs +++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs @@ -46,23 +46,26 @@ namespace Grpc.Core.Tests public class ClientServerTest { const string Host = "127.0.0.1"; - const string ServiceName = "/tests.Test"; + const string ServiceName = "tests.Test"; static readonly Method<string, string> EchoMethod = new Method<string, string>( MethodType.Unary, - "/tests.Test/Echo", + ServiceName, + "Echo", Marshallers.StringMarshaller, Marshallers.StringMarshaller); static readonly Method<string, string> ConcatAndEchoMethod = new Method<string, string>( MethodType.ClientStreaming, - "/tests.Test/ConcatAndEcho", + ServiceName, + "ConcatAndEcho", Marshallers.StringMarshaller, Marshallers.StringMarshaller); static readonly Method<string, string> NonexistentMethod = new Method<string, string>( MethodType.Unary, - "/tests.Test/NonexistentMethod", + ServiceName, + "NonexistentMethod", Marshallers.StringMarshaller, Marshallers.StringMarshaller); @@ -102,17 +105,17 @@ namespace Grpc.Core.Tests [Test] public void UnaryCall() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - Assert.AreEqual("ABC", Calls.BlockingUnaryCall(internalCall, "ABC", CancellationToken.None)); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); + Assert.AreEqual("ABC", Calls.BlockingUnaryCall(callDetails, "ABC")); } [Test] public void UnaryCall_ServerHandlerThrows() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); try { - Calls.BlockingUnaryCall(internalCall, "THROW", CancellationToken.None); + Calls.BlockingUnaryCall(callDetails, "THROW"); Assert.Fail(); } catch (RpcException e) @@ -124,10 +127,10 @@ namespace Grpc.Core.Tests [Test] public void UnaryCall_ServerHandlerThrowsRpcException() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); try { - Calls.BlockingUnaryCall(internalCall, "THROW_UNAUTHENTICATED", CancellationToken.None); + Calls.BlockingUnaryCall(callDetails, "THROW_UNAUTHENTICATED"); Assert.Fail(); } catch (RpcException e) @@ -139,10 +142,10 @@ namespace Grpc.Core.Tests [Test] public void UnaryCall_ServerHandlerSetsStatus() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); try { - Calls.BlockingUnaryCall(internalCall, "SET_UNAUTHENTICATED", CancellationToken.None); + Calls.BlockingUnaryCall(callDetails, "SET_UNAUTHENTICATED"); Assert.Fail(); } catch (RpcException e) @@ -152,20 +155,20 @@ namespace Grpc.Core.Tests } [Test] - public void AsyncUnaryCall() + public async Task AsyncUnaryCall() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - var result = Calls.AsyncUnaryCall(internalCall, "ABC", CancellationToken.None).ResponseAsync.Result; + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); + var result = await Calls.AsyncUnaryCall(callDetails, "ABC"); Assert.AreEqual("ABC", result); } [Test] public async Task AsyncUnaryCall_ServerHandlerThrows() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); try { - await Calls.AsyncUnaryCall(internalCall, "THROW", CancellationToken.None); + await Calls.AsyncUnaryCall(callDetails, "THROW"); Assert.Fail(); } catch (RpcException e) @@ -177,8 +180,8 @@ namespace Grpc.Core.Tests [Test] public async Task ClientStreamingCall() { - var internalCall = new Call<string, string>(ServiceName, ConcatAndEchoMethod, channel, Metadata.Empty); - var call = Calls.AsyncClientStreamingCall(internalCall, CancellationToken.None); + var callDetails = new CallInvocationDetails<string, string>(channel, ConcatAndEchoMethod, new CallOptions()); + var call = Calls.AsyncClientStreamingCall(callDetails); await call.RequestStream.WriteAll(new string[] { "A", "B", "C" }); Assert.AreEqual("ABC", await call.ResponseAsync); @@ -187,10 +190,9 @@ namespace Grpc.Core.Tests [Test] public async Task ClientStreamingCall_CancelAfterBegin() { - var internalCall = new Call<string, string>(ServiceName, ConcatAndEchoMethod, channel, Metadata.Empty); - var cts = new CancellationTokenSource(); - var call = Calls.AsyncClientStreamingCall(internalCall, cts.Token); + var callDetails = new CallInvocationDetails<string, string>(channel, ConcatAndEchoMethod, new CallOptions(cancellationToken: cts.Token)); + var call = Calls.AsyncClientStreamingCall(callDetails); // TODO(jtattermusch): we need this to ensure call has been initiated once we cancel it. await Task.Delay(1000); @@ -214,8 +216,8 @@ namespace Grpc.Core.Tests new Metadata.Entry("ascii-header", "abcdefg"), new Metadata.Entry("binary-header-bin", new byte[] { 1, 2, 3, 0, 0xff }), }; - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, headers); - var call = Calls.AsyncUnaryCall(internalCall, "ABC", CancellationToken.None); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions(headers: headers)); + var call = Calls.AsyncUnaryCall(callDetails, "ABC"); Assert.AreEqual("ABC", call.ResponseAsync.Result); @@ -235,25 +237,25 @@ namespace Grpc.Core.Tests { channel.Dispose(); - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(internalCall, "ABC", CancellationToken.None)); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); + Assert.Throws(typeof(ObjectDisposedException), () => Calls.BlockingUnaryCall(callDetails, "ABC")); } [Test] public void UnaryCallPerformance() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); BenchmarkUtil.RunBenchmark(100, 100, - () => { Calls.BlockingUnaryCall(internalCall, "ABC", default(CancellationToken)); }); + () => { Calls.BlockingUnaryCall(callDetails, "ABC"); }); } [Test] public void UnknownMethodHandler() { - var internalCall = new Call<string, string>(ServiceName, NonexistentMethod, channel, Metadata.Empty); + var callDetails = new CallInvocationDetails<string, string>(channel, NonexistentMethod, new CallOptions()); try { - Calls.BlockingUnaryCall(internalCall, "ABC", default(CancellationToken)); + Calls.BlockingUnaryCall(callDetails, "ABC"); Assert.Fail(); } catch (RpcException e) @@ -265,16 +267,16 @@ namespace Grpc.Core.Tests [Test] public void UserAgentStringPresent() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - string userAgent = Calls.BlockingUnaryCall(internalCall, "RETURN-USER-AGENT", CancellationToken.None); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); + string userAgent = Calls.BlockingUnaryCall(callDetails, "RETURN-USER-AGENT"); Assert.IsTrue(userAgent.StartsWith("grpc-csharp/")); } [Test] public void PeerInfoPresent() { - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - string peer = Calls.BlockingUnaryCall(internalCall, "RETURN-PEER", CancellationToken.None); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); + string peer = Calls.BlockingUnaryCall(callDetails, "RETURN-PEER"); Assert.IsTrue(peer.Contains(Host)); } @@ -286,8 +288,8 @@ namespace Grpc.Core.Tests var stateChangedTask = channel.WaitForStateChangedAsync(channel.State); - var internalCall = new Call<string, string>(ServiceName, EchoMethod, channel, Metadata.Empty); - await Calls.AsyncUnaryCall(internalCall, "abc", CancellationToken.None); + var callDetails = new CallInvocationDetails<string, string>(channel, EchoMethod, new CallOptions()); + await Calls.AsyncUnaryCall(callDetails, "abc"); await stateChangedTask; Assert.AreEqual(ChannelState.Ready, channel.State); diff --git a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs index d84801fbac..fc395b0acd 100644 --- a/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs +++ b/src/csharp/Grpc.Core.Tests/TimeoutsTest.cs @@ -49,11 +49,12 @@ namespace Grpc.Core.Tests public class TimeoutsTest { const string Host = "localhost"; - const string ServiceName = "/tests.Test"; + const string ServiceName = "tests.Test"; static readonly Method<string, string> TestMethod = new Method<string, string>( MethodType.Unary, - "/tests.Test/Test", + ServiceName, + "Test", Marshallers.StringMarshaller, Marshallers.StringMarshaller); @@ -98,12 +99,12 @@ namespace Grpc.Core.Tests public void InfiniteDeadline() { // no deadline specified, check server sees infinite deadline - var internalCall = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty); - Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(internalCall, "RETURN_DEADLINE", CancellationToken.None)); + var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions()); + Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(callDetails, "RETURN_DEADLINE")); // DateTime.MaxValue deadline specified, check server sees infinite deadline - var internalCall2 = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty, DateTime.MaxValue); - Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(internalCall2, "RETURN_DEADLINE", CancellationToken.None)); + var callDetails2 = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions()); + Assert.AreEqual("DATETIME_MAXVALUE", Calls.BlockingUnaryCall(callDetails2, "RETURN_DEADLINE")); } [Test] @@ -112,9 +113,9 @@ namespace Grpc.Core.Tests var remainingTimeClient = TimeSpan.FromDays(7); var deadline = DateTime.UtcNow + remainingTimeClient; Thread.Sleep(1000); - var internalCall = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty, deadline); + var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline)); - var serverDeadlineTicksString = Calls.BlockingUnaryCall(internalCall, "RETURN_DEADLINE", CancellationToken.None); + var serverDeadlineTicksString = Calls.BlockingUnaryCall(callDetails, "RETURN_DEADLINE"); var serverDeadline = new DateTime(long.Parse(serverDeadlineTicksString), DateTimeKind.Utc); // A fairly relaxed check that the deadline set by client and deadline seen by server @@ -126,12 +127,11 @@ namespace Grpc.Core.Tests [Test] public void DeadlineInThePast() { - var deadline = DateTime.MinValue; - var internalCall = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty, deadline); + var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: DateTime.MinValue)); try { - Calls.BlockingUnaryCall(internalCall, "TIMEOUT", CancellationToken.None); + Calls.BlockingUnaryCall(callDetails, "TIMEOUT"); Assert.Fail(); } catch (RpcException e) @@ -145,11 +145,11 @@ namespace Grpc.Core.Tests public void DeadlineExceededStatusOnTimeout() { var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); - var internalCall = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty, deadline); + var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline)); try { - Calls.BlockingUnaryCall(internalCall, "TIMEOUT", CancellationToken.None); + Calls.BlockingUnaryCall(callDetails, "TIMEOUT"); Assert.Fail(); } catch (RpcException e) @@ -163,11 +163,11 @@ namespace Grpc.Core.Tests public void ServerReceivesCancellationOnTimeout() { var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); - var internalCall = new Call<string, string>(ServiceName, TestMethod, channel, Metadata.Empty, deadline); + var callDetails = new CallInvocationDetails<string, string>(channel, TestMethod, new CallOptions(deadline: deadline)); try { - Calls.BlockingUnaryCall(internalCall, "CHECK_CANCELLATION_RECEIVED", CancellationToken.None); + Calls.BlockingUnaryCall(callDetails, "CHECK_CANCELLATION_RECEIVED"); Assert.Fail(); } catch (RpcException e) diff --git a/src/csharp/Grpc.Core/Call.cs b/src/csharp/Grpc.Core/CallInvocationDetails.cs index 94c5e26082..eb23a3a209 100644 --- a/src/csharp/Grpc.Core/Call.cs +++ b/src/csharp/Grpc.Core/CallInvocationDetails.cs @@ -38,30 +38,30 @@ using Grpc.Core.Utils; namespace Grpc.Core { /// <summary> - /// Abstraction of a call to be invoked on a client. + /// Details about a client-side call to be invoked. /// </summary> - public class Call<TRequest, TResponse> + public class CallInvocationDetails<TRequest, TResponse> { - readonly string name; + readonly Channel channel; + readonly string method; + readonly string host; readonly Marshaller<TRequest> requestMarshaller; readonly Marshaller<TResponse> responseMarshaller; - readonly Channel channel; - readonly Metadata headers; - readonly DateTime deadline; + readonly CallOptions options; - public Call(string serviceName, Method<TRequest, TResponse> method, Channel channel, Metadata headers) - : this(serviceName, method, channel, headers, DateTime.MaxValue) + public CallInvocationDetails(Channel channel, Method<TRequest, TResponse> method, CallOptions options) : + this(channel, method.FullName, null, method.RequestMarshaller, method.ResponseMarshaller, options) { } - public Call(string serviceName, Method<TRequest, TResponse> method, Channel channel, Metadata headers, DateTime deadline) + public CallInvocationDetails(Channel channel, string method, string host, Marshaller<TRequest> requestMarshaller, Marshaller<TResponse> responseMarshaller, CallOptions options) { - this.name = method.GetFullName(serviceName); - this.requestMarshaller = method.RequestMarshaller; - this.responseMarshaller = method.ResponseMarshaller; this.channel = Preconditions.CheckNotNull(channel); - this.headers = Preconditions.CheckNotNull(headers); - this.deadline = deadline; + this.method = Preconditions.CheckNotNull(method); + this.host = host; + this.requestMarshaller = Preconditions.CheckNotNull(requestMarshaller); + this.responseMarshaller = Preconditions.CheckNotNull(responseMarshaller); + this.options = Preconditions.CheckNotNull(options); } public Channel Channel @@ -72,49 +72,43 @@ namespace Grpc.Core } } - /// <summary> - /// Full methods name including the service name. - /// </summary> - public string Name + public string Method { get { - return name; + return this.method; } } - /// <summary> - /// Headers to send at the beginning of the call. - /// </summary> - public Metadata Headers + public string Host { get { - return headers; + return this.host; } } - public DateTime Deadline + public Marshaller<TRequest> RequestMarshaller { get { - return this.deadline; + return this.requestMarshaller; } } - public Marshaller<TRequest> RequestMarshaller + public Marshaller<TResponse> ResponseMarshaller { get { - return requestMarshaller; + return this.responseMarshaller; } } - - public Marshaller<TResponse> ResponseMarshaller + + public CallOptions Options { get { - return responseMarshaller; + return options; } } } diff --git a/src/csharp/Grpc.Core/CallOptions.cs b/src/csharp/Grpc.Core/CallOptions.cs new file mode 100644 index 0000000000..8e9739335f --- /dev/null +++ b/src/csharp/Grpc.Core/CallOptions.cs @@ -0,0 +1,89 @@ +#region Copyright notice and license + +// 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. + +#endregion + +using System; +using System.Threading; + +using Grpc.Core.Internal; +using Grpc.Core.Utils; + +namespace Grpc.Core +{ + /// <summary> + /// Options for calls made by client. + /// </summary> + public class CallOptions + { + readonly Metadata headers; + readonly DateTime deadline; + readonly CancellationToken cancellationToken; + + /// <summary> + /// Creates a new instance of <c>CallOptions</c>. + /// </summary> + /// <param name="headers">Headers to be sent with the call.</param> + /// <param name="deadline">Deadline for the call to finish. null means no deadline.</param> + /// <param name="cancellationToken">Can be used to request cancellation of the call.</param> + public CallOptions(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) + { + // TODO(jtattermusch): consider only creating metadata object once it's really needed. + this.headers = headers != null ? headers : new Metadata(); + this.deadline = deadline.HasValue ? deadline.Value : DateTime.MaxValue; + this.cancellationToken = cancellationToken; + } + + /// <summary> + /// Headers to send at the beginning of the call. + /// </summary> + public Metadata Headers + { + get { return headers; } + } + + /// <summary> + /// Call deadline. + /// </summary> + public DateTime Deadline + { + get { return deadline; } + } + + /// <summary> + /// Token that can be used for cancelling the call. + /// </summary> + public CancellationToken CancellationToken + { + get { return cancellationToken; } + } + } +} diff --git a/src/csharp/Grpc.Core/Calls.cs b/src/csharp/Grpc.Core/Calls.cs index 054fc27491..00a8cabf82 100644 --- a/src/csharp/Grpc.Core/Calls.cs +++ b/src/csharp/Grpc.Core/Calls.cs @@ -43,70 +43,52 @@ namespace Grpc.Core /// </summary> public static class Calls { - public static TResponse BlockingUnaryCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token) + public static TResponse BlockingUnaryCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call, TRequest req) where TRequest : class where TResponse : class { - var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - // TODO(jtattermusch): this gives a race that cancellation can be requested before the call even starts. - RegisterCancellationCallback(asyncCall, token); - return asyncCall.UnaryCall(call.Channel, call.Name, req, call.Headers, call.Deadline); + var asyncCall = new AsyncCall<TRequest, TResponse>(call); + return asyncCall.UnaryCall(req); } - public static AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token) + public static AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call, TRequest req) where TRequest : class where TResponse : class { - var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name, Timespec.FromDateTime(call.Deadline)); - var asyncResult = asyncCall.UnaryCallAsync(req, call.Headers, call.Deadline); - RegisterCancellationCallback(asyncCall, token); + var asyncCall = new AsyncCall<TRequest, TResponse>(call); + var asyncResult = asyncCall.UnaryCallAsync(req); return new AsyncUnaryCall<TResponse>(asyncResult, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } - public static AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token) + public static AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call, TRequest req) where TRequest : class where TResponse : class { - var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name, Timespec.FromDateTime(call.Deadline)); - asyncCall.StartServerStreamingCall(req, call.Headers, call.Deadline); - RegisterCancellationCallback(asyncCall, token); + var asyncCall = new AsyncCall<TRequest, TResponse>(call); + asyncCall.StartServerStreamingCall(req); var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall); return new AsyncServerStreamingCall<TResponse>(responseStream, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } - public static AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, CancellationToken token) + public static AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call) where TRequest : class where TResponse : class { - var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name, Timespec.FromDateTime(call.Deadline)); - var resultTask = asyncCall.ClientStreamingCallAsync(call.Headers, call.Deadline); - RegisterCancellationCallback(asyncCall, token); + var asyncCall = new AsyncCall<TRequest, TResponse>(call); + var resultTask = asyncCall.ClientStreamingCallAsync(); var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall); return new AsyncClientStreamingCall<TRequest, TResponse>(requestStream, resultTask, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } - public static AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, CancellationToken token) + public static AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(CallInvocationDetails<TRequest, TResponse> call) where TRequest : class where TResponse : class { - var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name, Timespec.FromDateTime(call.Deadline)); - asyncCall.StartDuplexStreamingCall(call.Headers, call.Deadline); - RegisterCancellationCallback(asyncCall, token); + var asyncCall = new AsyncCall<TRequest, TResponse>(call); + asyncCall.StartDuplexStreamingCall(); var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall); var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall); return new AsyncDuplexStreamingCall<TRequest, TResponse>(requestStream, responseStream, asyncCall.GetStatus, asyncCall.GetTrailers, asyncCall.Cancel); } - - private static void RegisterCancellationCallback<TRequest, TResponse>(AsyncCall<TRequest, TResponse> asyncCall, CancellationToken token) - { - if (token.CanBeCanceled) - { - token.Register(() => asyncCall.Cancel()); - } - } } } diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs index 0b69610443..9273ea4582 100644 --- a/src/csharp/Grpc.Core/Channel.cs +++ b/src/csharp/Grpc.Core/Channel.cs @@ -178,22 +178,6 @@ namespace Grpc.Core } } - internal CompletionQueueSafeHandle CompletionQueue - { - get - { - return this.environment.CompletionQueue; - } - } - - internal CompletionRegistry CompletionRegistry - { - get - { - return this.environment.CompletionRegistry; - } - } - internal GrpcEnvironment Environment { get diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs index fd3473128a..88494bb4ac 100644 --- a/src/csharp/Grpc.Core/ClientBase.cs +++ b/src/csharp/Grpc.Core/ClientBase.cs @@ -76,19 +76,17 @@ namespace Grpc.Core /// <summary> /// Creates a new call to given method. /// </summary> - protected Call<TRequest, TResponse> CreateCall<TRequest, TResponse>(string serviceName, Method<TRequest, TResponse> method, Metadata metadata, DateTime? deadline) + protected CallInvocationDetails<TRequest, TResponse> CreateCall<TRequest, TResponse>(Method<TRequest, TResponse> method, CallOptions options) where TRequest : class where TResponse : class { var interceptor = HeaderInterceptor; if (interceptor != null) { - metadata = metadata ?? new Metadata(); - interceptor(metadata); - metadata.Freeze(); + interceptor(options.Headers); + options.Headers.Freeze(); } - return new Call<TRequest, TResponse>(serviceName, method, channel, - metadata ?? Metadata.Empty, deadline ?? DateTime.MaxValue); + return new CallInvocationDetails<TRequest, TResponse>(channel, method, options); } } } diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj index 17add77164..52defd1965 100644 --- a/src/csharp/Grpc.Core/Grpc.Core.csproj +++ b/src/csharp/Grpc.Core/Grpc.Core.csproj @@ -57,7 +57,6 @@ <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="RpcException.cs" /> <Compile Include="Calls.cs" /> - <Compile Include="Call.cs" /> <Compile Include="AsyncClientStreamingCall.cs" /> <Compile Include="GrpcEnvironment.cs" /> <Compile Include="Status.cs" /> @@ -114,6 +113,8 @@ <Compile Include="Logging\ConsoleLogger.cs" /> <Compile Include="Internal\NativeLogRedirector.cs" /> <Compile Include="ChannelState.cs" /> + <Compile Include="CallInvocationDetails.cs" /> + <Compile Include="CallOptions.cs" /> </ItemGroup> <ItemGroup> <None Include="Grpc.Core.nuspec" /> diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs index 48f466460f..414b5c4282 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs @@ -50,7 +50,7 @@ namespace Grpc.Core.Internal { static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<AsyncCall<TRequest, TResponse>>(); - Channel channel; + readonly CallInvocationDetails<TRequest, TResponse> callDetails; // Completion of a pending unary response if not null. TaskCompletionSource<TResponse> unaryResponseTcs; @@ -60,26 +60,18 @@ namespace Grpc.Core.Internal bool readObserverCompleted; // True if readObserver has already been completed. - public AsyncCall(Func<TRequest, byte[]> serializer, Func<byte[], TResponse> deserializer) : base(serializer, deserializer) + public AsyncCall(CallInvocationDetails<TRequest, TResponse> callDetails) + : base(callDetails.RequestMarshaller.Serializer, callDetails.ResponseMarshaller.Deserializer) { - } - - public void Initialize(Channel channel, CompletionQueueSafeHandle cq, string methodName, Timespec deadline) - { - this.channel = channel; - var call = channel.Handle.CreateCall(channel.CompletionRegistry, cq, methodName, null, deadline); - channel.Environment.DebugStats.ActiveClientCalls.Increment(); - InitializeInternal(call); + this.callDetails = callDetails; } // TODO: this method is not Async, so it shouldn't be in AsyncCall class, but // it is reusing fair amount of code in this class, so we are leaving it here. - // TODO: for other calls, you need to call Initialize, this methods calls initialize - // on its own, so there's a usage inconsistency. /// <summary> /// Blocking unary request - unary response call. /// </summary> - public TResponse UnaryCall(Channel channel, string methodName, TRequest msg, Metadata headers, DateTime deadline) + public TResponse UnaryCall(TRequest msg) { using (CompletionQueueSafeHandle cq = CompletionQueueSafeHandle.Create()) { @@ -89,13 +81,15 @@ namespace Grpc.Core.Internal lock (myLock) { - Initialize(channel, cq, methodName, Timespec.FromDateTime(deadline)); + Preconditions.CheckState(!started); started = true; + Initialize(cq); + halfcloseRequested = true; readingDone = true; } - using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers)) { using (var ctx = BatchContextSafeHandle.Create()) { @@ -129,20 +123,22 @@ namespace Grpc.Core.Internal /// <summary> /// Starts a unary request - unary response call. /// </summary> - public Task<TResponse> UnaryCallAsync(TRequest msg, Metadata headers, DateTime deadline) + public Task<TResponse> UnaryCallAsync(TRequest msg) { lock (myLock) { - Preconditions.CheckNotNull(call); - + Preconditions.CheckState(!started); started = true; + + Initialize(callDetails.Channel.Environment.CompletionQueue); + halfcloseRequested = true; readingDone = true; byte[] payload = UnsafeSerialize(msg); unaryResponseTcs = new TaskCompletionSource<TResponse>(); - using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers)) { call.StartUnary(payload, HandleUnaryResponse, metadataArray); } @@ -154,17 +150,19 @@ namespace Grpc.Core.Internal /// Starts a streamed request - unary response call. /// Use StartSendMessage and StartSendCloseFromClient to stream requests. /// </summary> - public Task<TResponse> ClientStreamingCallAsync(Metadata headers, DateTime deadline) + public Task<TResponse> ClientStreamingCallAsync() { lock (myLock) { - Preconditions.CheckNotNull(call); - + Preconditions.CheckState(!started); started = true; + + Initialize(callDetails.Channel.Environment.CompletionQueue); + readingDone = true; unaryResponseTcs = new TaskCompletionSource<TResponse>(); - using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers)) { call.StartClientStreaming(HandleUnaryResponse, metadataArray); } @@ -176,19 +174,21 @@ namespace Grpc.Core.Internal /// <summary> /// Starts a unary request - streamed response call. /// </summary> - public void StartServerStreamingCall(TRequest msg, Metadata headers, DateTime deadline) + public void StartServerStreamingCall(TRequest msg) { lock (myLock) { - Preconditions.CheckNotNull(call); - + Preconditions.CheckState(!started); started = true; + + Initialize(callDetails.Channel.Environment.CompletionQueue); + halfcloseRequested = true; halfclosed = true; // halfclose not confirmed yet, but it will be once finishedHandler is called. byte[] payload = UnsafeSerialize(msg); - using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers)) { call.StartServerStreaming(payload, HandleFinished, metadataArray); } @@ -199,15 +199,16 @@ namespace Grpc.Core.Internal /// Starts a streaming request - streaming response call. /// Use StartSendMessage and StartSendCloseFromClient to stream requests. /// </summary> - public void StartDuplexStreamingCall(Metadata headers, DateTime deadline) + public void StartDuplexStreamingCall() { lock (myLock) { - Preconditions.CheckNotNull(call); - + Preconditions.CheckState(!started); started = true; - using (var metadataArray = MetadataArraySafeHandle.Create(headers)) + Initialize(callDetails.Channel.Environment.CompletionQueue); + + using (var metadataArray = MetadataArraySafeHandle.Create(callDetails.Options.Headers)) { call.StartDuplexStreaming(HandleFinished, metadataArray); } @@ -309,7 +310,26 @@ namespace Grpc.Core.Internal protected override void OnReleaseResources() { - channel.Environment.DebugStats.ActiveClientCalls.Decrement(); + callDetails.Channel.Environment.DebugStats.ActiveClientCalls.Decrement(); + } + + private void Initialize(CompletionQueueSafeHandle cq) + { + var call = callDetails.Channel.Handle.CreateCall(callDetails.Channel.Environment.CompletionRegistry, cq, + callDetails.Method, callDetails.Host, Timespec.FromDateTime(callDetails.Options.Deadline)); + callDetails.Channel.Environment.DebugStats.ActiveClientCalls.Increment(); + InitializeInternal(call); + RegisterCancellationCallback(); + } + + // Make sure that once cancellationToken for this call is cancelled, Cancel() will be called. + private void RegisterCancellationCallback() + { + var token = callDetails.Options.CancellationToken; + if (token.CanBeCanceled) + { + token.Register(() => this.Cancel()); + } } /// <summary> diff --git a/src/csharp/Grpc.Core/Method.cs b/src/csharp/Grpc.Core/Method.cs index 77d36191c3..cc047ac9f8 100644 --- a/src/csharp/Grpc.Core/Method.cs +++ b/src/csharp/Grpc.Core/Method.cs @@ -53,16 +53,20 @@ namespace Grpc.Core public class Method<TRequest, TResponse> { readonly MethodType type; + readonly string serviceName; readonly string name; readonly Marshaller<TRequest> requestMarshaller; readonly Marshaller<TResponse> responseMarshaller; + readonly string fullName; - public Method(MethodType type, string name, Marshaller<TRequest> requestMarshaller, Marshaller<TResponse> responseMarshaller) + public Method(MethodType type, string serviceName, string name, Marshaller<TRequest> requestMarshaller, Marshaller<TResponse> responseMarshaller) { this.type = type; - this.name = name; - this.requestMarshaller = requestMarshaller; - this.responseMarshaller = responseMarshaller; + this.serviceName = Preconditions.CheckNotNull(serviceName); + this.name = Preconditions.CheckNotNull(name); + this.requestMarshaller = Preconditions.CheckNotNull(requestMarshaller); + this.responseMarshaller = Preconditions.CheckNotNull(responseMarshaller); + this.fullName = GetFullName(serviceName); } public MethodType Type @@ -72,6 +76,14 @@ namespace Grpc.Core return this.type; } } + + public string ServiceName + { + get + { + return this.serviceName; + } + } public string Name { @@ -97,6 +109,14 @@ namespace Grpc.Core } } + public string FullName + { + get + { + return this.fullName; + } + } + /// <summary> /// Gets full name of the method including the service name. /// </summary> diff --git a/src/csharp/Grpc.Core/ServerCallContext.cs b/src/csharp/Grpc.Core/ServerCallContext.cs index 0c48adaea5..032b1390db 100644 --- a/src/csharp/Grpc.Core/ServerCallContext.cs +++ b/src/csharp/Grpc.Core/ServerCallContext.cs @@ -65,7 +65,7 @@ namespace Grpc.Core this.cancellationToken = cancellationToken; } - /// <summary> Name of method called in this RPC. </summary> + /// <summary>Name of method called in this RPC.</summary> public string Method { get @@ -74,7 +74,7 @@ namespace Grpc.Core } } - /// <summary> Name of host called in this RPC. </summary> + /// <summary>Name of host called in this RPC.</summary> public string Host { get @@ -83,7 +83,7 @@ namespace Grpc.Core } } - /// <summary> Address of the remote endpoint in URI format. </summary> + /// <summary>Address of the remote endpoint in URI format.</summary> public string Peer { get @@ -92,7 +92,7 @@ namespace Grpc.Core } } - /// <summary> Deadline for this RPC. </summary> + /// <summary>Deadline for this RPC.</summary> public DateTime Deadline { get @@ -101,7 +101,7 @@ namespace Grpc.Core } } - /// <summary> Initial metadata sent by client. </summary> + /// <summary>Initial metadata sent by client.</summary> public Metadata RequestHeaders { get @@ -110,8 +110,7 @@ namespace Grpc.Core } } - // TODO(jtattermusch): support signalling cancellation. - /// <summary> Cancellation token signals when call is cancelled. </summary> + ///<summary>Cancellation token signals when call is cancelled.</summary> public CancellationToken CancellationToken { get @@ -120,7 +119,7 @@ namespace Grpc.Core } } - /// <summary> Trailers to send back to client after RPC finishes.</summary> + /// <summary>Trailers to send back to client after RPC finishes.</summary> public Metadata ResponseTrailers { get diff --git a/src/csharp/Grpc.Examples/MathGrpc.cs b/src/csharp/Grpc.Examples/MathGrpc.cs index 67827e7b4f..4941ff35f7 100644 --- a/src/csharp/Grpc.Examples/MathGrpc.cs +++ b/src/csharp/Grpc.Examples/MathGrpc.cs @@ -19,24 +19,28 @@ namespace math { static readonly Method<global::math.DivArgs, global::math.DivReply> __Method_Div = new Method<global::math.DivArgs, global::math.DivReply>( MethodType.Unary, + __ServiceName, "Div", __Marshaller_DivArgs, __Marshaller_DivReply); static readonly Method<global::math.DivArgs, global::math.DivReply> __Method_DivMany = new Method<global::math.DivArgs, global::math.DivReply>( MethodType.DuplexStreaming, + __ServiceName, "DivMany", __Marshaller_DivArgs, __Marshaller_DivReply); static readonly Method<global::math.FibArgs, global::math.Num> __Method_Fib = new Method<global::math.FibArgs, global::math.Num>( MethodType.ServerStreaming, + __ServiceName, "Fib", __Marshaller_FibArgs, __Marshaller_Num); static readonly Method<global::math.Num, global::math.Num> __Method_Sum = new Method<global::math.Num, global::math.Num>( MethodType.ClientStreaming, + __ServiceName, "Sum", __Marshaller_Num, __Marshaller_Num); @@ -45,10 +49,15 @@ namespace math { public interface IMathClient { global::math.DivReply Div(global::math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + global::math.DivReply Div(global::math.DivArgs request, CallOptions options); AsyncUnaryCall<global::math.DivReply> DivAsync(global::math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncUnaryCall<global::math.DivReply> DivAsync(global::math.DivArgs request, CallOptions options); AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(CallOptions options); AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, CallOptions options); AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(CallOptions options); } // server-side interface @@ -68,28 +77,53 @@ namespace math { } public global::math.DivReply Div(global::math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Div, headers, deadline); - return Calls.BlockingUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_Div, new CallOptions(headers, deadline, cancellationToken)); + return Calls.BlockingUnaryCall(call, request); + } + public global::math.DivReply Div(global::math.DivArgs request, CallOptions options) + { + var call = CreateCall(__Method_Div, options); + return Calls.BlockingUnaryCall(call, request); } public AsyncUnaryCall<global::math.DivReply> DivAsync(global::math.DivArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Div, headers, deadline); - return Calls.AsyncUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_Div, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncUnaryCall(call, request); + } + public AsyncUnaryCall<global::math.DivReply> DivAsync(global::math.DivArgs request, CallOptions options) + { + var call = CreateCall(__Method_Div, options); + return Calls.AsyncUnaryCall(call, request); } public AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_DivMany, headers, deadline); - return Calls.AsyncDuplexStreamingCall(call, cancellationToken); + var call = CreateCall(__Method_DivMany, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncDuplexStreamingCall(call); + } + public AsyncDuplexStreamingCall<global::math.DivArgs, global::math.DivReply> DivMany(CallOptions options) + { + var call = CreateCall(__Method_DivMany, options); + return Calls.AsyncDuplexStreamingCall(call); } public AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Fib, headers, deadline); - return Calls.AsyncServerStreamingCall(call, request, cancellationToken); + var call = CreateCall(__Method_Fib, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncServerStreamingCall(call, request); + } + public AsyncServerStreamingCall<global::math.Num> Fib(global::math.FibArgs request, CallOptions options) + { + var call = CreateCall(__Method_Fib, options); + return Calls.AsyncServerStreamingCall(call, request); } public AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Sum, headers, deadline); - return Calls.AsyncClientStreamingCall(call, cancellationToken); + var call = CreateCall(__Method_Sum, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncClientStreamingCall(call); + } + public AsyncClientStreamingCall<global::math.Num, global::math.Num> Sum(CallOptions options) + { + var call = CreateCall(__Method_Sum, options); + return Calls.AsyncClientStreamingCall(call); } } diff --git a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs index 892cdb3f04..0dabc91f7c 100644 --- a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs +++ b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs @@ -17,6 +17,7 @@ namespace Grpc.Health.V1Alpha { static readonly Method<global::Grpc.Health.V1Alpha.HealthCheckRequest, global::Grpc.Health.V1Alpha.HealthCheckResponse> __Method_Check = new Method<global::Grpc.Health.V1Alpha.HealthCheckRequest, global::Grpc.Health.V1Alpha.HealthCheckResponse>( MethodType.Unary, + __ServiceName, "Check", __Marshaller_HealthCheckRequest, __Marshaller_HealthCheckResponse); @@ -25,7 +26,9 @@ namespace Grpc.Health.V1Alpha { public interface IHealthClient { global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CallOptions options); AsyncUnaryCall<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncUnaryCall<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CallOptions options); } // server-side interface @@ -42,13 +45,23 @@ namespace Grpc.Health.V1Alpha { } public global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Check, headers, deadline); - return Calls.BlockingUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_Check, new CallOptions(headers, deadline, cancellationToken)); + return Calls.BlockingUnaryCall(call, request); + } + public global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CallOptions options) + { + var call = CreateCall(__Method_Check, options); + return Calls.BlockingUnaryCall(call, request); } public AsyncUnaryCall<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_Check, headers, deadline); - return Calls.AsyncUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_Check, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncUnaryCall(call, request); + } + public AsyncUnaryCall<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CallOptions options) + { + var call = CreateCall(__Method_Check, options); + return Calls.AsyncUnaryCall(call, request); } } diff --git a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs index ddcd0c2958..697acb53d8 100644 --- a/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs +++ b/src/csharp/Grpc.IntegrationTesting/TestGrpc.cs @@ -22,36 +22,42 @@ namespace grpc.testing { static readonly Method<global::grpc.testing.Empty, global::grpc.testing.Empty> __Method_EmptyCall = new Method<global::grpc.testing.Empty, global::grpc.testing.Empty>( MethodType.Unary, + __ServiceName, "EmptyCall", __Marshaller_Empty, __Marshaller_Empty); static readonly Method<global::grpc.testing.SimpleRequest, global::grpc.testing.SimpleResponse> __Method_UnaryCall = new Method<global::grpc.testing.SimpleRequest, global::grpc.testing.SimpleResponse>( MethodType.Unary, + __ServiceName, "UnaryCall", __Marshaller_SimpleRequest, __Marshaller_SimpleResponse); static readonly Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> __Method_StreamingOutputCall = new Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse>( MethodType.ServerStreaming, + __ServiceName, "StreamingOutputCall", __Marshaller_StreamingOutputCallRequest, __Marshaller_StreamingOutputCallResponse); static readonly Method<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> __Method_StreamingInputCall = new Method<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse>( MethodType.ClientStreaming, + __ServiceName, "StreamingInputCall", __Marshaller_StreamingInputCallRequest, __Marshaller_StreamingInputCallResponse); static readonly Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> __Method_FullDuplexCall = new Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse>( MethodType.DuplexStreaming, + __ServiceName, "FullDuplexCall", __Marshaller_StreamingOutputCallRequest, __Marshaller_StreamingOutputCallResponse); static readonly Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> __Method_HalfDuplexCall = new Method<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse>( MethodType.DuplexStreaming, + __ServiceName, "HalfDuplexCall", __Marshaller_StreamingOutputCallRequest, __Marshaller_StreamingOutputCallResponse); @@ -60,13 +66,21 @@ namespace grpc.testing { public interface ITestServiceClient { global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, CallOptions options); AsyncUnaryCall<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncUnaryCall<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, CallOptions options); global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, CallOptions options); AsyncUnaryCall<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncUnaryCall<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, CallOptions options); AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, CallOptions options); AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(CallOptions options); AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(CallOptions options); AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)); + AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(CallOptions options); } // server-side interface @@ -88,43 +102,83 @@ namespace grpc.testing { } public global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_EmptyCall, headers, deadline); - return Calls.BlockingUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_EmptyCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.BlockingUnaryCall(call, request); + } + public global::grpc.testing.Empty EmptyCall(global::grpc.testing.Empty request, CallOptions options) + { + var call = CreateCall(__Method_EmptyCall, options); + return Calls.BlockingUnaryCall(call, request); } public AsyncUnaryCall<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_EmptyCall, headers, deadline); - return Calls.AsyncUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_EmptyCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncUnaryCall(call, request); + } + public AsyncUnaryCall<global::grpc.testing.Empty> EmptyCallAsync(global::grpc.testing.Empty request, CallOptions options) + { + var call = CreateCall(__Method_EmptyCall, options); + return Calls.AsyncUnaryCall(call, request); } public global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_UnaryCall, headers, deadline); - return Calls.BlockingUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_UnaryCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.BlockingUnaryCall(call, request); + } + public global::grpc.testing.SimpleResponse UnaryCall(global::grpc.testing.SimpleRequest request, CallOptions options) + { + var call = CreateCall(__Method_UnaryCall, options); + return Calls.BlockingUnaryCall(call, request); } public AsyncUnaryCall<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_UnaryCall, headers, deadline); - return Calls.AsyncUnaryCall(call, request, cancellationToken); + var call = CreateCall(__Method_UnaryCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncUnaryCall(call, request); + } + public AsyncUnaryCall<global::grpc.testing.SimpleResponse> UnaryCallAsync(global::grpc.testing.SimpleRequest request, CallOptions options) + { + var call = CreateCall(__Method_UnaryCall, options); + return Calls.AsyncUnaryCall(call, request); } public AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_StreamingOutputCall, headers, deadline); - return Calls.AsyncServerStreamingCall(call, request, cancellationToken); + var call = CreateCall(__Method_StreamingOutputCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncServerStreamingCall(call, request); + } + public AsyncServerStreamingCall<global::grpc.testing.StreamingOutputCallResponse> StreamingOutputCall(global::grpc.testing.StreamingOutputCallRequest request, CallOptions options) + { + var call = CreateCall(__Method_StreamingOutputCall, options); + return Calls.AsyncServerStreamingCall(call, request); } public AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_StreamingInputCall, headers, deadline); - return Calls.AsyncClientStreamingCall(call, cancellationToken); + var call = CreateCall(__Method_StreamingInputCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncClientStreamingCall(call); + } + public AsyncClientStreamingCall<global::grpc.testing.StreamingInputCallRequest, global::grpc.testing.StreamingInputCallResponse> StreamingInputCall(CallOptions options) + { + var call = CreateCall(__Method_StreamingInputCall, options); + return Calls.AsyncClientStreamingCall(call); } public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_FullDuplexCall, headers, deadline); - return Calls.AsyncDuplexStreamingCall(call, cancellationToken); + var call = CreateCall(__Method_FullDuplexCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncDuplexStreamingCall(call); + } + public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> FullDuplexCall(CallOptions options) + { + var call = CreateCall(__Method_FullDuplexCall, options); + return Calls.AsyncDuplexStreamingCall(call); } public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default(CancellationToken)) { - var call = CreateCall(__ServiceName, __Method_HalfDuplexCall, headers, deadline); - return Calls.AsyncDuplexStreamingCall(call, cancellationToken); + var call = CreateCall(__Method_HalfDuplexCall, new CallOptions(headers, deadline, cancellationToken)); + return Calls.AsyncDuplexStreamingCall(call); + } + public AsyncDuplexStreamingCall<global::grpc.testing.StreamingOutputCallRequest, global::grpc.testing.StreamingOutputCallResponse> HalfDuplexCall(CallOptions options) + { + var call = CreateCall(__Method_HalfDuplexCall, options); + return Calls.AsyncDuplexStreamingCall(call); } } diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c index 37864a62a5..048887bc12 100644 --- a/src/csharp/ext/grpc_csharp_ext.c +++ b/src/csharp/ext/grpc_csharp_ext.c @@ -379,7 +379,8 @@ GPR_EXPORT grpc_call *GPR_CALLTYPE grpcsharp_channel_create_call(grpc_channel *channel, grpc_completion_queue *cq, const char *method, const char *host, gpr_timespec deadline) { - return grpc_channel_create_call(channel, cq, method, host, deadline); + return grpc_channel_create_call(channel, NULL, GRPC_PROPAGATE_DEFAULTS, cq, + method, host, deadline); } GPR_EXPORT grpc_connectivity_state GPR_CALLTYPE diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc index dc45c8d8ae..fe585a0d4f 100644 --- a/src/node/ext/call.cc +++ b/src/node/ext/call.cc @@ -511,8 +511,9 @@ NAN_METHOD(Call::New) { double deadline = args[2]->NumberValue(); grpc_channel *wrapped_channel = channel->GetWrappedChannel(); grpc_call *wrapped_call = grpc_channel_create_call( - wrapped_channel, CompletionQueueAsyncWorker::GetQueue(), *method, - channel->GetHost(), MillisecondsToTimespec(deadline)); + wrapped_channel, NULL, GRPC_PROPAGATE_DEFAULTS, + CompletionQueueAsyncWorker::GetQueue(), *method, channel->GetHost(), + MillisecondsToTimespec(deadline)); call = new Call(wrapped_call); args.This()->SetHiddenValue(NanNew("channel_"), channel_object); } diff --git a/src/objective-c/GRPCClient/GRPCCall+Tests.h b/src/objective-c/GRPCClient/GRPCCall+Tests.h new file mode 100644 index 0000000000..3d617b05d9 --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCall+Tests.h @@ -0,0 +1,45 @@ +/* + * + * 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 "GRPCCall.h" + +@interface GRPCCall (Tests) + +// Establish all SSL connections to the provided host using the passed SSL target name and the root +// certificates found in the file at |certsPath|. +// Must be called before any gRPC call to that host is made. ++ (void)useTestCertsPath:(NSString *)certsPath + testName:(NSString *)testName + forHost:(NSString *)host; + +@end diff --git a/src/objective-c/GRPCClient/GRPCCall+Tests.m b/src/objective-c/GRPCClient/GRPCCall+Tests.m new file mode 100644 index 0000000000..7c5b81d661 --- /dev/null +++ b/src/objective-c/GRPCClient/GRPCCall+Tests.m @@ -0,0 +1,47 @@ +/* + * + * 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 "GRPCCall+Tests.h" + +#import "private/GRPCHost.h" + +@implementation GRPCCall (Tests) ++ (void)useTestCertsPath:(NSString *)certsPath + testName:(NSString *)testName + forHost:(NSString *)host { + GRPCHost *hostConfig = [GRPCHost hostWithAddress:host]; + hostConfig.secure = YES; + hostConfig.pathToCertificates = certsPath; + hostConfig.hostNameOverride = testName; +} +@end diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index 9d9648ae28..5f7d74bca8 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -37,7 +37,6 @@ #include <grpc/support/time.h> #import <RxLibrary/GRXConcurrentWriteable.h> -#import "private/GRPCChannel.h" #import "private/GRPCWrappedCall.h" #import "private/NSData+GRPC.h" #import "private/NSDictionary+GRPC.h" @@ -70,8 +69,6 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey"; GRPCWrappedCall *_wrappedCall; dispatch_once_t _callAlreadyInvoked; - GRPCChannel *_channel; - // The C gRPC library has less guarantees on the ordering of events than we // do. Particularly, in the face of errors, there's no ordering guarantee at // all. This wrapper over our actual writeable ensures thread-safety and @@ -105,11 +102,10 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey"; format:@"The requests writer can't be already started."]; } if ((self = [super init])) { - _channel = [GRPCChannel channelToHost:host]; - - _wrappedCall = [[GRPCWrappedCall alloc] initWithChannel:_channel - path:path - host:host]; + _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:host path:path]; + if (!_wrappedCall) { + return nil; + } // Serial queue to invoke the non-reentrant methods of the grpc_call object. _callQueue = dispatch_queue_create("org.grpc.call", NULL); diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.h b/src/objective-c/GRPCClient/private/GRPCChannel.h index bc6a47d469..2a7b701576 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.h +++ b/src/objective-c/GRPCClient/private/GRPCChannel.h @@ -35,17 +35,12 @@ struct grpc_channel; -// Each separate instance of this class represents at least one TCP -// connection to the provided host. To create a grpc_call, pass the -// value of the unmanagedChannel property to grpc_channel_create_call. -// Release this object when the call is finished. +// Each separate instance of this class represents at least one TCP connection to the provided host. +// Create them using one of the subclasses |GRPCSecureChannel| and |GRPCUnsecuredChannel|. @interface GRPCChannel : NSObject @property(nonatomic, readonly) struct grpc_channel *unmanagedChannel; -// Convenience constructor to allow for reuse of connections. -+ (instancetype)channelToHost:(NSString *)host; - -- (instancetype)initWithHost:(NSString *)host NS_DESIGNATED_INITIALIZER; - +// This initializer takes ownership of the passed channel, and will destroy it when this object is +// deallocated. It's illegal to pass the same grpc_channel to two different GRPCChannel objects. - (instancetype)initWithChannel:(struct grpc_channel *)unmanagedChannel NS_DESIGNATED_INITIALIZER; @end diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.m b/src/objective-c/GRPCClient/private/GRPCChannel.m index af4326332f..4366e63320 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCChannel.m @@ -35,53 +35,17 @@ #include <grpc/grpc.h> -#import "GRPCSecureChannel.h" -#import "GRPCUnsecuredChannel.h" - @implementation GRPCChannel -+ (instancetype)channelToHost:(NSString *)host { - // TODO(mlumish): Investigate whether a cache with strong links is a good idea - static NSMutableDictionary *channelCache; - static dispatch_once_t cacheInitialization; - dispatch_once(&cacheInitialization, ^{ - channelCache = [NSMutableDictionary dictionary]; - }); - GRPCChannel *channel = channelCache[host]; - if (!channel) { - channel = [[self alloc] initWithHost:host]; - channelCache[host] = channel; - } - return channel; -} - - (instancetype)init { - return [self initWithHost:nil]; + return [self initWithChannel:NULL]; } -- (instancetype)initWithHost:(NSString *)host { - if (![host rangeOfString:@"://"].length) { - // No scheme provided; assume https. - host = [@"https://" stringByAppendingString:host]; - } - NSURL *hostURL = [NSURL URLWithString:host]; - if (!hostURL) { - [NSException raise:NSInvalidArgumentException format:@"Invalid URL: %@", host]; +// Designated initializer +- (instancetype)initWithChannel:(grpc_channel *)unmanagedChannel { + if (!unmanagedChannel) { + return nil; } - if ([hostURL.scheme isEqualToString:@"https"]) { - host = [@[hostURL.host, hostURL.port ?: @443] componentsJoinedByString:@":"]; - return [[GRPCSecureChannel alloc] initWithHost:host]; - } - if ([hostURL.scheme isEqualToString:@"http"]) { - host = [@[hostURL.host, hostURL.port ?: @80] componentsJoinedByString:@":"]; - return [[GRPCUnsecuredChannel alloc] initWithHost:host]; - } - [NSException raise:NSInvalidArgumentException - format:@"URL scheme %@ isn't supported.", hostURL.scheme]; - return nil; // silence warning. -} - -- (instancetype)initWithChannel:(struct grpc_channel *)unmanagedChannel { if ((self = [super init])) { _unmanagedChannel = unmanagedChannel; } @@ -89,12 +53,8 @@ } - (void)dealloc { - // _unmanagedChannel is NULL when deallocating an object of the base class (because the - // initializer returns a different object). - if (_unmanagedChannel) { - // TODO(jcanizales): Be sure to add a test with a server that closes the connection prematurely, - // as in the past that made this call to crash. - grpc_channel_destroy(_unmanagedChannel); - } + // TODO(jcanizales): Be sure to add a test with a server that closes the connection prematurely, + // as in the past that made this call to crash. + grpc_channel_destroy(_unmanagedChannel); } @end diff --git a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m index 12535c9616..696069c200 100644 --- a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m +++ b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m @@ -38,8 +38,6 @@ @implementation GRPCCompletionQueue + (instancetype)completionQueue { - // TODO(jcanizales): Reuse completion queues to consume only one thread, - // instead of one per call. return [[self alloc] init]; } diff --git a/src/objective-c/GRPCClient/private/GRPCHost.h b/src/objective-c/GRPCClient/private/GRPCHost.h new file mode 100644 index 0000000000..f0bbd53023 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCHost.h @@ -0,0 +1,58 @@ +/* + * + * 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> + +@class GRPCCompletionQueue; +struct grpc_call; + +@interface GRPCHost : NSObject + +@property(nonatomic, readonly) NSString *address; + +// The following properties should only be modified for testing: + +@property(nonatomic, getter=isSecure) BOOL secure; + +@property(nonatomic, copy) NSString *pathToCertificates; +@property(nonatomic, copy) NSString *hostNameOverride; + +// Host objects initialized with the same address are the same. ++ (instancetype)hostWithAddress:(NSString *)address; +- (instancetype)initWithAddress:(NSString *)address NS_DESIGNATED_INITIALIZER; + +// Create a grpc_call object to the provided path on this host. +- (struct grpc_call *)unmanagedCallWithPath:(NSString *)path + completionQueue:(GRPCCompletionQueue *)queue; + +@end diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCHost.m new file mode 100644 index 0000000000..5d9c48a524 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCHost.m @@ -0,0 +1,134 @@ +/* + * + * 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 "GRPCHost.h" + +#include <grpc/grpc.h> + +#import "GRPCChannel.h" +#import "GRPCCompletionQueue.h" +#import "GRPCSecureChannel.h" +#import "GRPCUnsecuredChannel.h" + +@interface GRPCHost () +// TODO(mlumish): Investigate whether caching channels with strong links is a good idea. +@property(nonatomic, strong) GRPCChannel *channel; +@end + +@implementation GRPCHost + ++ (instancetype)hostWithAddress:(NSString *)address { + return [[self alloc] initWithAddress:address]; +} + +- (instancetype)init { + return [self initWithAddress:nil]; +} + +// Default initializer. +- (instancetype)initWithAddress:(NSString *)address { + + // Verify and normalize the address, and decide whether to use SSL. + if (![address rangeOfString:@"://"].length) { + // No scheme provided; assume https. + address = [@"https://" stringByAppendingString:address]; + } + NSURL *hostURL = [NSURL URLWithString:address]; + if (!hostURL) { + [NSException raise:NSInvalidArgumentException format:@"Invalid URL: %@", address]; + } + NSString *scheme = hostURL.scheme; + if (![scheme isEqualToString:@"https"] && ![scheme isEqualToString:@"http"]) { + [NSException raise:NSInvalidArgumentException format:@"URL scheme %@ isn't supported.", scheme]; + } + // If the user didn't specify a port (hostURL.port is nil), provide a default one. + NSNumber *port = hostURL.port ?: [scheme isEqualToString:@"https"] ? @443 : @80; + address = [@[hostURL.host, port] componentsJoinedByString:@":"]; + + // Look up the GRPCHost in the cache. + static NSMutableDictionary *hostCache; + static dispatch_once_t cacheInitialization; + dispatch_once(&cacheInitialization, ^{ + hostCache = [NSMutableDictionary dictionary]; + }); + @synchronized(hostCache) { + GRPCHost *cachedHost = hostCache[address]; + if (cachedHost) { + // We could verify here that the cached host uses the same protocol that we're expecting. But + // creating non-SSL channels by adding "http://" to the address is going away (to make the use + // of insecure channels less subtle), so it's not worth it now. + return cachedHost; + } + + if ((self = [super init])) { + _address = address; + _secure = [scheme isEqualToString:@"https"]; + hostCache[address] = self; + } + return self; + } +} + +- (grpc_call *)unmanagedCallWithPath:(NSString *)path completionQueue:(GRPCCompletionQueue *)queue { + if (!queue || !path || !self.channel) { + return NULL; + } + return grpc_channel_create_call(self.channel.unmanagedChannel, + NULL, GRPC_PROPAGATE_DEFAULTS, + queue.unmanagedQueue, + path.UTF8String, + self.hostName.UTF8String, + gpr_inf_future(GPR_CLOCK_REALTIME)); +} + +- (GRPCChannel *)channel { + // Create it lazily, because we don't want to open a connection just because someone is + // configuring a host. + if (!_channel) { + if (_secure) { + _channel = [[GRPCSecureChannel alloc] initWithHost:_address + pathToCertificates:_pathToCertificates + hostNameOverride:_hostNameOverride]; + } else { + _channel = [[GRPCUnsecuredChannel alloc] initWithHost:_address]; + } + } + return _channel; +} + +- (NSString *)hostName { + // TODO(jcanizales): Default to nil instead of _address when Issue #2635 is clarified. + return _hostNameOverride ?: _address; +} + +@end diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannel.h b/src/objective-c/GRPCClient/private/GRPCSecureChannel.h index d34ceaea0c..74257eb058 100644 --- a/src/objective-c/GRPCClient/private/GRPCSecureChannel.h +++ b/src/objective-c/GRPCClient/private/GRPCSecureChannel.h @@ -31,8 +31,23 @@ * */ +#include <grpc/grpc.h> + #import "GRPCChannel.h" +struct grpc_credentials; + @interface GRPCSecureChannel : GRPCChannel +- (instancetype)initWithHost:(NSString *)host; + +// Only in tests shouldn't pathToCertificates or hostNameOverride be nil. Passing nil for +// pathToCertificates results in using the default root certificates distributed with the library. +- (instancetype)initWithHost:(NSString *)host + pathToCertificates:(NSString *)path + hostNameOverride:(NSString *)hostNameOverride; +// The passed arguments aren't required to be valid beyond the invocation of this initializer. +- (instancetype)initWithHost:(NSString *)host + credentials:(struct grpc_credentials *)credentials + args:(grpc_channel_args *)args NS_DESIGNATED_INITIALIZER; @end diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannel.m b/src/objective-c/GRPCClient/private/GRPCSecureChannel.m index 43a8bd539e..9b4b6768f8 100644 --- a/src/objective-c/GRPCClient/private/GRPCSecureChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCSecureChannel.m @@ -33,28 +33,80 @@ #import "GRPCSecureChannel.h" -#import <grpc/grpc_security.h> +#include <grpc/grpc_security.h> + +// Returns NULL if the file at path couldn't be read. In that case, if errorPtr isn't NULL, +// *errorPtr will be an object describing what went wrong. +static grpc_credentials *CertificatesAtPath(NSString *path, NSError **errorPtr) { + NSString *certsContent = [NSString stringWithContentsOfFile:path + encoding:NSASCIIStringEncoding + error:errorPtr]; + if (!certsContent) { + // Passing NULL to grpc_ssl_credentials_create produces behavior we don't want, so return. + return NULL; + } + const char * asCString = [certsContent cStringUsingEncoding:NSASCIIStringEncoding]; + return grpc_ssl_credentials_create(asCString, NULL); +} @implementation GRPCSecureChannel - (instancetype)initWithHost:(NSString *)host { - static grpc_credentials *kCredentials; + return [self initWithHost:host pathToCertificates:nil hostNameOverride:nil]; +} + +- (instancetype)initWithHost:(NSString *)host + pathToCertificates:(NSString *)path + hostNameOverride:(NSString *)hostNameOverride { + // Load default SSL certificates once. + static grpc_credentials *kDefaultCertificates; static dispatch_once_t loading; dispatch_once(&loading, ^{ + NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem // Do not use NSBundle.mainBundle, as it's nil for tests of library projects. NSBundle *bundle = [NSBundle bundleForClass:self.class]; - NSString *certsPath = [bundle pathForResource:@"gRPCCertificates.bundle/roots" ofType:@"pem"]; - NSAssert(certsPath.length, - @"gRPCCertificates.bundle/roots.pem not found under %@. This file, with the root " - "certificates, is needed to establish TLS (HTTPS) connections.", bundle.bundlePath); - NSData *certsData = [NSData dataWithContentsOfFile:certsPath]; - NSAssert(certsData.length, @"No data read from %@", certsPath); - NSString *certsString = [[NSString alloc] initWithData:certsData encoding:NSUTF8StringEncoding]; - kCredentials = grpc_ssl_credentials_create(certsString.UTF8String, NULL); + NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"]; + NSError *error; + kDefaultCertificates = CertificatesAtPath(path, &error); + NSAssert(kDefaultCertificates, @"Could not read %@/%@.pem. This file, with the root " + "certificates, is needed to establish secure (TLS) connections. Because the file is " + "distributed with the gRPC library, this error is usually a sign that the library " + "wasn't configured correctly for your project. Error: %@", + bundle.bundlePath, defaultPath, error); }); - return (self = [super initWithChannel:grpc_secure_channel_create(kCredentials, - host.UTF8String, - NULL)]); + + //TODO(jcanizales): Add NSError** parameter to the initializer. + grpc_credentials *certificates = path ? CertificatesAtPath(path, NULL) : kDefaultCertificates; + if (!certificates) { + return nil; + } + + // Ritual to pass the SSL host name override to the C library. + grpc_channel_args channelArgs; + grpc_arg nameOverrideArg; + channelArgs.num_args = 1; + channelArgs.args = &nameOverrideArg; + nameOverrideArg.type = GRPC_ARG_STRING; + nameOverrideArg.key = GRPC_SSL_TARGET_NAME_OVERRIDE_ARG; + // Cast const away. Hope C gRPC doesn't modify it! + nameOverrideArg.value.string = (char *) hostNameOverride.UTF8String; + grpc_channel_args *args = hostNameOverride ? &channelArgs : NULL; + + return [self initWithHost:host credentials:certificates args:args]; +} + +- (instancetype)initWithHost:(NSString *)host + credentials:(grpc_credentials *)credentials + args:(grpc_channel_args *)args { + return (self = + [super initWithChannel:grpc_secure_channel_create(credentials, host.UTF8String, args)]); +} + +// TODO(jcanizales): GRPCSecureChannel and GRPCUnsecuredChannel are just convenience initializers +// for GRPCChannel. Move them into GRPCChannel, which will make the following unnecessary. +- (instancetype)initWithChannel:(grpc_channel *)unmanagedChannel { + [NSException raise:NSInternalInconsistencyException format:@"use another initializer"]; + return [self initWithHost:nil]; // silence warnings } @end diff --git a/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.h b/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.h index 9d89cfb541..8528be44c0 100644 --- a/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.h +++ b/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.h @@ -34,5 +34,5 @@ #import "GRPCChannel.h" @interface GRPCUnsecuredChannel : GRPCChannel - +- (instancetype)initWithHost:(NSString *)host NS_DESIGNATED_INITIALIZER; @end diff --git a/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.m b/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.m index 8518f78c5b..070a529629 100644 --- a/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCUnsecuredChannel.m @@ -41,4 +41,10 @@ return (self = [super initWithChannel:grpc_insecure_channel_create(host.UTF8String, NULL)]); } +// TODO(jcanizales): GRPCSecureChannel and GRPCUnsecuredChannel are just convenience initializers +// for GRPCChannel. Move them into GRPCChannel, which will make the following unnecessary. +- (instancetype)initWithChannel:(grpc_channel *)unmanagedChannel { + [NSException raise:NSInternalInconsistencyException format:@"use the other initializer"]; + return [self initWithHost:nil]; // silence warnings +} @end diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h index 18f8bb5531..da11cbb761 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h @@ -81,11 +81,12 @@ @end +#pragma mark GRPCWrappedCall + @interface GRPCWrappedCall : NSObject -- (instancetype)initWithChannel:(GRPCChannel *)channel - path:(NSString *)path - host:(NSString *)host NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithHost:(NSString *)host + path:(NSString *)path NS_DESIGNATED_INITIALIZER; - (void)startBatchWithOperations:(NSArray *)ops errorHandler:(void(^)())errorHandler; diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m index 1db63df77f..951c051036 100644 --- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m @@ -32,11 +32,14 @@ */ #import "GRPCWrappedCall.h" + #import <Foundation/Foundation.h> #include <grpc/grpc.h> #include <grpc/byte_buffer.h> #include <grpc/support/alloc.h> + #import "GRPCCompletionQueue.h" +#import "GRPCHost.h" #import "NSDictionary+GRPC.h" #import "NSData+GRPC.h" #import "NSError+GRPC.h" @@ -219,38 +222,36 @@ @end -@implementation GRPCWrappedCall{ - grpc_call *_call; +#pragma mark GRPCWrappedCall + +@implementation GRPCWrappedCall { GRPCCompletionQueue *_queue; + grpc_call *_call; } - (instancetype)init { - return [self initWithChannel:nil path:nil host:nil]; + return [self initWithHost:nil path:nil]; } -- (instancetype)initWithChannel:(GRPCChannel *)channel - path:(NSString *)path - host:(NSString *)host { - if (!channel || !path || !host) { +- (instancetype)initWithHost:(NSString *)host + path:(NSString *)path { + if (!path || !host) { [NSException raise:NSInvalidArgumentException - format:@"channel, method, and host cannot be nil."]; + format:@"path and host cannot be nil."]; } - + if (self = [super init]) { static dispatch_once_t initialization; dispatch_once(&initialization, ^{ grpc_init(); }); - + + // Each completion queue consumes one thread. There's a trade to be made between creating and + // consuming too many threads and having contention of multiple calls in a single completion + // queue. Currently we favor latency and use one per call. _queue = [GRPCCompletionQueue completionQueue]; - if (!_queue) { - return nil; - } - _call = grpc_channel_create_call(channel.unmanagedChannel, - _queue.unmanagedQueue, - path.UTF8String, - host.UTF8String, - gpr_inf_future(GPR_CLOCK_REALTIME)); + + _call = [[GRPCHost hostWithAddress:host] unmanagedCallWithPath:path completionQueue:_queue]; if (_call == NULL) { return nil; } @@ -299,4 +300,4 @@ grpc_call_destroy(_call); } -@end
\ No newline at end of file +@end diff --git a/src/objective-c/tests/InteropTests.h b/src/objective-c/tests/InteropTests.h new file mode 100644 index 0000000000..c675c8d241 --- /dev/null +++ b/src/objective-c/tests/InteropTests.h @@ -0,0 +1,44 @@ +/* + * + * 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 <XCTest/XCTest.h> + +// Implements tests as described here: +// https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md + +@interface InteropTests : XCTestCase +// Returns @"http://localhost:5050". +// Override in a subclass to perform the same tests against a different address. +// For interop tests, use @"grpc-test.sandbox.google.com". ++ (NSString *)host; +@end diff --git a/src/objective-c/tests/InteropTests.m b/src/objective-c/tests/InteropTests.m index b473d73422..a6611d27be 100644 --- a/src/objective-c/tests/InteropTests.m +++ b/src/objective-c/tests/InteropTests.m @@ -31,10 +31,9 @@ * */ -#include <grpc/status.h> +#import "InteropTests.h" -#import <UIKit/UIKit.h> -#import <XCTest/XCTest.h> +#include <grpc/status.h> #import <ProtoRPC/ProtoRPC.h> #import <RemoteTest/Empty.pbobjc.h> @@ -76,21 +75,18 @@ } @end -@interface InteropTests : XCTestCase -@end - @implementation InteropTests { RMTTestService *_service; } -// grpc-test.sandbox.google.com ++ (NSString *)host { + return @"http://localhost:5050"; +} - (void)setUp { - _service = [[RMTTestService alloc] initWithHost:@"http://localhost:5050"]; + _service = [[RMTTestService alloc] initWithHost:self.class.host]; } -// Tests as described here: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md - - (void)testEmptyUnaryRPC { __weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyUnary"]; diff --git a/src/objective-c/tests/InteropTestsLocalSSL.m b/src/objective-c/tests/InteropTestsLocalSSL.m new file mode 100644 index 0000000000..227ca79659 --- /dev/null +++ b/src/objective-c/tests/InteropTestsLocalSSL.m @@ -0,0 +1,62 @@ +/* + * + * 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. + * + */ + +// Repeat of the tests in InteropTests.m, but using SSL to communicate with the local server instead +// of cleartext. + +#import <GRPCClient/GRPCCall+Tests.h> + +#import "InteropTests.h" + +static NSString * const kLocalSSLHost = @"localhost:5051"; + +@interface InteropTestsLocalSSL : InteropTests +@end + +@implementation InteropTestsLocalSSL + ++ (NSString *)host { + return kLocalSSLHost; +} + +- (void)setUp { + // Register test server certificates and name. + NSBundle *bundle = [NSBundle bundleForClass:self.class]; + NSString *certsPath = [bundle pathForResource:@"TestCertificates.bundle/test-certificates" + ofType:@"pem"]; + [GRPCCall useTestCertsPath:certsPath testName:@"foo.test.google.fr" forHost:kLocalSSLHost]; + + [super setUp]; +} + +@end diff --git a/src/objective-c/tests/TestCertificates.bundle/test-certificates.pem b/src/objective-c/tests/TestCertificates.bundle/test-certificates.pem new file mode 100644 index 0000000000..6c8511a73c --- /dev/null +++ b/src/objective-c/tests/TestCertificates.bundle/test-certificates.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla +Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 +YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT +BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 ++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu +g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd +Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau +sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m +oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG +Dfcog5wrJytaQ6UA0wE= +-----END CERTIFICATE----- diff --git a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj index f13fb8288b..af98aba9c0 100644 --- a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj +++ b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 63423F511B151B77006CF63C /* RxLibraryUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63423F501B151B77006CF63C /* RxLibraryUnitTests.m */; }; 635697CD1B14FC11007A7283 /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635697CC1B14FC11007A7283 /* Tests.m */; }; 635ED2EC1B1A3BC400FDE5C3 /* InteropTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */; }; + 63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */; }; + 63E240D01B6C63DC005F3B0E /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; }; 7D8A186224D39101F90230F6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */; }; /* End PBXBuildFile section */ @@ -49,6 +51,9 @@ 635697CC1B14FC11007A7283 /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = "<group>"; }; 635697D81B14FC11007A7283 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTests.m; sourceTree = "<group>"; }; + 63E240CC1B6C4D3A005F3B0E /* InteropTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InteropTests.h; sourceTree = "<group>"; }; + 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalSSL.m; sourceTree = "<group>"; }; + 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TestCertificates.bundle; sourceTree = "<group>"; }; FF7B5489BCFE40111D768DD0 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; }; /* End PBXFileReference section */ @@ -93,6 +98,7 @@ isa = PBXGroup; children = ( 635697C91B14FC11007A7283 /* Tests */, + 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */, 635697C81B14FC11007A7283 /* Products */, 51E4650F34F854F41FF053B3 /* Pods */, 136D535E19727099B941D7B1 /* Frameworks */, @@ -111,12 +117,14 @@ 635697C91B14FC11007A7283 /* Tests */ = { isa = PBXGroup; children = ( + 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */, 6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */, 63175DFE1B1B9FAF00027841 /* LocalClearTextTests.m */, 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */, 63423F501B151B77006CF63C /* RxLibraryUnitTests.m */, 635697CC1B14FC11007A7283 /* Tests.m */, 635697D71B14FC11007A7283 /* Supporting Files */, + 63E240CC1B6C4D3A005F3B0E /* InteropTests.h */, ); name = Tests; sourceTree = SOURCE_ROOT; @@ -209,6 +217,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 63E240D01B6C63DC005F3B0E /* TestCertificates.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -254,6 +263,7 @@ files = ( 63175DFF1B1B9FAF00027841 /* LocalClearTextTests.m in Sources */, 63423F511B151B77006CF63C /* RxLibraryUnitTests.m in Sources */, + 63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */, 6312AE4E1B1BF49B00341DEE /* GRPCClientTests.m in Sources */, 635ED2EC1B1A3BC400FDE5C3 /* InteropTests.m in Sources */, ); diff --git a/src/objective-c/tests/run_tests.sh b/src/objective-c/tests/run_tests.sh index b13c0f0633..7b133c1782 100755 --- a/src/objective-c/tests/run_tests.sh +++ b/src/objective-c/tests/run_tests.sh @@ -37,7 +37,8 @@ cd $(dirname $0) # Run the tests server. ../../../bins/$CONFIG/interop_server --port=5050 & -# Kill it when this script exits. +../../../bins/$CONFIG/interop_server --port=5051 --enable_ssl & +# Kill them when this script exits. trap 'kill -9 `jobs -p`' EXIT # xcodebuild is very verbose. We filter its output and tell Bash to fail if any diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c index 1f76c7359d..01ec909b79 100644 --- a/src/php/ext/grpc/call.c +++ b/src/php/ext/grpc/call.c @@ -240,8 +240,8 @@ PHP_METHOD(Call, __construct) { (wrapped_grpc_timeval *)zend_object_store_get_object( deadline_obj TSRMLS_CC); call->wrapped = grpc_channel_create_call( - channel->wrapped, completion_queue, method, channel->target, - deadline->wrapped); + channel->wrapped, NULL, GRPC_PROPAGATE_DEFAULTS, completion_queue, method, + channel->target, deadline->wrapped); } /** diff --git a/src/python/grpcio/grpc/_adapter/_c/types/channel.c b/src/python/grpcio/grpc/_adapter/_c/types/channel.c index feb256cf00..963104742f 100644 --- a/src/python/grpcio/grpc/_adapter/_c/types/channel.c +++ b/src/python/grpcio/grpc/_adapter/_c/types/channel.c @@ -128,7 +128,7 @@ Call *pygrpc_Channel_create_call( } call = pygrpc_Call_new_empty(cq); call->c_call = grpc_channel_create_call( - self->c_chan, cq->c_cq, method, host, + self->c_chan, NULL, GRPC_PROPAGATE_DEFAULTS, cq->c_cq, method, host, pygrpc_cast_double_to_gpr_timespec(deadline)); return call; } diff --git a/src/ruby/ext/grpc/rb_channel.c b/src/ruby/ext/grpc/rb_channel.c index 0cb6fa2f80..a0663607c2 100644 --- a/src/ruby/ext/grpc/rb_channel.c +++ b/src/ruby/ext/grpc/rb_channel.c @@ -212,10 +212,10 @@ static VALUE grpc_rb_channel_create_call(VALUE self, VALUE cqueue, VALUE method, return Qnil; } - call = - grpc_channel_create_call(ch, cq, method_chars, host_chars, - grpc_rb_time_timeval(deadline, - /* absolute time */ 0)); + call = grpc_channel_create_call(ch, NULL, GRPC_PROPAGATE_DEFAULTS, cq, + method_chars, host_chars, + grpc_rb_time_timeval(deadline, + /* absolute time */ 0)); if (call == NULL) { rb_raise(rb_eRuntimeError, "cannot create call with method %s", method_chars); |