diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | build.json | 2 | ||||
-rw-r--r-- | include/grpc/grpc_http.h | 67 | ||||
-rw-r--r-- | src/core/channel/call_op_string.c | 3 | ||||
-rw-r--r-- | src/core/channel/channel_args.c | 8 | ||||
-rw-r--r-- | src/core/channel/channel_stack.c | 22 | ||||
-rw-r--r-- | src/core/channel/channel_stack.h | 5 | ||||
-rw-r--r-- | src/core/channel/connected_channel.c | 2 | ||||
-rw-r--r-- | src/core/channel/http_client_filter.c | 12 | ||||
-rw-r--r-- | src/core/channel/http_server_filter.c | 158 | ||||
-rw-r--r-- | src/core/security/auth.c | 2 | ||||
-rw-r--r-- | src/core/surface/call.c | 5 | ||||
-rw-r--r-- | src/core/surface/call.h | 2 | ||||
-rwxr-xr-x | src/ruby/bin/interop/interop_client.rb | 99 | ||||
-rwxr-xr-x | src/ruby/bin/interop/interop_server.rb | 19 | ||||
-rwxr-xr-x | src/ruby/grpc.gemspec | 1 | ||||
-rw-r--r-- | test/core/echo/server.c | 69 | ||||
-rw-r--r-- | tools/dockerfile/grpc_ruby/Dockerfile | 8 | ||||
-rwxr-xr-x | tools/gce_setup/grpc_docker.sh | 21 | ||||
-rwxr-xr-x | tools/gce_setup/shared_startup_funcs.sh | 38 | ||||
-rwxr-xr-x | tools/run_tests/run_lcov.sh | 2 | ||||
-rw-r--r-- | vsprojects/vs2013/gpr.vcxproj | 2 | ||||
-rw-r--r-- | vsprojects/vs2013/gpr.vcxproj.filters | 6 |
23 files changed, 458 insertions, 96 deletions
@@ -1233,7 +1233,6 @@ PUBLIC_HEADERS_C += \ include/grpc/support/port_platform.h \ include/grpc/support/slice.h \ include/grpc/support/slice_buffer.h \ - include/grpc/support/string.h \ include/grpc/support/sync.h \ include/grpc/support/sync_generic.h \ include/grpc/support/sync_posix.h \ diff --git a/build.json b/build.json index 4416c3fdd4..b90b1ca92e 100644 --- a/build.json +++ b/build.json @@ -202,7 +202,6 @@ "include/grpc/support/port_platform.h", "include/grpc/support/slice.h", "include/grpc/support/slice_buffer.h", - "include/grpc/support/string.h", "include/grpc/support/sync.h", "include/grpc/support/sync_generic.h", "include/grpc/support/sync_posix.h", @@ -218,6 +217,7 @@ "headers": [ "src/core/support/cpu.h", "src/core/support/murmur_hash.h", + "src/core/support/string.h", "src/core/support/thd_internal.h" ], "src": [ diff --git a/include/grpc/grpc_http.h b/include/grpc/grpc_http.h new file mode 100644 index 0000000000..b2ae5340a5 --- /dev/null +++ b/include/grpc/grpc_http.h @@ -0,0 +1,67 @@ +/* + * + * Copyright 2014, 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. + * + */ + +#ifndef __GRPC_GRPC_HTTP_H__ +#define __GRPC_GRPC_HTTP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* HTTP GET support. + + HTTP2 servers can publish statically generated text content served + via HTTP2 GET queries by publishing one or more grpc_http_server_page + elements via repeated GRPC_ARG_SERVE_OVER_HTTP elements in the servers + channel_args. + + This is not: + - a general purpose web server + - particularly fast + + It's useful for being able to serve up some static content (maybe some + javascript to be able to interact with your GRPC server?) */ + +typedef struct { + const char *path; + const char *content_type; + const char *content; +} grpc_http_server_page; + +#define GRPC_ARG_SERVE_OVER_HTTP "grpc.serve_over_http" + +#ifdef __cplusplus +} +#endif + +#endif /* __GRPC_GRPC_HTTP_H__ */ diff --git a/src/core/channel/call_op_string.c b/src/core/channel/call_op_string.c index d36d51e789..127ea707bf 100644 --- a/src/core/channel/call_op_string.c +++ b/src/core/channel/call_op_string.c @@ -83,6 +83,9 @@ char *grpc_call_op_string(grpc_call_op *op) { case GRPC_SEND_MESSAGE: gpr_strvec_add(&b, gpr_strdup("SEND_MESSAGE")); break; + case GRPC_SEND_PREFORMATTED_MESSAGE: + gpr_strvec_add(&b, gpr_strdup("SEND_PREFORMATTED_MESSAGE")); + break; case GRPC_SEND_FINISH: gpr_strvec_add(&b, gpr_strdup("SEND_FINISH")); break; diff --git a/src/core/channel/channel_args.c b/src/core/channel/channel_args.c index 04ce519ff3..5f16c7b7e9 100644 --- a/src/core/channel/channel_args.c +++ b/src/core/channel/channel_args.c @@ -52,7 +52,9 @@ static grpc_arg copy_arg(const grpc_arg *src) { break; case GRPC_ARG_POINTER: dst.value.pointer = src->value.pointer; - dst.value.pointer.p = src->value.pointer.copy(src->value.pointer.p); + dst.value.pointer.p = src->value.pointer.copy + ? src->value.pointer.copy(src->value.pointer.p) + : src->value.pointer.p; break; } return dst; @@ -91,7 +93,9 @@ void grpc_channel_args_destroy(grpc_channel_args *a) { case GRPC_ARG_INTEGER: break; case GRPC_ARG_POINTER: - a->args[i].value.pointer.destroy(a->args[i].value.pointer.p); + if (a->args[i].value.pointer.destroy) { + a->args[i].value.pointer.destroy(a->args[i].value.pointer.p); + } break; } gpr_free(a->args[i].key); diff --git a/src/core/channel/channel_stack.c b/src/core/channel/channel_stack.c index 5ee412bf7d..2721ed8cfc 100644 --- a/src/core/channel/channel_stack.c +++ b/src/core/channel/channel_stack.c @@ -202,6 +202,17 @@ grpc_call_stack *grpc_call_stack_from_top_element(grpc_call_element *elem) { static void do_nothing(void *user_data, grpc_op_error error) {} +void grpc_call_element_recv_metadata(grpc_call_element *cur_elem, + grpc_mdelem *mdelem) { + grpc_call_op metadata_op; + metadata_op.type = GRPC_RECV_METADATA; + metadata_op.dir = GRPC_CALL_UP; + metadata_op.done_cb = do_nothing; + metadata_op.user_data = NULL; + metadata_op.data.metadata = mdelem; + grpc_call_next_op(cur_elem, &metadata_op); +} + void grpc_call_element_send_metadata(grpc_call_element *cur_elem, grpc_mdelem *mdelem) { grpc_call_op metadata_op; @@ -209,7 +220,7 @@ void grpc_call_element_send_metadata(grpc_call_element *cur_elem, metadata_op.dir = GRPC_CALL_DOWN; metadata_op.done_cb = do_nothing; metadata_op.user_data = NULL; - metadata_op.data.metadata = grpc_mdelem_ref(mdelem); + metadata_op.data.metadata = mdelem; grpc_call_next_op(cur_elem, &metadata_op); } @@ -221,3 +232,12 @@ void grpc_call_element_send_cancel(grpc_call_element *cur_elem) { cancel_op.user_data = NULL; grpc_call_next_op(cur_elem, &cancel_op); } + +void grpc_call_element_send_finish(grpc_call_element *cur_elem) { + grpc_call_op cancel_op; + cancel_op.type = GRPC_SEND_FINISH; + cancel_op.dir = GRPC_CALL_DOWN; + cancel_op.done_cb = do_nothing; + cancel_op.user_data = NULL; + grpc_call_next_op(cur_elem, &cancel_op); +} diff --git a/src/core/channel/channel_stack.h b/src/core/channel/channel_stack.h index eb68102b43..754e16f4ff 100644 --- a/src/core/channel/channel_stack.h +++ b/src/core/channel/channel_stack.h @@ -69,6 +69,8 @@ typedef enum { GRPC_SEND_START, /* send a message to the channels peer */ GRPC_SEND_MESSAGE, + /* send a pre-formatted message to the channels peer */ + GRPC_SEND_PREFORMATTED_MESSAGE, /* send half-close to the channels peer */ GRPC_SEND_FINISH, /* request that more data be allowed through flow control */ @@ -292,7 +294,10 @@ void grpc_call_log_op(char *file, int line, gpr_log_severity severity, void grpc_call_element_send_metadata(grpc_call_element *cur_elem, grpc_mdelem *elem); +void grpc_call_element_recv_metadata(grpc_call_element *cur_elem, + grpc_mdelem *elem); void grpc_call_element_send_cancel(grpc_call_element *cur_elem); +void grpc_call_element_send_finish(grpc_call_element *cur_elem); #ifdef GRPC_CHANNEL_STACK_TRACE #define GRPC_CALL_LOG_OP(sev, elem, op) grpc_call_log_op(sev, elem, op) diff --git a/src/core/channel/connected_channel.c b/src/core/channel/connected_channel.c index 55486ed5c3..adbeec0fc6 100644 --- a/src/core/channel/connected_channel.c +++ b/src/core/channel/connected_channel.c @@ -140,6 +140,8 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem, grpc_sopb_add_begin_message(&calld->outgoing_sopb, grpc_byte_buffer_length(op->data.message), op->flags); + /* fall-through */ + case GRPC_SEND_PREFORMATTED_MESSAGE: copy_byte_buffer_to_stream_ops(op->data.message, &calld->outgoing_sopb); calld->outgoing_buffer_length_estimate += (5 + grpc_byte_buffer_length(op->data.message)); diff --git a/src/core/channel/http_client_filter.c b/src/core/channel/http_client_filter.c index b139b72795..1f6f3e0ca3 100644 --- a/src/core/channel/http_client_filter.c +++ b/src/core/channel/http_client_filter.c @@ -67,8 +67,8 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem, /* Send : prefixed headers, which have to be before any application * layer headers. */ calld->sent_headers = 1; - grpc_call_element_send_metadata(elem, channeld->method); - grpc_call_element_send_metadata(elem, channeld->scheme); + grpc_call_element_send_metadata(elem, grpc_mdelem_ref(channeld->method)); + grpc_call_element_send_metadata(elem, grpc_mdelem_ref(channeld->scheme)); } grpc_call_next_op(elem, op); break; @@ -76,12 +76,12 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem, if (!calld->sent_headers) { /* Send : prefixed headers, if we haven't already */ calld->sent_headers = 1; - grpc_call_element_send_metadata(elem, channeld->method); - grpc_call_element_send_metadata(elem, channeld->scheme); + grpc_call_element_send_metadata(elem, grpc_mdelem_ref(channeld->method)); + grpc_call_element_send_metadata(elem, grpc_mdelem_ref(channeld->scheme)); } /* Send non : prefixed headers */ - grpc_call_element_send_metadata(elem, channeld->te_trailers); - grpc_call_element_send_metadata(elem, channeld->content_type); + grpc_call_element_send_metadata(elem, grpc_mdelem_ref(channeld->te_trailers)); + grpc_call_element_send_metadata(elem, grpc_mdelem_ref(channeld->content_type)); grpc_call_next_op(elem, op); break; default: diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c index 19b9606b43..2658a6d42e 100644 --- a/src/core/channel/http_server_filter.c +++ b/src/core/channel/http_server_filter.c @@ -34,29 +34,80 @@ #include "src/core/channel/http_server_filter.h" #include <string.h> +#include <grpc/grpc_http.h> +#include <grpc/support/alloc.h> #include <grpc/support/log.h> +typedef enum { NOT_RECEIVED, POST, GET } known_method_type; + +typedef struct { + grpc_mdelem *path; + grpc_mdelem *content_type; + grpc_byte_buffer *content; +} gettable; + typedef struct call_data { - int sent_status; - int seen_scheme; - int seen_method; - int seen_te_trailers; + known_method_type seen_method; + gpr_uint8 sent_status; + gpr_uint8 seen_scheme; + gpr_uint8 seen_te_trailers; + grpc_mdelem *path; } call_data; typedef struct channel_data { grpc_mdelem *te_trailers; - grpc_mdelem *method; + grpc_mdelem *method_get; + grpc_mdelem *method_post; grpc_mdelem *http_scheme; grpc_mdelem *https_scheme; /* TODO(klempner): Remove this once we stop using it */ grpc_mdelem *grpc_scheme; grpc_mdelem *content_type; - grpc_mdelem *status; + grpc_mdelem *status_ok; + grpc_mdelem *status_not_found; + grpc_mdstr *path_key; + + size_t gettable_count; + gettable *gettables; } channel_data; /* used to silence 'variable not used' warnings */ static void ignore_unused(void *ignored) {} +/* Handle 'GET': not technically grpc, so probably a web browser hitting + us */ +static void payload_done(void *elem, grpc_op_error error) { + if (error == GRPC_OP_OK) { + grpc_call_element_send_finish(elem); + } +} + +static void handle_get(grpc_call_element *elem) { + channel_data *channeld = elem->channel_data; + call_data *calld = elem->call_data; + grpc_call_op op; + size_t i; + + for (i = 0; i < channeld->gettable_count; i++) { + if (channeld->gettables[i].path == calld->path) { + grpc_call_element_send_metadata(elem, + grpc_mdelem_ref(channeld->status_ok)); + grpc_call_element_send_metadata( + elem, grpc_mdelem_ref(channeld->gettables[i].content_type)); + op.type = GRPC_SEND_PREFORMATTED_MESSAGE; + op.dir = GRPC_CALL_DOWN; + op.flags = 0; + op.data.message = channeld->gettables[i].content; + op.done_cb = payload_done; + op.user_data = elem; + grpc_call_next_op(elem, &op); + } + } + grpc_call_element_send_metadata(elem, + grpc_mdelem_ref(channeld->status_not_found)); + grpc_call_element_send_finish(elem); +} + /* Called either: - in response to an API call (or similar) from above, to send something - a network event (or similar) from below, to receive something @@ -73,14 +124,17 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem, case GRPC_RECV_METADATA: /* Check if it is one of the headers we care about. */ if (op->data.metadata == channeld->te_trailers || - op->data.metadata == channeld->method || + op->data.metadata == channeld->method_get || + op->data.metadata == channeld->method_post || op->data.metadata == channeld->http_scheme || op->data.metadata == channeld->https_scheme || op->data.metadata == channeld->grpc_scheme || op->data.metadata == channeld->content_type) { /* swallow it */ - if (op->data.metadata == channeld->method) { - calld->seen_method = 1; + if (op->data.metadata == channeld->method_get) { + calld->seen_method = GET; + } else if (op->data.metadata == channeld->method_post) { + calld->seen_method = POST; } else if (op->data.metadata->key == channeld->http_scheme->key) { calld->seen_scheme = 1; } else if (op->data.metadata == channeld->te_trailers) { @@ -108,7 +162,7 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem, grpc_mdelem_unref(op->data.metadata); op->done_cb(op->user_data, GRPC_OP_OK); } else if (op->data.metadata->key == channeld->te_trailers->key || - op->data.metadata->key == channeld->method->key || + op->data.metadata->key == channeld->method_post->key || op->data.metadata->key == channeld->http_scheme->key || op->data.metadata->key == channeld->content_type->key) { gpr_log(GPR_ERROR, "Invalid %s: header: '%s'", @@ -120,6 +174,13 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem, grpc_mdelem_unref(op->data.metadata); op->done_cb(op->user_data, GRPC_OP_OK); grpc_call_element_send_cancel(elem); + } else if (op->data.metadata->key == channeld->path_key) { + if (calld->path != NULL) { + gpr_log(GPR_ERROR, "Received :path twice"); + grpc_mdelem_unref(calld->path); + } + calld->path = op->data.metadata; + op->done_cb(op->user_data, GRPC_OP_OK); } else { /* pass the event up */ grpc_call_next_op(elem, op); @@ -129,14 +190,21 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem, /* Have we seen the required http2 transport headers? (:method, :scheme, content-type, with :path and :authority covered at the channel level right now) */ - if (calld->seen_method && calld->seen_scheme && calld->seen_te_trailers) { + if (calld->seen_method == POST && calld->seen_scheme && + calld->seen_te_trailers && calld->path) { + grpc_call_element_recv_metadata(elem, calld->path); + calld->path = NULL; grpc_call_next_op(elem, op); + } else if (calld->seen_method == GET) { + handle_get(elem); } else { - if (!calld->seen_method) { + if (calld->seen_method == NOT_RECEIVED) { gpr_log(GPR_ERROR, "Missing :method header"); - } else if (!calld->seen_scheme) { + } + if (!calld->seen_scheme) { gpr_log(GPR_ERROR, "Missing :scheme header"); - } else if (!calld->seen_te_trailers) { + } + if (!calld->seen_te_trailers) { gpr_log(GPR_ERROR, "Missing te trailers header"); } /* Error this call out */ @@ -151,7 +219,8 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem, if (!calld->sent_status) { calld->sent_status = 1; /* status is reffed by grpc_call_element_send_metadata */ - grpc_call_element_send_metadata(elem, channeld->status); + grpc_call_element_send_metadata(elem, + grpc_mdelem_ref(channeld->status_ok)); } grpc_call_next_op(elem, op); break; @@ -189,9 +258,10 @@ static void init_call_elem(grpc_call_element *elem, ignore_unused(channeld); /* initialize members */ + calld->path = NULL; calld->sent_status = 0; calld->seen_scheme = 0; - calld->seen_method = 0; + calld->seen_method = NOT_RECEIVED; calld->seen_te_trailers = 0; } @@ -201,14 +271,20 @@ static void destroy_call_elem(grpc_call_element *elem) { call_data *calld = elem->call_data; channel_data *channeld = elem->channel_data; - ignore_unused(calld); ignore_unused(channeld); + + if (calld->path) { + grpc_mdelem_unref(calld->path); + } } /* Constructor for channel_data */ static void init_channel_elem(grpc_channel_element *elem, const grpc_channel_args *args, grpc_mdctx *mdctx, int is_first, int is_last) { + size_t i; + size_t gettable_capacity = 0; + /* grab pointers to our data from the channel element */ channel_data *channeld = elem->channel_data; @@ -220,13 +296,40 @@ static void init_channel_elem(grpc_channel_element *elem, /* initialize members */ channeld->te_trailers = grpc_mdelem_from_strings(mdctx, "te", "trailers"); - channeld->status = grpc_mdelem_from_strings(mdctx, ":status", "200"); - channeld->method = grpc_mdelem_from_strings(mdctx, ":method", "POST"); + channeld->status_ok = grpc_mdelem_from_strings(mdctx, ":status", "200"); + channeld->status_not_found = + grpc_mdelem_from_strings(mdctx, ":status", "404"); + channeld->method_post = grpc_mdelem_from_strings(mdctx, ":method", "POST"); + channeld->method_get = grpc_mdelem_from_strings(mdctx, ":method", "GET"); channeld->http_scheme = grpc_mdelem_from_strings(mdctx, ":scheme", "http"); channeld->https_scheme = grpc_mdelem_from_strings(mdctx, ":scheme", "https"); channeld->grpc_scheme = grpc_mdelem_from_strings(mdctx, ":scheme", "grpc"); + channeld->path_key = grpc_mdstr_from_string(mdctx, ":path"); channeld->content_type = grpc_mdelem_from_strings(mdctx, "content-type", "application/grpc"); + + /* initialize http download support */ + channeld->gettable_count = 0; + channeld->gettables = NULL; + for (i = 0; i < args->num_args; i++) { + if (0 == strcmp(args->args[i].key, GRPC_ARG_SERVE_OVER_HTTP)) { + gettable *g; + gpr_slice slice; + grpc_http_server_page *p = args->args[i].value.pointer.p; + if (channeld->gettable_count == gettable_capacity) { + gettable_capacity = + GPR_MAX(gettable_capacity * 3 / 2, gettable_capacity + 1); + channeld->gettables = + gpr_realloc(channeld->gettables, gettable_capacity * sizeof(gettable)); + } + g = &channeld->gettables[channeld->gettable_count++]; + g->path = grpc_mdelem_from_strings(mdctx, ":path", p->path); + g->content_type = + grpc_mdelem_from_strings(mdctx, "content-type", p->content_type); + slice = gpr_slice_from_copied_string(p->content); + g->content = grpc_byte_buffer_create(&slice, 1); + } + } } /* Destructor for channel data */ @@ -235,19 +338,18 @@ static void destroy_channel_elem(grpc_channel_element *elem) { channel_data *channeld = elem->channel_data; grpc_mdelem_unref(channeld->te_trailers); - grpc_mdelem_unref(channeld->status); - grpc_mdelem_unref(channeld->method); + grpc_mdelem_unref(channeld->status_ok); + grpc_mdelem_unref(channeld->status_not_found); + grpc_mdelem_unref(channeld->method_post); + grpc_mdelem_unref(channeld->method_get); grpc_mdelem_unref(channeld->http_scheme); grpc_mdelem_unref(channeld->https_scheme); grpc_mdelem_unref(channeld->grpc_scheme); grpc_mdelem_unref(channeld->content_type); + grpc_mdstr_unref(channeld->path_key); } const grpc_channel_filter grpc_http_server_filter = { - call_op, channel_op, - - sizeof(call_data), init_call_elem, destroy_call_elem, - - sizeof(channel_data), init_channel_elem, destroy_channel_elem, - - "http-server"}; + call_op, channel_op, sizeof(call_data), + init_call_elem, destroy_call_elem, sizeof(channel_data), + init_channel_elem, destroy_channel_elem, "http-server"}; diff --git a/src/core/security/auth.c b/src/core/security/auth.c index f743b25838..9d0c075bc3 100644 --- a/src/core/security/auth.c +++ b/src/core/security/auth.c @@ -57,7 +57,7 @@ static void on_credentials_metadata(void *user_data, grpc_mdelem **md_elems, grpc_call_element *elem = (grpc_call_element *)user_data; size_t i; for (i = 0; i < num_md; i++) { - grpc_call_element_send_metadata(elem, md_elems[i]); + grpc_call_element_send_metadata(elem, grpc_mdelem_ref(md_elems[i])); } grpc_call_next_op(elem, &((call_data *)elem->call_data)->op); } diff --git a/src/core/surface/call.c b/src/core/surface/call.c index 14d990df6a..bbd705b0c9 100644 --- a/src/core/surface/call.c +++ b/src/core/surface/call.c @@ -981,3 +981,8 @@ void grpc_call_set_deadline(grpc_call_element *elem, gpr_timespec deadline) { call->have_alarm = 1; grpc_alarm_init(&call->alarm, deadline, call_alarm, call, gpr_now()); } + +grpc_call_stack *grpc_call_get_call_stack(grpc_call *call) { + return CALL_STACK_FROM_CALL(call); +} + diff --git a/src/core/surface/call.h b/src/core/surface/call.h index 01605bb38a..804b387cb1 100644 --- a/src/core/surface/call.h +++ b/src/core/surface/call.h @@ -64,6 +64,8 @@ void grpc_call_client_initial_metadata_complete( void grpc_call_set_deadline(grpc_call_element *surface_element, gpr_timespec deadline); +grpc_call_stack *grpc_call_get_call_stack(grpc_call *call); + /* Given the top call_element, get the call object. */ grpc_call *grpc_call_from_top_element(grpc_call_element *surface_element); diff --git a/src/ruby/bin/interop/interop_client.rb b/src/ruby/bin/interop/interop_client.rb index 0ea7f376be..86739b7b67 100755 --- a/src/ruby/bin/interop/interop_client.rb +++ b/src/ruby/bin/interop/interop_client.rb @@ -54,6 +54,8 @@ require 'test/cpp/interop/test_services' require 'test/cpp/interop/messages' require 'test/cpp/interop/empty' +require 'signet/ssl_config' + # loads the certificates used to access the test server securely. def load_test_certs this_dir = File.expand_path(File.dirname(__FILE__)) @@ -62,21 +64,49 @@ def load_test_certs files.map { |f| File.open(File.join(data_dir, f)).read } end +# loads the certificates used to access the test server securely. +def load_prod_cert + fail 'could not find a production cert' if ENV['SSL_CERT_FILE'].nil? + p "loading prod certs from #{ENV['SSL_CERT_FILE']}" + File.open(ENV['SSL_CERT_FILE']).read +end + # creates a Credentials from the test certificates. def test_creds certs = load_test_certs GRPC::Core::Credentials.new(certs[0]) end +RX_CERT = /-----BEGIN CERTIFICATE-----\n.*?-----END CERTIFICATE-----\n/m + + +# creates a Credentials from the production certificates. +def prod_creds + cert_text = load_prod_cert + GRPC::Core::Credentials.new(cert_text) +end + # creates a test stub that accesses host:port securely. -def create_stub(host, port) +def create_stub(host, port, is_secure, host_override, use_test_ca) address = "#{host}:#{port}" - stub_opts = { - :creds => test_creds, - GRPC::Core::Channel::SSL_TARGET => 'foo.test.google.com' - } - logger.info("... connecting securely to #{address}") - Grpc::Testing::TestService::Stub.new(address, **stub_opts) + if is_secure + creds = nil + if use_test_ca + creds = test_creds + else + creds = prod_creds + end + + stub_opts = { + :creds => creds, + GRPC::Core::Channel::SSL_TARGET => host_override + } + logger.info("... connecting securely to #{address}") + Grpc::Testing::TestService::Stub.new(address, **stub_opts) + else + logger.info("... connecting insecurely to #{address}") + Grpc::Testing::TestService::Stub.new(address) + end end # produces a string of null chars (\0) of length l. @@ -133,20 +163,12 @@ class NamedTests @stub = stub end - # TESTING - # PASSED - # FAIL - # ruby server: fails protobuf-ruby can't pass an empty message def empty_unary resp = @stub.empty_call(Empty.new) assert resp.is_a?(Empty), 'empty_unary: invalid response' p 'OK: empty_unary' end - # TESTING - # PASSED - # ruby server - # FAILED def large_unary req_size, wanted_response_size = 271_828, 314_159 payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size)) @@ -163,10 +185,6 @@ class NamedTests p 'OK: large_unary' end - # TESTING: - # PASSED - # ruby server - # FAILED def client_streaming msg_sizes = [27_182, 8, 1828, 45_904] wanted_aggregate_size = 74_922 @@ -180,10 +198,6 @@ class NamedTests p 'OK: client_streaming' end - # TESTING: - # PASSED - # ruby server - # FAILED def server_streaming msg_sizes = [31_415, 9, 2653, 58_979] response_spec = msg_sizes.map { |s| ResponseParameters.new(size: s) } @@ -200,10 +214,6 @@ class NamedTests p 'OK: server_streaming' end - # TESTING: - # PASSED - # ruby server - # FAILED def ping_pong msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]] ppp = PingPongPlayer.new(msg_sizes) @@ -211,12 +221,23 @@ class NamedTests resps.each { |r| ppp.queue.push(r) } p 'OK: ping_pong' end + + def all + all_methods = NamedTests.instance_methods(false).map(&:to_s) + all_methods.each do |m| + next if m == 'all' || m.start_with?('assert') + p "TESTCASE: #{m}" + method(m).call + end + end end # validates the the command line options, returning them as a Hash. def parse_options options = { + 'secure' => false, 'server_host' => nil, + 'server_host_override' => nil, 'server_port' => nil, 'test_case' => nil } @@ -225,6 +246,10 @@ def parse_options opts.on('--server_host SERVER_HOST', 'server hostname') do |v| options['server_host'] = v end + opts.on('--server_host_override HOST_OVERRIDE', + 'override host via a HTTP header') do |v| + options['server_host_override'] = v + end opts.on('--server_port SERVER_PORT', 'server port') do |v| options['server_port'] = v end @@ -235,19 +260,33 @@ def parse_options " (#{test_case_list})") do |v| options['test_case'] = v end + opts.on('-s', '--use_tls', 'require a secure connection?') do |v| + options['secure'] = v + end + opts.on('-t', '--use_test_ca', + 'if secure, use the test certificate?') do |v| + options['use_test_ca'] = v + end end.parse! + _check_options(options) +end +def _check_options(opts) %w(server_host server_port test_case).each do |arg| - if options[arg].nil? + if opts[arg].nil? fail(OptionParser::MissingArgument, "please specify --#{arg}") end end - options + if opts['server_host_override'].nil? + opts['server_host_override'] = opts['server_host'] + end + opts end def main opts = parse_options - stub = create_stub(opts['server_host'], opts['server_port']) + stub = create_stub(opts['server_host'], opts['server_port'], opts['secure'], + opts['server_host_override'], opts['use_test_ca']) NamedTests.new(stub).method(opts['test_case']).call end diff --git a/src/ruby/bin/interop/interop_server.rb b/src/ruby/bin/interop/interop_server.rb index 83212823f6..cc4d260879 100755 --- a/src/ruby/bin/interop/interop_server.rb +++ b/src/ruby/bin/interop/interop_server.rb @@ -154,13 +154,17 @@ end # validates the the command line options, returning them as a Hash. def parse_options options = { - 'port' => nil + 'port' => nil, + 'secure' => false } OptionParser.new do |opts| opts.banner = 'Usage: --port port' opts.on('--port PORT', 'server port') do |v| options['port'] = v end + opts.on('-s', '--use_tls', 'require a secure connection?') do |v| + options['secure'] = v + end end.parse! if options['port'].nil? @@ -172,10 +176,15 @@ end def main opts = parse_options host = "0.0.0.0:#{opts['port']}" - s = GRPC::RpcServer.new(creds: test_server_creds) - s.add_http2_port(host, true) - logger.info("... running securely on #{host}") - + if opts['secure'] + s = GRPC::RpcServer.new(creds: test_server_creds) + s.add_http2_port(host, true) + logger.info("... running securely on #{host}") + else + s = GRPC::RpcServer.new + s.add_http2_port(host) + logger.info("... running insecurely on #{host}") + end s.handle(TestTarget) s.run end diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec index 450362f5a8..ffd084dc91 100755 --- a/src/ruby/grpc.gemspec +++ b/src/ruby/grpc.gemspec @@ -22,6 +22,7 @@ Gem::Specification.new do |s| s.add_dependency 'xray' s.add_dependency 'logging', '~> 1.8' s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.1' + s.add_dependency 'signet', '~> 0.5.1' s.add_dependency 'minitest', '~> 5.4' # reqd for interop tests s.add_development_dependency 'bundler', '~> 1.7' diff --git a/test/core/echo/server.c b/test/core/echo/server.c index 57b083779c..6b67334248 100644 --- a/test/core/echo/server.c +++ b/test/core/echo/server.c @@ -32,6 +32,8 @@ */ #include <grpc/grpc.h> +#include <grpc/grpc_http.h> +#include <grpc/grpc_security.h> #include <signal.h> #include <stdio.h> @@ -42,10 +44,12 @@ #include "src/core/support/string.h" #include "test/core/util/test_config.h" #include <grpc/support/alloc.h> +#include <grpc/support/cmdline.h> #include <grpc/support/host_port.h> #include <grpc/support/log.h> #include <grpc/support/time.h> #include "test/core/util/port.h" +#include "test/core/end2end/data/ssl_test_data.h" static grpc_completion_queue *cq; static grpc_server *server; @@ -83,29 +87,74 @@ static void sigint_handler(int x) { got_sigint = 1; } int main(int argc, char **argv) { grpc_event *ev; - char *addr; call_state *s; + char *addr_buf = NULL; + gpr_cmdline *cl; int shutdown_started = 0; int shutdown_finished = 0; - grpc_test_init(argc, argv); + int secure = 0; + char *addr = NULL; + + char *fake_argv[1]; + +#define MAX_ARGS 4 + grpc_arg arge[MAX_ARGS]; + grpc_arg *e; + grpc_channel_args args = {0, NULL}; + + grpc_http_server_page home_page = {"/", "text/html", + "<head>\n" + "<title>Echo Server</title>\n" + "</head>\n" + "<body>\n" + "Welcome to the world of the future!\n" + "</body>\n"}; + + GPR_ASSERT(argc >= 1); + fake_argv[0] = argv[0]; + grpc_test_init(1, fake_argv); grpc_init(); srand(clock()); - - if (argc == 2) { - addr = gpr_strdup(argv[1]); - } else { - gpr_join_host_port(&addr, "::", grpc_pick_unused_port_or_die()); + memset(arge, 0, sizeof(arge)); + args.args = arge; + + cl = gpr_cmdline_create("echo server"); + gpr_cmdline_add_string(cl, "bind", "Bind host:port", &addr); + gpr_cmdline_add_flag(cl, "secure", "Run with security?", &secure); + gpr_cmdline_parse(cl, argc, argv); + gpr_cmdline_destroy(cl); + + e = &arge[args.num_args++]; + e->type = GRPC_ARG_POINTER; + e->key = GRPC_ARG_SERVE_OVER_HTTP; + e->value.pointer.p = &home_page; + + if (addr == NULL) { + gpr_join_host_port(&addr_buf, "::", grpc_pick_unused_port_or_die()); + addr = addr_buf; } gpr_log(GPR_INFO, "creating server on: %s", addr); cq = grpc_completion_queue_create(); - server = grpc_server_create(cq, NULL); - GPR_ASSERT(grpc_server_add_http2_port(server, addr)); - gpr_free(addr); + if (secure) { + grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {test_server1_key, + test_server1_cert}; + grpc_server_credentials *ssl_creds = + grpc_ssl_server_credentials_create(NULL, &pem_key_cert_pair, 1); + server = grpc_secure_server_create(ssl_creds, cq, &args); + GPR_ASSERT(grpc_server_add_secure_http2_port(server, addr)); + grpc_server_credentials_release(ssl_creds); + } else { + server = grpc_server_create(cq, &args); + GPR_ASSERT(grpc_server_add_http2_port(server, addr)); + } grpc_server_start(server); + gpr_free(addr_buf); + addr = addr_buf = NULL; + request_call(); signal(SIGINT, sigint_handler); diff --git a/tools/dockerfile/grpc_ruby/Dockerfile b/tools/dockerfile/grpc_ruby/Dockerfile index c677ceffff..f01f81d539 100644 --- a/tools/dockerfile/grpc_ruby/Dockerfile +++ b/tools/dockerfile/grpc_ruby/Dockerfile @@ -6,6 +6,9 @@ RUN cd /var/local/git/grpc \ && git pull --recurse-submodules \ && git submodule update --init --recursive +# TODO: remove this, once make install is fixed +RUN touch /var/local/git/grpc/include/grpc/support/string.h + # Build the C core. RUN make install_c -C /var/local/git/grpc @@ -18,5 +21,8 @@ RUN /bin/bash -l -c 'cd /var/local/git/grpc/src/ruby && bundle && rake compile:g # - however, the interop server and client run OK, so this bug can be investigated # RUN /bin/bash -l -c 'cd /var/local/git/grpc/src/ruby && bundle && rake' +# Add a cacerts directory containing the Google root pem file, allowing the ruby client to access the production test instance +ADD cacerts cacerts + # Specify the default command such that the interop server runs on its known testing port -CMD ["/bin/bash", "-l", "-c", "ruby /var/local/git/grpc/src/ruby/bin/interop/interop_server.rb --port 8060"] +CMD ["/bin/bash", "-l", "-c", "ruby /var/local/git/grpc/src/ruby/bin/interop/interop_server.rb --use_tls --port 8060"] diff --git a/tools/gce_setup/grpc_docker.sh b/tools/gce_setup/grpc_docker.sh index 3c2c5ae67a..16d6ef40e3 100755 --- a/tools/gce_setup/grpc_docker.sh +++ b/tools/gce_setup/grpc_docker.sh @@ -723,10 +723,27 @@ grpc_cloud_prod_test() { grpc_interop_gen_ruby_cmd() { local cmd_prefix="sudo docker run grpc/ruby bin/bash -l -c" local test_script="/var/local/git/grpc/src/ruby/bin/interop/interop_client.rb" - local the_cmd="$cmd_prefix 'ruby $test_script $@'" + local the_cmd="$cmd_prefix 'ruby $test_script --use_test_ca --use_tls $@'" echo $the_cmd } + +# constructs the full dockerized java interop test cmd. +# +# call-seq: +# flags= .... # generic flags to include the command +# cmd=$($grpc_gen_test_cmd $flags) +grpc_cloud_prod_gen_ruby_cmd() { + local cmd_prefix="sudo docker run grpc/ruby bin/bash -l -c" + local test_script="/var/local/git/grpc/src/ruby/bin/interop/interop_client.rb" + local test_script+=" --use_tls" + local gfe_flags=" --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com" + local env_prefix="SSL_CERT_FILE=/cacerts/roots.pem" + local the_cmd="$cmd_prefix '$env_prefix ruby $test_script $gfe_flags $@'" + echo $the_cmd +} + + # constructs the full dockerized Go interop test cmd. # # call-seq: @@ -803,7 +820,7 @@ grpc_interop_gen_cxx_cmd() { # flags= .... # generic flags to include the command # cmd=$($grpc_gen_test_cmd $flags) grpc_cloud_prod_gen_cxx_cmd() { - local cmd_prefix="sudo docker run grpc/cxx"; + local cmd_prefix="sudo docker run grpc/cxx"; local test_script="/var/local/git/grpc/bins/opt/interop_client --enable_ssl"; local gfe_flags=" --use_prod_roots --server_port=443 --server_host=grpc-test.sandbox.google.com --server_host_override=grpc-test.sandbox.google.com" local the_cmd="$cmd_prefix $test_script $gfe_flags $@"; diff --git a/tools/gce_setup/shared_startup_funcs.sh b/tools/gce_setup/shared_startup_funcs.sh index f1dbca9a2e..69f6ba8cc0 100755 --- a/tools/gce_setup/shared_startup_funcs.sh +++ b/tools/gce_setup/shared_startup_funcs.sh @@ -405,14 +405,18 @@ grpc_dockerfile_install() { # For specific base images, sync the ssh key into the .ssh dir in the dockerfile context [[ $image_label == "grpc/base" ]] && { - grpc_docker_sync_github_key $dockerfile_dir/.ssh 'base_ssh_key'|| return 1; + grpc_docker_sync_github_key $dockerfile_dir/.ssh 'base_ssh_key' || return 1; } [[ $image_label == "grpc/go" ]] && { - grpc_docker_sync_github_key $dockerfile_dir/.ssh 'go_ssh_key'|| return 1; + grpc_docker_sync_github_key $dockerfile_dir/.ssh 'go_ssh_key' || return 1; } [[ $image_label == "grpc/java_base" ]] && { - grpc_docker_sync_github_key $dockerfile_dir/.ssh 'java_base_ssh_key'|| return 1; + grpc_docker_sync_github_key $dockerfile_dir/.ssh 'java_base_ssh_key' || return 1; } + [[ $image_label == "grpc/ruby" ]] && { + grpc_docker_sync_roots_pem $dockerfile_dir/cacerts || return 1; + } + # TODO(temiola): maybe make cache/no-cache a func option? sudo docker build $cache_opt -t $image_label $dockerfile_dir || { @@ -471,3 +475,31 @@ grpc_docker_sync_github_key() { } gsutil cp $src $gcs_key_path $local_key_path } + +# grpc_docker_sync_roots_pem. +# +# Copies the root pems from GCS to the target dir +# +# call-seq: +# grpc_docker_sync_roots_pem <target_dir> +grpc_docker_sync_roots_pem() { + local target_dir=$1 + [[ -n $target_dir ]] || { echo "$FUNCNAME: missing arg: target_dir" >&2; return 1; } + + # determine the admin root; the parent of the dockerfile root, + local gs_dockerfile_root=$(load_metadata "attributes/gs_dockerfile_root") + [[ -n $gs_dockerfile_root ]] || { + echo "$FUNCNAME: missing metadata: gs_dockerfile_root" >&2 + return 1 + } + local gcs_admin_root=$(dirname $gs_dockerfile_root) + + # cp the file from gsutil to a known local area + local gcs_certs_path=$gcs_admin_root/cacerts/roots.pem + local local_certs_path=$target_dir/roots.pem + mkdir -p $target_dir || { + echo "$FUNCNAME: could not create dir: $target_dir" 1>&2 + return 1 + } + gsutil cp $src $gcs_certs_path $local_certs_path +} diff --git a/tools/run_tests/run_lcov.sh b/tools/run_tests/run_lcov.sh index 6f22b0e8a4..068213a3d2 100755 --- a/tools/run_tests/run_lcov.sh +++ b/tools/run_tests/run_lcov.sh @@ -7,7 +7,7 @@ out=`realpath ${1:-coverage}` root=`realpath $(dirname $0)/../..` tmp=`mktemp` cd $root -tools/run_tests/run_tests.py -c gcov +tools/run_tests/run_tests.py -c gcov -l c c++ lcov --capture --directory . --output-file $tmp genhtml $tmp --output-directory $out rm $tmp diff --git a/vsprojects/vs2013/gpr.vcxproj b/vsprojects/vs2013/gpr.vcxproj index 6075e25ca0..f71b586aff 100644 --- a/vsprojects/vs2013/gpr.vcxproj +++ b/vsprojects/vs2013/gpr.vcxproj @@ -86,7 +86,6 @@ <ClInclude Include="..\..\include\grpc\support\port_platform.h" /> <ClInclude Include="..\..\include\grpc\support\slice.h" /> <ClInclude Include="..\..\include\grpc\support\slice_buffer.h" /> - <ClInclude Include="..\..\include\grpc\support\string.h" /> <ClInclude Include="..\..\include\grpc\support\sync.h" /> <ClInclude Include="..\..\include\grpc\support\sync_generic.h" /> <ClInclude Include="..\..\include\grpc\support\sync_posix.h" /> @@ -102,6 +101,7 @@ <ItemGroup> <ClInclude Include="..\..\src\core\support\cpu.h" /> <ClInclude Include="..\..\src\core\support\murmur_hash.h" /> + <ClInclude Include="..\..\src\core\support\string.h" /> <ClInclude Include="..\..\src\core\support\thd_internal.h" /> </ItemGroup> <ItemGroup> diff --git a/vsprojects/vs2013/gpr.vcxproj.filters b/vsprojects/vs2013/gpr.vcxproj.filters index c1fccfff3b..013ed4b3c9 100644 --- a/vsprojects/vs2013/gpr.vcxproj.filters +++ b/vsprojects/vs2013/gpr.vcxproj.filters @@ -120,9 +120,6 @@ <ClInclude Include="..\..\include\grpc\support\slice_buffer.h"> <Filter>include\grpc\support</Filter> </ClInclude> - <ClInclude Include="..\..\include\grpc\support\string.h"> - <Filter>include\grpc\support</Filter> - </ClInclude> <ClInclude Include="..\..\include\grpc\support\sync.h"> <Filter>include\grpc\support</Filter> </ClInclude> @@ -164,6 +161,9 @@ <ClInclude Include="..\..\src\core\support\murmur_hash.h"> <Filter>src\core\support</Filter> </ClInclude> + <ClInclude Include="..\..\src\core\support\string.h"> + <Filter>src\core\support</Filter> + </ClInclude> <ClInclude Include="..\..\src\core\support\thd_internal.h"> <Filter>src\core\support</Filter> </ClInclude> |