|
|
|
@ -31,6 +31,7 @@ |
|
|
|
|
* |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
#include <assert.h> |
|
|
|
|
#include <string.h> |
|
|
|
|
|
|
|
|
|
#include "src/core/channel/compress_filter.h" |
|
|
|
@ -42,16 +43,16 @@ |
|
|
|
|
|
|
|
|
|
typedef struct call_data { |
|
|
|
|
gpr_slice_buffer slices; |
|
|
|
|
grpc_linked_mdelem compression_algorithm_storage; |
|
|
|
|
int remaining_slice_bytes; |
|
|
|
|
int no_compress; /**< whether to skip compression for this specific call */ |
|
|
|
|
|
|
|
|
|
grpc_linked_mdelem compression_algorithm; |
|
|
|
|
grpc_compression_algorithm compression_algorithm; |
|
|
|
|
gpr_uint8 has_compression_algorithm; |
|
|
|
|
} call_data; |
|
|
|
|
|
|
|
|
|
typedef struct channel_data { |
|
|
|
|
grpc_compression_algorithm compress_algorithm; |
|
|
|
|
grpc_mdelem *compress_algorithm_md; |
|
|
|
|
grpc_mdelem *no_compression_md; |
|
|
|
|
grpc_mdstr *mdstr_compression_algorithm_key; |
|
|
|
|
grpc_mdelem *mdelem_compression_algorithms[GRPC_COMPRESS_ALGORITHMS_COUNT]; |
|
|
|
|
grpc_compression_algorithm default_compression_algorithm; |
|
|
|
|
} channel_data; |
|
|
|
|
|
|
|
|
|
/** Compress \a slices in place using \a algorithm. Returns 1 if compression did
|
|
|
|
@ -70,14 +71,41 @@ static int compress_send_sb(grpc_compression_algorithm algorithm, |
|
|
|
|
return did_compress; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** For each \a md element from the incoming metadata, filter out the entry for
|
|
|
|
|
* "grpc-compression-algorithm", using its value to populate the call data's |
|
|
|
|
* compression_algorithm field. */ |
|
|
|
|
static grpc_mdelem* compression_md_filter(void *user_data, grpc_mdelem *md) { |
|
|
|
|
grpc_call_element *elem = user_data; |
|
|
|
|
call_data *calld = elem->call_data; |
|
|
|
|
channel_data *channeld = elem->channel_data; |
|
|
|
|
|
|
|
|
|
if (md->key == channeld->mdstr_compression_algorithm_key) { |
|
|
|
|
assert(GPR_SLICE_LENGTH(md->value->slice) == |
|
|
|
|
sizeof(grpc_compression_algorithm)); |
|
|
|
|
memcpy(&calld->compression_algorithm, GPR_SLICE_START_PTR(md->value->slice), |
|
|
|
|
sizeof(grpc_compression_algorithm)); |
|
|
|
|
calld->has_compression_algorithm = 1; |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return md; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int skip_compression(channel_data *channeld, call_data *calld) { |
|
|
|
|
if (calld->has_compression_algorithm && |
|
|
|
|
(calld->compression_algorithm == GRPC_COMPRESS_NONE)) { |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
/* no per-call compression override */ |
|
|
|
|
return channeld->default_compression_algorithm == GRPC_COMPRESS_NONE; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void process_send_ops(grpc_call_element *elem, |
|
|
|
|
grpc_stream_op_buffer *send_ops) { |
|
|
|
|
call_data *calld = elem->call_data; |
|
|
|
|
channel_data *channeld = elem->channel_data; |
|
|
|
|
size_t i, j; |
|
|
|
|
int begin_message_index = -1; |
|
|
|
|
int metadata_op_index = -1; |
|
|
|
|
grpc_mdelem *actual_compression_md; |
|
|
|
|
int did_compress = 0; |
|
|
|
|
|
|
|
|
|
/* buffer up slices until we've processed all the expected ones (as given by
|
|
|
|
|
* GRPC_OP_BEGIN_MESSAGE) */ |
|
|
|
@ -85,73 +113,83 @@ static void process_send_ops(grpc_call_element *elem, |
|
|
|
|
grpc_stream_op *sop = &send_ops->ops[i]; |
|
|
|
|
switch (sop->type) { |
|
|
|
|
case GRPC_OP_BEGIN_MESSAGE: |
|
|
|
|
begin_message_index = i; |
|
|
|
|
calld->remaining_slice_bytes = sop->data.begin_message.length; |
|
|
|
|
calld->no_compress = |
|
|
|
|
!!(sop->data.begin_message.flags & GRPC_WRITE_NO_COMPRESS); |
|
|
|
|
/* TODO(dgq): we may want to get rid of the flags mechanism to have
|
|
|
|
|
* exceptions to compression: we can rely solely on metadata to set NONE |
|
|
|
|
* as the compression algorithm */ |
|
|
|
|
if (sop->data.begin_message.flags & GRPC_WRITE_NO_COMPRESS) { |
|
|
|
|
calld->has_compression_algorithm = 1; /* GPR_TRUE */ |
|
|
|
|
calld->compression_algorithm = GRPC_COMPRESS_NONE; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case GRPC_OP_METADATA: |
|
|
|
|
/* Parse incoming request for compression. If any, it'll be available at
|
|
|
|
|
* calld->compression_algorithm */ |
|
|
|
|
grpc_metadata_batch_filter(&(sop->data.metadata), compression_md_filter, |
|
|
|
|
elem); |
|
|
|
|
if (!calld->has_compression_algorithm) { |
|
|
|
|
/* If no algorithm was found in the metadata and we aren't
|
|
|
|
|
* exceptionally skipping compression, fall back to the channel |
|
|
|
|
* default */ |
|
|
|
|
calld->compression_algorithm = |
|
|
|
|
channeld->default_compression_algorithm; |
|
|
|
|
calld->has_compression_algorithm = 1; /* GPR_TRUE */ |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case GRPC_OP_SLICE: |
|
|
|
|
if (calld->no_compress) continue; |
|
|
|
|
if (skip_compression(channeld, calld)) continue; |
|
|
|
|
GPR_ASSERT(calld->remaining_slice_bytes > 0); |
|
|
|
|
/* add to calld->slices */ |
|
|
|
|
gpr_slice_buffer_add(&calld->slices, sop->data.slice); |
|
|
|
|
calld->remaining_slice_bytes -= GPR_SLICE_LENGTH(sop->data.slice); |
|
|
|
|
if (calld->remaining_slice_bytes == 0) { |
|
|
|
|
/* compress */ |
|
|
|
|
if (!compress_send_sb(channeld->compress_algorithm, &calld->slices)) { |
|
|
|
|
calld->no_compress = 1; /* GPR_TRUE */ |
|
|
|
|
} |
|
|
|
|
did_compress = |
|
|
|
|
compress_send_sb(calld->compression_algorithm, &calld->slices); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case GRPC_OP_METADATA: |
|
|
|
|
/* Save the index of the first metadata op, to be processed after we
|
|
|
|
|
* know whether compression actually happened */ |
|
|
|
|
if (metadata_op_index < 0) metadata_op_index = i; |
|
|
|
|
break; |
|
|
|
|
case GRPC_NO_OP: |
|
|
|
|
; /* fallthrough, ignore */ |
|
|
|
|
; /* fallthrough */ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (metadata_op_index < 0 || begin_message_index < 0) { /* bail out */ |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* update both the metadata and the begin_message's flags */ |
|
|
|
|
if (calld->no_compress) { |
|
|
|
|
/* either because the user requested the exception or because compressing
|
|
|
|
|
* would have resulted in a larger output */ |
|
|
|
|
channeld->compress_algorithm = GRPC_COMPRESS_NONE; |
|
|
|
|
actual_compression_md = channeld->no_compression_md; |
|
|
|
|
/* reset the flag compression bit */ |
|
|
|
|
send_ops->ops[begin_message_index].data.begin_message.flags &= |
|
|
|
|
~GRPC_WRITE_INTERNAL_COMPRESS; |
|
|
|
|
} else { /* DID compress */ |
|
|
|
|
actual_compression_md = channeld->compress_algorithm_md; |
|
|
|
|
/* at this point, calld->slices contains the *compressed* slices from
|
|
|
|
|
* send_ops->ops[*]->data.slice. We now replace these input slices with the |
|
|
|
|
* compressed ones. */ |
|
|
|
|
for (i = 0, j = 0; i < send_ops->nops; ++i) { |
|
|
|
|
grpc_stream_op *sop = &send_ops->ops[i]; |
|
|
|
|
GPR_ASSERT(j < calld->slices.count); |
|
|
|
|
switch (sop->type) { |
|
|
|
|
case GRPC_OP_SLICE: |
|
|
|
|
gpr_slice_unref(sop->data.slice); |
|
|
|
|
sop->data.slice = gpr_slice_ref(calld->slices.slices[j++]); |
|
|
|
|
break; |
|
|
|
|
case GRPC_OP_BEGIN_MESSAGE: |
|
|
|
|
/* We need to:
|
|
|
|
|
* - (OP_SLICE) If compression happened, replace the input slices with the |
|
|
|
|
* compressed ones. |
|
|
|
|
* - (BEGIN_MESSAGE) Update the message info (size, flags). |
|
|
|
|
* - (OP_METADATA) Convey the compression configuration */ |
|
|
|
|
for (i = 0, j = 0; i < send_ops->nops; ++i) { |
|
|
|
|
grpc_stream_op *sop = &send_ops->ops[i]; |
|
|
|
|
switch (sop->type) { |
|
|
|
|
case GRPC_OP_BEGIN_MESSAGE: |
|
|
|
|
if (did_compress) { |
|
|
|
|
sop->data.begin_message.length = calld->slices.length; |
|
|
|
|
sop->data.begin_message.flags |= GRPC_WRITE_INTERNAL_COMPRESS; |
|
|
|
|
case GRPC_NO_OP: |
|
|
|
|
case GRPC_OP_METADATA: |
|
|
|
|
; /* fallthrough, ignore */ |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
/* either because the user requested the exception or because compressing
|
|
|
|
|
* would have resulted in a larger output */ |
|
|
|
|
calld->compression_algorithm = GRPC_COMPRESS_NONE; |
|
|
|
|
/* reset the flag compression bit */ |
|
|
|
|
sop->data.begin_message.flags &= ~GRPC_WRITE_INTERNAL_COMPRESS; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case GRPC_OP_METADATA: |
|
|
|
|
grpc_metadata_batch_add_head( |
|
|
|
|
&(sop->data.metadata), &calld->compression_algorithm_storage, |
|
|
|
|
grpc_mdelem_ref(channeld->mdelem_compression_algorithms |
|
|
|
|
[calld->compression_algorithm])); |
|
|
|
|
break; |
|
|
|
|
case GRPC_OP_SLICE: |
|
|
|
|
if (did_compress) { |
|
|
|
|
GPR_ASSERT(j < calld->slices.count); |
|
|
|
|
gpr_slice_unref(sop->data.slice); |
|
|
|
|
sop->data.slice = gpr_slice_ref(calld->slices.slices[j++]); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case GRPC_NO_OP: |
|
|
|
|
; /* fallthrough */ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
grpc_metadata_batch_add_head( |
|
|
|
|
&(send_ops->ops[metadata_op_index].data.metadata), |
|
|
|
|
&calld->compression_algorithm, grpc_mdelem_ref(actual_compression_md)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Called either:
|
|
|
|
@ -189,6 +227,7 @@ static void init_call_elem(grpc_call_element *elem, |
|
|
|
|
|
|
|
|
|
/* initialize members */ |
|
|
|
|
gpr_slice_buffer_init(&calld->slices); |
|
|
|
|
calld->has_compression_algorithm = 0; |
|
|
|
|
|
|
|
|
|
if (initial_op) { |
|
|
|
|
if (initial_op->send_ops && initial_op->send_ops->nops > 0) { |
|
|
|
@ -209,17 +248,23 @@ static void init_channel_elem(grpc_channel_element *elem, |
|
|
|
|
const grpc_channel_args *args, grpc_mdctx *mdctx, |
|
|
|
|
int is_first, int is_last) { |
|
|
|
|
channel_data *channeld = elem->channel_data; |
|
|
|
|
grpc_compression_algorithm algo_idx; |
|
|
|
|
const grpc_compression_level clevel = |
|
|
|
|
grpc_channel_args_get_compression_level(args); |
|
|
|
|
const grpc_compression_algorithm none_alg = GRPC_COMPRESS_NONE; |
|
|
|
|
|
|
|
|
|
channeld->compress_algorithm_md = grpc_mdelem_from_string_and_buffer( |
|
|
|
|
mdctx, "grpc-compression-level", (gpr_uint8*)&clevel, sizeof(clevel)); |
|
|
|
|
channeld->compress_algorithm = grpc_compression_algorithm_for_level(clevel); |
|
|
|
|
channeld->default_compression_algorithm = |
|
|
|
|
grpc_compression_algorithm_for_level(clevel); |
|
|
|
|
|
|
|
|
|
channeld->no_compression_md = grpc_mdelem_from_string_and_buffer( |
|
|
|
|
mdctx, "grpc-compression-level", (gpr_uint8 *)&none_alg, |
|
|
|
|
sizeof(none_alg)); |
|
|
|
|
channeld->mdstr_compression_algorithm_key = |
|
|
|
|
grpc_mdstr_from_string(mdctx, "grpc-compression-algorithm"); |
|
|
|
|
|
|
|
|
|
for (algo_idx = 0; algo_idx < GRPC_COMPRESS_ALGORITHMS_COUNT; ++algo_idx) { |
|
|
|
|
channeld->mdelem_compression_algorithms[algo_idx] = |
|
|
|
|
grpc_mdelem_from_metadata_strings( |
|
|
|
|
mdctx, grpc_mdstr_ref(channeld->mdstr_compression_algorithm_key), |
|
|
|
|
grpc_mdstr_from_buffer(mdctx, (gpr_uint8 *)&algo_idx, |
|
|
|
|
sizeof(algo_idx))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* The first and the last filters tend to be implemented differently to
|
|
|
|
|
handle the case that there's no 'next' filter to call on the up or down |
|
|
|
@ -231,8 +276,13 @@ static void init_channel_elem(grpc_channel_element *elem, |
|
|
|
|
/* Destructor for channel data */ |
|
|
|
|
static void destroy_channel_elem(grpc_channel_element *elem) { |
|
|
|
|
channel_data *channeld = elem->channel_data; |
|
|
|
|
grpc_mdelem_unref(channeld->compress_algorithm_md); |
|
|
|
|
grpc_mdelem_unref(channeld->no_compression_md); |
|
|
|
|
grpc_compression_algorithm algo_idx; |
|
|
|
|
|
|
|
|
|
grpc_mdstr_unref(channeld->mdstr_compression_algorithm_key); |
|
|
|
|
for (algo_idx = 0; algo_idx < GRPC_COMPRESS_ALGORITHMS_COUNT; |
|
|
|
|
++algo_idx) { |
|
|
|
|
grpc_mdelem_unref(channeld->mdelem_compression_algorithms[algo_idx]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const grpc_channel_filter grpc_compress_filter = { |
|
|
|
|