From d2d0a1184e15c6497b11c4248b335573dd87d459 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Mon, 19 Jan 2015 16:07:52 -0800 Subject: [PATCH 01/19] Capture :path, and send it separately --- src/core/channel/channel_stack.c | 11 ++++++++++ src/core/channel/channel_stack.h | 2 ++ src/core/channel/http_server_filter.c | 30 +++++++++++++++++++++------ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/core/channel/channel_stack.c b/src/core/channel/channel_stack.c index 5ee412bf7d0..1320568d23d 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 = grpc_mdelem_ref(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; diff --git a/src/core/channel/channel_stack.h b/src/core/channel/channel_stack.h index eb68102b435..8d3ed0aeede 100644 --- a/src/core/channel/channel_stack.h +++ b/src/core/channel/channel_stack.h @@ -292,6 +292,8 @@ 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); #ifdef GRPC_CHANNEL_STACK_TRACE diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c index 19b9606b433..8290ead8a78 100644 --- a/src/core/channel/http_server_filter.c +++ b/src/core/channel/http_server_filter.c @@ -37,10 +37,11 @@ #include typedef struct call_data { - int sent_status; - int seen_scheme; - int seen_method; - int seen_te_trailers; + gpr_uint8 sent_status; + gpr_uint8 seen_scheme; + gpr_uint8 seen_method; + gpr_uint8 seen_te_trailers; + grpc_mdelem *path; } call_data; typedef struct channel_data { @@ -52,6 +53,7 @@ typedef struct channel_data { grpc_mdelem *grpc_scheme; grpc_mdelem *content_type; grpc_mdelem *status; + grpc_mdstr *path_key; } channel_data; /* used to silence 'variable not used' warnings */ @@ -120,6 +122,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 = grpc_mdelem_ref(op->data.metadata); + op->done_cb(op->user_data, GRPC_OP_OK); } else { /* pass the event up */ grpc_call_next_op(elem, op); @@ -129,7 +138,10 @@ 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 && 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) { @@ -189,6 +201,7 @@ 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; @@ -201,8 +214,11 @@ 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 */ @@ -225,6 +241,7 @@ static void init_channel_elem(grpc_channel_element *elem, 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"); } @@ -241,6 +258,7 @@ static void destroy_channel_elem(grpc_channel_element *elem) { 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 = { From 12b533de59e1c1ec81e52a9f51df70b8dfbe77a7 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Mon, 19 Jan 2015 20:31:44 -0800 Subject: [PATCH 02/19] Make send/recv_metadata take ownership of passed in metadata --- src/core/channel/channel_stack.c | 4 ++-- src/core/channel/http_client_filter.c | 12 ++++++------ src/core/channel/http_server_filter.c | 4 ++-- src/core/security/auth.c | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/core/channel/channel_stack.c b/src/core/channel/channel_stack.c index 1320568d23d..c70b565d2b9 100644 --- a/src/core/channel/channel_stack.c +++ b/src/core/channel/channel_stack.c @@ -209,7 +209,7 @@ void grpc_call_element_recv_metadata(grpc_call_element *cur_elem, metadata_op.dir = GRPC_CALL_UP; 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); } @@ -220,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); } diff --git a/src/core/channel/http_client_filter.c b/src/core/channel/http_client_filter.c index b139b72795f..1f6f3e0ca3d 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 8290ead8a78..87f9f437e6b 100644 --- a/src/core/channel/http_server_filter.c +++ b/src/core/channel/http_server_filter.c @@ -127,7 +127,7 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem, gpr_log(GPR_ERROR, "Received :path twice"); grpc_mdelem_unref(calld->path); } - calld->path = grpc_mdelem_ref(op->data.metadata); + calld->path = op->data.metadata; op->done_cb(op->user_data, GRPC_OP_OK); } else { /* pass the event up */ @@ -163,7 +163,7 @@ 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)); } grpc_call_next_op(elem, op); break; diff --git a/src/core/security/auth.c b/src/core/security/auth.c index f743b258382..9d0c075bc3f 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); } From 6afe256b5663ec67308e5ed5cac03ccae5dcd340 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Mon, 19 Jan 2015 18:04:23 -0800 Subject: [PATCH 03/19] Add helpers for SEND_FINISH --- src/core/channel/channel_stack.c | 9 +++++++++ src/core/channel/channel_stack.h | 1 + 2 files changed, 10 insertions(+) diff --git a/src/core/channel/channel_stack.c b/src/core/channel/channel_stack.c index c70b565d2b9..2721ed8cfc3 100644 --- a/src/core/channel/channel_stack.c +++ b/src/core/channel/channel_stack.c @@ -232,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 8d3ed0aeede..0bc31ffeaf4 100644 --- a/src/core/channel/channel_stack.h +++ b/src/core/channel/channel_stack.h @@ -295,6 +295,7 @@ void grpc_call_element_send_metadata(grpc_call_element *cur_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) From c923cd7683ec7c7f43075cacce6b7f1a15aacc54 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Mon, 19 Jan 2015 16:14:31 -0800 Subject: [PATCH 04/19] Add knowledge of :method GET --- src/core/channel/http_server_filter.c | 40 ++++++++++++++++++--------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c index 87f9f437e6b..b5eb1ba3962 100644 --- a/src/core/channel/http_server_filter.c +++ b/src/core/channel/http_server_filter.c @@ -36,17 +36,24 @@ #include #include +typedef enum { + NOT_RECEIVED, + POST, + GET +} known_method_type; + typedef struct call_data { + known_method_type seen_method; gpr_uint8 sent_status; gpr_uint8 seen_scheme; - gpr_uint8 seen_method; 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 */ @@ -75,14 +82,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) { @@ -110,7 +120,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'", @@ -138,17 +148,19 @@ 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) { + 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 */ @@ -204,7 +216,7 @@ static void init_call_elem(grpc_call_element *elem, 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; } @@ -237,7 +249,8 @@ 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->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"); @@ -253,7 +266,8 @@ static void destroy_channel_elem(grpc_channel_element *elem) { grpc_mdelem_unref(channeld->te_trailers); grpc_mdelem_unref(channeld->status); - grpc_mdelem_unref(channeld->method); + 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); From ce6e350111ea0bbea5cde5bdd78c4c4b24228695 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Mon, 19 Jan 2015 18:04:42 -0800 Subject: [PATCH 05/19] Add a simple GET handler --- src/core/channel/http_server_filter.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c index b5eb1ba3962..270df0f59bc 100644 --- a/src/core/channel/http_server_filter.c +++ b/src/core/channel/http_server_filter.c @@ -66,6 +66,14 @@ typedef struct 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 handle_get(grpc_call_element *elem) { + channel_data *channeld = elem->channel_data; + grpc_call_element_send_metadata(elem, channeld->status); + 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 @@ -153,6 +161,8 @@ static void call_op(grpc_call_element *elem, grpc_call_element *from_elem, 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 == NOT_RECEIVED) { gpr_log(GPR_ERROR, "Missing :method header"); From 7cfa2d951d01f6fade7968042e0abdc6ea5bda26 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Mon, 19 Jan 2015 15:41:49 -0800 Subject: [PATCH 06/19] Allow sending preformatted messages --- src/core/channel/call_op_string.c | 3 +++ src/core/channel/channel_stack.h | 2 ++ src/core/channel/connected_channel.c | 2 ++ 3 files changed, 7 insertions(+) diff --git a/src/core/channel/call_op_string.c b/src/core/channel/call_op_string.c index 9a7838ce2fc..e6557cef993 100644 --- a/src/core/channel/call_op_string.c +++ b/src/core/channel/call_op_string.c @@ -112,6 +112,9 @@ char *grpc_call_op_string(grpc_call_op *op) { case GRPC_SEND_MESSAGE: bprintf(&b, "SEND_MESSAGE"); break; + case GRPC_SEND_PREFORMATTED_MESSAGE: + bprintf(&b, "SEND_PREFORMATTED_MESSAGE"); + break; case GRPC_SEND_FINISH: bprintf(&b, "SEND_FINISH"); break; diff --git a/src/core/channel/channel_stack.h b/src/core/channel/channel_stack.h index 0bc31ffeaf4..754e16f4ff7 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 */ diff --git a/src/core/channel/connected_channel.c b/src/core/channel/connected_channel.c index e01cb81a890..6067896a8ce 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)); From c50e398d6582da7d2bbe2484201ef4900b68e0d1 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Tue, 20 Jan 2015 16:30:54 -0800 Subject: [PATCH 07/19] First pass API for serving static data --- include/grpc/grpc_http.h | 53 +++++++++++++++++++++++++++ src/core/channel/http_server_filter.c | 31 ++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 include/grpc/grpc_http.h diff --git a/include/grpc/grpc_http.h b/include/grpc/grpc_http.h new file mode 100644 index 00000000000..a025f8607d6 --- /dev/null +++ b/include/grpc/grpc_http.h @@ -0,0 +1,53 @@ +/* + * + * 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 + +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/http_server_filter.c b/src/core/channel/http_server_filter.c index 270df0f59bc..aef001b7cdf 100644 --- a/src/core/channel/http_server_filter.c +++ b/src/core/channel/http_server_filter.c @@ -34,6 +34,8 @@ #include "src/core/channel/http_server_filter.h" #include +#include +#include #include typedef enum { @@ -42,6 +44,12 @@ typedef enum { GET } known_method_type; +typedef struct { + grpc_mdelem *path; + grpc_mdelem *content_type; + grpc_byte_buffer *content; +} gettable; + typedef struct call_data { known_method_type seen_method; gpr_uint8 sent_status; @@ -61,6 +69,9 @@ typedef struct channel_data { grpc_mdelem *content_type; grpc_mdelem *status; grpc_mdstr *path_key; + + size_t gettable_count; + gettable *gettables; } channel_data; /* used to silence 'variable not used' warnings */ @@ -247,6 +258,9 @@ static void destroy_call_elem(grpc_call_element *elem) { 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; @@ -267,6 +281,23 @@ static void init_channel_elem(grpc_channel_element *elem, channeld->path_key = grpc_mdstr_from_string(mdctx, ":path"); channeld->content_type = grpc_mdelem_from_strings(mdctx, "content-type", "application/grpc"); + + 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); + } + 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 */ From 8a84be382ad937c7227505ba5516363f82c5f59e Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 21 Jan 2015 12:29:41 -0800 Subject: [PATCH 08/19] Echo server gets security --- test/core/echo/server.c | 45 ++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/test/core/echo/server.c b/test/core/echo/server.c index 35f118dc9b1..590cb681ac1 100644 --- a/test/core/echo/server.c +++ b/test/core/echo/server.c @@ -32,6 +32,7 @@ */ #include +#include #include #include @@ -41,11 +42,13 @@ #include "test/core/util/test_config.h" #include +#include #include #include #include #include #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 +86,53 @@ 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]; + + 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()); + 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); + + 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_server_credentials *ssl_creds = grpc_ssl_server_credentials_create( + NULL, 0, test_server1_key, test_server1_key_size, test_server1_cert, + test_server1_cert_size); + server = grpc_secure_server_create(ssl_creds, cq, NULL); + GPR_ASSERT(grpc_server_add_secure_http2_port(server, addr)); + grpc_server_credentials_release(ssl_creds); + } else { + server = grpc_server_create(cq, NULL); + 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); From c6dffe5c5172bc4d8f98980b561dd77d9f631876 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 21 Jan 2015 13:00:10 -0800 Subject: [PATCH 09/19] Allow null copy/destroy functions --- src/core/channel/channel_args.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/channel/channel_args.c b/src/core/channel/channel_args.c index 36312e54de8..7ba5fbc9d9f 100644 --- a/src/core/channel/channel_args.c +++ b/src/core/channel/channel_args.c @@ -52,7 +52,7 @@ 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 +91,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); From 53d5f9d887ae50d255a96536b8555b32631d4489 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 21 Jan 2015 13:03:18 -0800 Subject: [PATCH 10/19] Initialize some members --- src/core/channel/http_server_filter.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c index aef001b7cdf..e6f88a4bd41 100644 --- a/src/core/channel/http_server_filter.c +++ b/src/core/channel/http_server_filter.c @@ -282,6 +282,9 @@ static void init_channel_elem(grpc_channel_element *elem, 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; From fd7d3ec0392a1f39c9a826c29001dd32a45d99f6 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 21 Jan 2015 13:37:09 -0800 Subject: [PATCH 11/19] Actually serve up content --- src/core/channel/http_server_filter.c | 72 ++++++++++++++++++--------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c index e6f88a4bd41..bb676a40685 100644 --- a/src/core/channel/http_server_filter.c +++ b/src/core/channel/http_server_filter.c @@ -38,11 +38,7 @@ #include #include -typedef enum { - NOT_RECEIVED, - POST, - GET -} known_method_type; +typedef enum { NOT_RECEIVED, POST, GET } known_method_type; typedef struct { grpc_mdelem *path; @@ -67,7 +63,8 @@ typedef struct channel_data { /* 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; @@ -79,9 +76,35 @@ 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; - grpc_call_element_send_metadata(elem, channeld->status); + 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); } @@ -167,8 +190,8 @@ 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 == POST && calld->seen_scheme && calld->seen_te_trailers && - calld->path) { + 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); @@ -196,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, grpc_mdelem_ref(channeld->status)); + grpc_call_element_send_metadata(elem, + grpc_mdelem_ref(channeld->status_ok)); } grpc_call_next_op(elem, op); break; @@ -272,7 +296,9 @@ 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->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"); @@ -281,7 +307,7 @@ static void init_channel_elem(grpc_channel_element *elem, 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; @@ -291,12 +317,15 @@ static void init_channel_elem(grpc_channel_element *elem, 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); + gettable_capacity = + GPR_MAX(gettable_capacity * 3 / 2, gettable_capacity + 1); + channeld->gettables = + gpr_realloc(channeld->gettables, gettable_capacity); } 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); + 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); } @@ -309,7 +338,8 @@ 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->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); @@ -320,10 +350,6 @@ static void destroy_channel_elem(grpc_channel_element *elem) { } 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"}; From 3948412b15672834ba099756cf9bf62ddc06ac78 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 21 Jan 2015 13:37:23 -0800 Subject: [PATCH 12/19] Have echo server serve up some html --- test/core/echo/server.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/core/echo/server.c b/test/core/echo/server.c index 590cb681ac1..ee8058ebf5f 100644 --- a/test/core/echo/server.c +++ b/test/core/echo/server.c @@ -32,6 +32,7 @@ */ #include +#include #include #include @@ -97,12 +98,27 @@ int main(int argc, char **argv) { 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", + "\n" + "Echo Server\n" + "\n" + "\n" + "Welcome to the world of the future!\n" + "\n"}; + GPR_ASSERT(argc >= 1); fake_argv[0] = argv[0]; grpc_test_init(1, fake_argv); grpc_init(); srand(clock()); + memset(arge, 0, sizeof(arge)); + args.args = arge; cl = gpr_cmdline_create("echo server"); gpr_cmdline_add_string(cl, "bind", "Bind host:port", &addr); @@ -110,6 +126,11 @@ int main(int argc, char **argv) { 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; @@ -121,7 +142,7 @@ int main(int argc, char **argv) { grpc_server_credentials *ssl_creds = grpc_ssl_server_credentials_create( NULL, 0, test_server1_key, test_server1_key_size, test_server1_cert, test_server1_cert_size); - server = grpc_secure_server_create(ssl_creds, cq, NULL); + 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 { From b7e0cb56bed6ba18a28ce4bc4013dc667d485781 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 21 Jan 2015 13:41:30 -0800 Subject: [PATCH 13/19] Allocate the correct amount of memory --- src/core/channel/http_server_filter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/channel/http_server_filter.c b/src/core/channel/http_server_filter.c index bb676a40685..2658a6d42ea 100644 --- a/src/core/channel/http_server_filter.c +++ b/src/core/channel/http_server_filter.c @@ -320,7 +320,7 @@ static void init_channel_elem(grpc_channel_element *elem, gettable_capacity = GPR_MAX(gettable_capacity * 3 / 2, gettable_capacity + 1); channeld->gettables = - gpr_realloc(channeld->gettables, gettable_capacity); + gpr_realloc(channeld->gettables, gettable_capacity * sizeof(gettable)); } g = &channeld->gettables[channeld->gettable_count++]; g->path = grpc_mdelem_from_strings(mdctx, ":path", p->path); From faadd2386c8ab7cf15347cfd2131d4c26a6abdc2 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 21 Jan 2015 14:01:19 -0800 Subject: [PATCH 14/19] Add some documentation --- include/grpc/grpc_http.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/include/grpc/grpc_http.h b/include/grpc/grpc_http.h index a025f8607d6..b2ae5340a56 100644 --- a/include/grpc/grpc_http.h +++ b/include/grpc/grpc_http.h @@ -38,6 +38,20 @@ 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; From 59963419c4a0448c10122bed4de0e4a1eac6c005 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 21 Jan 2015 14:04:01 -0800 Subject: [PATCH 15/19] Clean up formatting --- test/core/echo/server.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/core/echo/server.c b/test/core/echo/server.c index ee8058ebf5f..93feba26688 100644 --- a/test/core/echo/server.c +++ b/test/core/echo/server.c @@ -104,12 +104,12 @@ int main(int argc, char **argv) { grpc_channel_args args = {0, NULL}; grpc_http_server_page home_page = {"/", "text/html", - "\n" - "Echo Server\n" - "\n" - "\n" - "Welcome to the world of the future!\n" - "\n"}; + "\n" + "Echo Server\n" + "\n" + "\n" + "Welcome to the world of the future!\n" + "\n"}; GPR_ASSERT(argc >= 1); fake_argv[0] = argv[0]; From 0a2077176524ae08eeb02fcd63f1e3e28a715741 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Fri, 23 Jan 2015 08:12:38 -0800 Subject: [PATCH 16/19] Update to the latest security API --- test/core/echo/server.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/core/echo/server.c b/test/core/echo/server.c index 93feba26688..c20260884ba 100644 --- a/test/core/echo/server.c +++ b/test/core/echo/server.c @@ -139,10 +139,11 @@ int main(int argc, char **argv) { cq = grpc_completion_queue_create(); if (secure) { - grpc_server_credentials *ssl_creds = grpc_ssl_server_credentials_create( - NULL, 0, test_server1_key, test_server1_key_size, test_server1_cert, - test_server1_cert_size); - server = grpc_secure_server_create(ssl_creds, cq, &args); + 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, NULL); GPR_ASSERT(grpc_server_add_secure_http2_port(server, addr)); grpc_server_credentials_release(ssl_creds); } else { From 94022a765aaa5af004f20af698696e3af643445d Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Fri, 23 Jan 2015 08:18:47 -0800 Subject: [PATCH 17/19] clang-format --- src/core/channel/channel_args.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/channel/channel_args.c b/src/core/channel/channel_args.c index 7ba5fbc9d9f..c1ab6980124 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.copy(src->value.pointer.p) : 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; From 103481ec8a7d93e0b120c639aa0c879e5e0aff88 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Tue, 27 Jan 2015 10:18:04 -0800 Subject: [PATCH 18/19] Add args --- test/core/echo/server.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/core/echo/server.c b/test/core/echo/server.c index c20260884ba..0d412244c64 100644 --- a/test/core/echo/server.c +++ b/test/core/echo/server.c @@ -143,11 +143,11 @@ int main(int argc, char **argv) { 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, NULL); + 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, NULL); + server = grpc_server_create(cq, &args); GPR_ASSERT(grpc_server_add_http2_port(server, addr)); } grpc_server_start(server); From ec4e0cb40b944dee55eadfd8324edac7db06837d Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Tue, 27 Jan 2015 10:21:57 -0800 Subject: [PATCH 19/19] Fix compile --- src/core/channel/call_op_string.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/channel/call_op_string.c b/src/core/channel/call_op_string.c index 40d53693c22..127ea707bf5 100644 --- a/src/core/channel/call_op_string.c +++ b/src/core/channel/call_op_string.c @@ -84,7 +84,7 @@ char *grpc_call_op_string(grpc_call_op *op) { gpr_strvec_add(&b, gpr_strdup("SEND_MESSAGE")); break; case GRPC_SEND_PREFORMATTED_MESSAGE: - bprintf(&b, "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"));