|
|
|
@ -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"}; |
|
|
|
|