2 * Copyright 2015 gRPC authors.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <grpc/support/port_platform.h>
20 #include <grpc/support/alloc.h>
21 #include <grpc/support/log.h>
22 #include <grpc/support/string_util.h>
25 #include "src/core/ext/filters/http/client/http_client_filter.h"
26 #include "src/core/lib/gpr/string.h"
27 #include "src/core/lib/gprpp/manual_constructor.h"
28 #include "src/core/lib/profiling/timers.h"
29 #include "src/core/lib/slice/b64.h"
30 #include "src/core/lib/slice/percent_encoding.h"
31 #include "src/core/lib/slice/slice_internal.h"
32 #include "src/core/lib/slice/slice_string_helpers.h"
33 #include "src/core/lib/transport/static_metadata.h"
34 #include "src/core/lib/transport/transport_impl.h"
36 #define EXPECTED_CONTENT_TYPE "application/grpc"
37 #define EXPECTED_CONTENT_TYPE_LENGTH sizeof(EXPECTED_CONTENT_TYPE) - 1
39 /* default maximum size of payload eligible for GET request */
40 static constexpr size_t kMaxPayloadSizeForGet = 2048;
42 static void recv_initial_metadata_ready(void* user_data, grpc_error* error);
43 static void recv_trailing_metadata_ready(void* user_data, grpc_error* error);
44 static void on_send_message_next_done(void* arg, grpc_error* error);
45 static void send_message_on_complete(void* arg, grpc_error* error);
49 call_data(grpc_call_element* elem, const grpc_call_element_args& args)
50 : call_combiner(args.call_combiner) {
51 GRPC_CLOSURE_INIT(&recv_initial_metadata_ready,
52 ::recv_initial_metadata_ready, elem,
53 grpc_schedule_on_exec_ctx);
54 GRPC_CLOSURE_INIT(&recv_trailing_metadata_ready,
55 ::recv_trailing_metadata_ready, elem,
56 grpc_schedule_on_exec_ctx);
57 GRPC_CLOSURE_INIT(&on_send_message_next_done, ::on_send_message_next_done,
58 elem, grpc_schedule_on_exec_ctx);
59 GRPC_CLOSURE_INIT(&send_message_on_complete, ::send_message_on_complete,
60 elem, grpc_schedule_on_exec_ctx);
63 ~call_data() { GRPC_ERROR_UNREF(recv_initial_metadata_error); }
65 grpc_core::CallCombiner* call_combiner;
66 // State for handling send_initial_metadata ops.
67 grpc_linked_mdelem method;
68 grpc_linked_mdelem scheme;
69 grpc_linked_mdelem authority;
70 grpc_linked_mdelem te_trailers;
71 grpc_linked_mdelem content_type;
72 grpc_linked_mdelem user_agent;
73 // State for handling recv_initial_metadata ops.
74 grpc_metadata_batch* recv_initial_metadata;
75 grpc_error* recv_initial_metadata_error = GRPC_ERROR_NONE;
76 grpc_closure* original_recv_initial_metadata_ready = nullptr;
77 grpc_closure recv_initial_metadata_ready;
78 // State for handling recv_trailing_metadata ops.
79 grpc_metadata_batch* recv_trailing_metadata;
80 grpc_closure* original_recv_trailing_metadata_ready;
81 grpc_closure recv_trailing_metadata_ready;
82 grpc_error* recv_trailing_metadata_error = GRPC_ERROR_NONE;
83 bool seen_recv_trailing_metadata_ready = false;
84 // State for handling send_message ops.
85 grpc_transport_stream_op_batch* send_message_batch;
86 size_t send_message_bytes_read = 0;
87 grpc_core::ManualConstructor<grpc_core::ByteStreamCache> send_message_cache;
88 grpc_core::ManualConstructor<grpc_core::ByteStreamCache::CachingByteStream>
89 send_message_caching_stream;
90 grpc_closure on_send_message_next_done;
91 grpc_closure* original_send_message_on_complete;
92 grpc_closure send_message_on_complete;
96 grpc_mdelem static_scheme;
97 grpc_mdelem user_agent;
98 size_t max_payload_size_for_get;
102 static grpc_error* client_filter_incoming_metadata(grpc_call_element* elem,
103 grpc_metadata_batch* b) {
104 if (b->idx.named.status != nullptr) {
105 /* If both gRPC status and HTTP status are provided in the response, we
106 * should prefer the gRPC status code, as mentioned in
107 * https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md.
109 if (b->idx.named.grpc_status != nullptr ||
110 grpc_mdelem_static_value_eq(b->idx.named.status->md,
111 GRPC_MDELEM_STATUS_200)) {
112 grpc_metadata_batch_remove(b, GRPC_BATCH_STATUS);
114 char* val = grpc_dump_slice(GRPC_MDVALUE(b->idx.named.status->md),
117 gpr_asprintf(&msg, "Received http2 header with status: %s", val);
118 grpc_error* e = grpc_error_set_str(
121 GRPC_ERROR_CREATE_FROM_STATIC_STRING(
122 "Received http2 :status header with non-200 OK status"),
123 GRPC_ERROR_STR_VALUE, grpc_slice_from_copied_string(val)),
124 GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_CANCELLED),
125 GRPC_ERROR_STR_GRPC_MESSAGE, grpc_slice_from_copied_string(msg));
132 if (b->idx.named.grpc_message != nullptr) {
133 grpc_slice pct_decoded_msg = grpc_permissive_percent_decode_slice(
134 GRPC_MDVALUE(b->idx.named.grpc_message->md));
135 if (grpc_slice_is_equivalent(pct_decoded_msg,
136 GRPC_MDVALUE(b->idx.named.grpc_message->md))) {
137 grpc_slice_unref_internal(pct_decoded_msg);
139 grpc_metadata_batch_set_value(b->idx.named.grpc_message, pct_decoded_msg);
143 if (b->idx.named.content_type != nullptr) {
144 if (!grpc_mdelem_static_value_eq(
145 b->idx.named.content_type->md,
146 GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC)) {
147 if (grpc_slice_buf_start_eq(GRPC_MDVALUE(b->idx.named.content_type->md),
148 EXPECTED_CONTENT_TYPE,
149 EXPECTED_CONTENT_TYPE_LENGTH) &&
150 (GRPC_SLICE_START_PTR(GRPC_MDVALUE(
151 b->idx.named.content_type->md))[EXPECTED_CONTENT_TYPE_LENGTH] ==
153 GRPC_SLICE_START_PTR(GRPC_MDVALUE(
154 b->idx.named.content_type->md))[EXPECTED_CONTENT_TYPE_LENGTH] ==
156 /* Although the C implementation doesn't (currently) generate them,
157 any custom +-suffix is explicitly valid. */
158 /* TODO(klempner): We should consider preallocating common values such
159 as +proto or +json, or at least stashing them if we see them. */
160 /* TODO(klempner): Should we be surfacing this to application code? */
162 /* TODO(klempner): We're currently allowing this, but we shouldn't
163 see it without a proxy so log for now. */
164 char* val = grpc_dump_slice(GRPC_MDVALUE(b->idx.named.content_type->md),
166 gpr_log(GPR_INFO, "Unexpected content-type '%s'", val);
170 grpc_metadata_batch_remove(b, GRPC_BATCH_CONTENT_TYPE);
173 return GRPC_ERROR_NONE;
176 static void recv_initial_metadata_ready(void* user_data, grpc_error* error) {
177 grpc_call_element* elem = static_cast<grpc_call_element*>(user_data);
178 call_data* calld = static_cast<call_data*>(elem->call_data);
179 if (error == GRPC_ERROR_NONE) {
180 error = client_filter_incoming_metadata(elem, calld->recv_initial_metadata);
181 calld->recv_initial_metadata_error = GRPC_ERROR_REF(error);
183 GRPC_ERROR_REF(error);
185 grpc_closure* closure = calld->original_recv_initial_metadata_ready;
186 calld->original_recv_initial_metadata_ready = nullptr;
187 if (calld->seen_recv_trailing_metadata_ready) {
188 GRPC_CALL_COMBINER_START(
189 calld->call_combiner, &calld->recv_trailing_metadata_ready,
190 calld->recv_trailing_metadata_error, "continue recv_trailing_metadata");
192 GRPC_CLOSURE_RUN(closure, error);
195 static void recv_trailing_metadata_ready(void* user_data, grpc_error* error) {
196 grpc_call_element* elem = static_cast<grpc_call_element*>(user_data);
197 call_data* calld = static_cast<call_data*>(elem->call_data);
198 if (calld->original_recv_initial_metadata_ready != nullptr) {
199 calld->recv_trailing_metadata_error = GRPC_ERROR_REF(error);
200 calld->seen_recv_trailing_metadata_ready = true;
201 GRPC_CALL_COMBINER_STOP(calld->call_combiner,
202 "deferring recv_trailing_metadata_ready until "
203 "after recv_initial_metadata_ready");
206 if (error == GRPC_ERROR_NONE) {
208 client_filter_incoming_metadata(elem, calld->recv_trailing_metadata);
210 GRPC_ERROR_REF(error);
212 error = grpc_error_add_child(
213 error, GRPC_ERROR_REF(calld->recv_initial_metadata_error));
214 GRPC_CLOSURE_RUN(calld->original_recv_trailing_metadata_ready, error);
217 static void send_message_on_complete(void* arg, grpc_error* error) {
218 grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
219 call_data* calld = static_cast<call_data*>(elem->call_data);
220 calld->send_message_cache.Destroy();
221 GRPC_CLOSURE_RUN(calld->original_send_message_on_complete,
222 GRPC_ERROR_REF(error));
225 // Pulls a slice from the send_message byte stream, updating
226 // calld->send_message_bytes_read.
227 static grpc_error* pull_slice_from_send_message(call_data* calld) {
228 grpc_slice incoming_slice;
229 grpc_error* error = calld->send_message_caching_stream->Pull(&incoming_slice);
230 if (error == GRPC_ERROR_NONE) {
231 calld->send_message_bytes_read += GRPC_SLICE_LENGTH(incoming_slice);
232 grpc_slice_unref_internal(incoming_slice);
237 // Reads as many slices as possible from the send_message byte stream.
238 // Upon successful return, if calld->send_message_bytes_read ==
239 // calld->send_message_caching_stream->length(), then we have completed
240 // reading from the byte stream; otherwise, an async read has been dispatched
241 // and on_send_message_next_done() will be invoked when it is complete.
242 static grpc_error* read_all_available_send_message_data(call_data* calld) {
243 while (calld->send_message_caching_stream->Next(
244 SIZE_MAX, &calld->on_send_message_next_done)) {
245 grpc_error* error = pull_slice_from_send_message(calld);
246 if (error != GRPC_ERROR_NONE) return error;
247 if (calld->send_message_bytes_read ==
248 calld->send_message_caching_stream->length()) {
252 return GRPC_ERROR_NONE;
255 // Async callback for ByteStream::Next().
256 static void on_send_message_next_done(void* arg, grpc_error* error) {
257 grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
258 call_data* calld = static_cast<call_data*>(elem->call_data);
259 if (error != GRPC_ERROR_NONE) {
260 grpc_transport_stream_op_batch_finish_with_failure(
261 calld->send_message_batch, error, calld->call_combiner);
264 error = pull_slice_from_send_message(calld);
265 if (error != GRPC_ERROR_NONE) {
266 grpc_transport_stream_op_batch_finish_with_failure(
267 calld->send_message_batch, error, calld->call_combiner);
270 // There may or may not be more to read, but we don't care. If we got
271 // here, then we know that all of the data was not available
272 // synchronously, so we were not able to do a cached call. Instead,
273 // we just reset the byte stream and then send down the batch as-is.
274 calld->send_message_caching_stream->Reset();
275 grpc_call_next_op(elem, calld->send_message_batch);
278 static char* slice_buffer_to_string(grpc_slice_buffer* slice_buffer) {
279 char* payload_bytes =
280 static_cast<char*>(gpr_malloc(slice_buffer->length + 1));
282 for (size_t i = 0; i < slice_buffer->count; ++i) {
283 memcpy(payload_bytes + offset,
284 GRPC_SLICE_START_PTR(slice_buffer->slices[i]),
285 GRPC_SLICE_LENGTH(slice_buffer->slices[i]));
286 offset += GRPC_SLICE_LENGTH(slice_buffer->slices[i]);
288 *(payload_bytes + offset) = '\0';
289 return payload_bytes;
292 // Modifies the path entry in the batch's send_initial_metadata to
293 // append the base64-encoded query for a GET request.
294 static grpc_error* update_path_for_get(grpc_call_element* elem,
295 grpc_transport_stream_op_batch* batch) {
296 call_data* calld = static_cast<call_data*>(elem->call_data);
297 grpc_slice path_slice =
298 GRPC_MDVALUE(batch->payload->send_initial_metadata.send_initial_metadata
299 ->idx.named.path->md);
300 /* sum up individual component's lengths and allocate enough memory to
301 * hold combined path+query */
302 size_t estimated_len = GRPC_SLICE_LENGTH(path_slice);
303 estimated_len++; /* for the '?' */
304 estimated_len += grpc_base64_estimate_encoded_size(
305 batch->payload->send_message.send_message->length(), true /* url_safe */,
306 false /* multi_line */);
307 grpc_core::UnmanagedMemorySlice path_with_query_slice(estimated_len);
308 /* memcopy individual pieces into this slice */
310 reinterpret_cast<char*> GRPC_SLICE_START_PTR(path_with_query_slice);
311 char* original_path =
312 reinterpret_cast<char*> GRPC_SLICE_START_PTR(path_slice);
313 memcpy(write_ptr, original_path, GRPC_SLICE_LENGTH(path_slice));
314 write_ptr += GRPC_SLICE_LENGTH(path_slice);
316 char* payload_bytes =
317 slice_buffer_to_string(calld->send_message_cache->cache_buffer());
318 grpc_base64_encode_core(write_ptr, payload_bytes,
319 batch->payload->send_message.send_message->length(),
320 true /* url_safe */, false /* multi_line */);
321 gpr_free(payload_bytes);
322 /* remove trailing unused memory and add trailing 0 to terminate string */
323 char* t = reinterpret_cast<char*> GRPC_SLICE_START_PTR(path_with_query_slice);
324 /* safe to use strlen since base64_encode will always add '\0' */
325 path_with_query_slice =
326 grpc_slice_sub_no_ref(path_with_query_slice, 0, strlen(t));
327 /* substitute previous path with the new path+query */
328 grpc_mdelem mdelem_path_and_query =
329 grpc_mdelem_from_slices(GRPC_MDSTR_PATH, path_with_query_slice);
330 grpc_metadata_batch* b =
331 batch->payload->send_initial_metadata.send_initial_metadata;
332 return grpc_metadata_batch_substitute(b, b->idx.named.path,
333 mdelem_path_and_query);
336 static void remove_if_present(grpc_metadata_batch* batch,
337 grpc_metadata_batch_callouts_index idx) {
338 if (batch->idx.array[idx] != nullptr) {
339 grpc_metadata_batch_remove(batch, idx);
343 static void hc_start_transport_stream_op_batch(
344 grpc_call_element* elem, grpc_transport_stream_op_batch* batch) {
345 call_data* calld = static_cast<call_data*>(elem->call_data);
346 channel_data* channeld = static_cast<channel_data*>(elem->channel_data);
347 GPR_TIMER_SCOPE("hc_start_transport_stream_op_batch", 0);
349 if (batch->recv_initial_metadata) {
350 /* substitute our callback for the higher callback */
351 calld->recv_initial_metadata =
352 batch->payload->recv_initial_metadata.recv_initial_metadata;
353 calld->original_recv_initial_metadata_ready =
354 batch->payload->recv_initial_metadata.recv_initial_metadata_ready;
355 batch->payload->recv_initial_metadata.recv_initial_metadata_ready =
356 &calld->recv_initial_metadata_ready;
359 if (batch->recv_trailing_metadata) {
360 /* substitute our callback for the higher callback */
361 calld->recv_trailing_metadata =
362 batch->payload->recv_trailing_metadata.recv_trailing_metadata;
363 calld->original_recv_trailing_metadata_ready =
364 batch->payload->recv_trailing_metadata.recv_trailing_metadata_ready;
365 batch->payload->recv_trailing_metadata.recv_trailing_metadata_ready =
366 &calld->recv_trailing_metadata_ready;
369 grpc_error* error = GRPC_ERROR_NONE;
370 bool batch_will_be_handled_asynchronously = false;
371 if (batch->send_initial_metadata) {
372 // Decide which HTTP VERB to use. We use GET if the request is marked
373 // cacheable, and the operation contains both initial metadata and send
374 // message, and the payload is below the size threshold, and all the data
375 // for this request is immediately available.
376 grpc_mdelem method = GRPC_MDELEM_METHOD_POST;
377 if (batch->send_message &&
378 (batch->payload->send_initial_metadata.send_initial_metadata_flags &
379 GRPC_INITIAL_METADATA_CACHEABLE_REQUEST) &&
380 batch->payload->send_message.send_message->length() <
381 channeld->max_payload_size_for_get) {
382 calld->send_message_bytes_read = 0;
383 calld->send_message_cache.Init(
384 std::move(batch->payload->send_message.send_message));
385 calld->send_message_caching_stream.Init(calld->send_message_cache.get());
386 batch->payload->send_message.send_message.reset(
387 calld->send_message_caching_stream.get());
388 calld->original_send_message_on_complete = batch->on_complete;
389 batch->on_complete = &calld->send_message_on_complete;
390 calld->send_message_batch = batch;
391 error = read_all_available_send_message_data(calld);
392 if (error != GRPC_ERROR_NONE) goto done;
393 // If all the data has been read, then we can use GET.
394 if (calld->send_message_bytes_read ==
395 calld->send_message_caching_stream->length()) {
396 method = GRPC_MDELEM_METHOD_GET;
397 error = update_path_for_get(elem, batch);
398 if (error != GRPC_ERROR_NONE) goto done;
399 batch->send_message = false;
400 calld->send_message_caching_stream->Orphan();
402 // Not all data is available. The batch will be sent down
403 // asynchronously in on_send_message_next_done().
404 batch_will_be_handled_asynchronously = true;
405 // Fall back to POST.
407 "Request is marked Cacheable but not all data is available. "
408 "Falling back to POST");
410 } else if (batch->payload->send_initial_metadata
411 .send_initial_metadata_flags &
412 GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST) {
413 method = GRPC_MDELEM_METHOD_PUT;
417 batch->payload->send_initial_metadata.send_initial_metadata,
420 batch->payload->send_initial_metadata.send_initial_metadata,
423 batch->payload->send_initial_metadata.send_initial_metadata,
426 batch->payload->send_initial_metadata.send_initial_metadata,
427 GRPC_BATCH_CONTENT_TYPE);
429 batch->payload->send_initial_metadata.send_initial_metadata,
430 GRPC_BATCH_USER_AGENT);
432 /* Send : prefixed headers, which have to be before any application
434 error = grpc_metadata_batch_add_head(
435 batch->payload->send_initial_metadata.send_initial_metadata,
436 &calld->method, method, GRPC_BATCH_METHOD);
437 if (error != GRPC_ERROR_NONE) goto done;
438 error = grpc_metadata_batch_add_head(
439 batch->payload->send_initial_metadata.send_initial_metadata,
440 &calld->scheme, channeld->static_scheme, GRPC_BATCH_SCHEME);
441 if (error != GRPC_ERROR_NONE) goto done;
442 error = grpc_metadata_batch_add_tail(
443 batch->payload->send_initial_metadata.send_initial_metadata,
444 &calld->te_trailers, GRPC_MDELEM_TE_TRAILERS, GRPC_BATCH_TE);
445 if (error != GRPC_ERROR_NONE) goto done;
446 error = grpc_metadata_batch_add_tail(
447 batch->payload->send_initial_metadata.send_initial_metadata,
448 &calld->content_type, GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC,
449 GRPC_BATCH_CONTENT_TYPE);
450 if (error != GRPC_ERROR_NONE) goto done;
451 error = grpc_metadata_batch_add_tail(
452 batch->payload->send_initial_metadata.send_initial_metadata,
453 &calld->user_agent, GRPC_MDELEM_REF(channeld->user_agent),
454 GRPC_BATCH_USER_AGENT);
455 if (error != GRPC_ERROR_NONE) goto done;
459 if (error != GRPC_ERROR_NONE) {
460 grpc_transport_stream_op_batch_finish_with_failure(
461 calld->send_message_batch, error, calld->call_combiner);
462 } else if (!batch_will_be_handled_asynchronously) {
463 grpc_call_next_op(elem, batch);
467 /* Constructor for call_data */
468 static grpc_error* init_call_elem(grpc_call_element* elem,
469 const grpc_call_element_args* args) {
470 new (elem->call_data) call_data(elem, *args);
471 return GRPC_ERROR_NONE;
474 /* Destructor for call_data */
475 static void destroy_call_elem(grpc_call_element* elem,
476 const grpc_call_final_info* final_info,
477 grpc_closure* ignored) {
478 call_data* calld = static_cast<call_data*>(elem->call_data);
482 static grpc_mdelem scheme_from_args(const grpc_channel_args* args) {
485 grpc_mdelem valid_schemes[] = {GRPC_MDELEM_SCHEME_HTTP,
486 GRPC_MDELEM_SCHEME_HTTPS};
487 if (args != nullptr) {
488 for (i = 0; i < args->num_args; ++i) {
489 if (args->args[i].type == GRPC_ARG_STRING &&
490 strcmp(args->args[i].key, GRPC_ARG_HTTP2_SCHEME) == 0) {
491 for (j = 0; j < GPR_ARRAY_SIZE(valid_schemes); j++) {
492 if (0 == grpc_slice_str_cmp(GRPC_MDVALUE(valid_schemes[j]),
493 args->args[i].value.string)) {
494 return valid_schemes[j];
500 return GRPC_MDELEM_SCHEME_HTTP;
503 static size_t max_payload_size_from_args(const grpc_channel_args* args) {
504 if (args != nullptr) {
505 for (size_t i = 0; i < args->num_args; ++i) {
506 if (0 == strcmp(args->args[i].key, GRPC_ARG_MAX_PAYLOAD_SIZE_FOR_GET)) {
507 if (args->args[i].type != GRPC_ARG_INTEGER) {
508 gpr_log(GPR_ERROR, "%s: must be an integer",
509 GRPC_ARG_MAX_PAYLOAD_SIZE_FOR_GET);
511 return static_cast<size_t>(args->args[i].value.integer);
516 return kMaxPayloadSizeForGet;
519 static grpc_core::ManagedMemorySlice user_agent_from_args(
520 const grpc_channel_args* args, const char* transport_name) {
528 for (i = 0; args && i < args->num_args; i++) {
529 if (0 == strcmp(args->args[i].key, GRPC_ARG_PRIMARY_USER_AGENT_STRING)) {
530 if (args->args[i].type != GRPC_ARG_STRING) {
531 gpr_log(GPR_ERROR, "Channel argument '%s' should be a string",
532 GRPC_ARG_PRIMARY_USER_AGENT_STRING);
534 if (!is_first) gpr_strvec_add(&v, gpr_strdup(" "));
536 gpr_strvec_add(&v, gpr_strdup(args->args[i].value.string));
541 gpr_asprintf(&tmp, "%sgrpc-c/%s (%s; %s; %s)", is_first ? "" : " ",
542 grpc_version_string(), GPR_PLATFORM_STRING, transport_name,
543 grpc_g_stands_for());
545 gpr_strvec_add(&v, tmp);
547 for (i = 0; args && i < args->num_args; i++) {
548 if (0 == strcmp(args->args[i].key, GRPC_ARG_SECONDARY_USER_AGENT_STRING)) {
549 if (args->args[i].type != GRPC_ARG_STRING) {
550 gpr_log(GPR_ERROR, "Channel argument '%s' should be a string",
551 GRPC_ARG_SECONDARY_USER_AGENT_STRING);
553 if (!is_first) gpr_strvec_add(&v, gpr_strdup(" "));
555 gpr_strvec_add(&v, gpr_strdup(args->args[i].value.string));
560 tmp = gpr_strvec_flatten(&v, nullptr);
561 gpr_strvec_destroy(&v);
562 grpc_core::ManagedMemorySlice result(tmp);
568 /* Constructor for channel_data */
569 static grpc_error* init_channel_elem(grpc_channel_element* elem,
570 grpc_channel_element_args* args) {
571 channel_data* chand = static_cast<channel_data*>(elem->channel_data);
572 GPR_ASSERT(!args->is_last);
573 GPR_ASSERT(args->optional_transport != nullptr);
574 chand->static_scheme = scheme_from_args(args->channel_args);
575 chand->max_payload_size_for_get =
576 max_payload_size_from_args(args->channel_args);
577 chand->user_agent = grpc_mdelem_from_slices(
578 GRPC_MDSTR_USER_AGENT,
579 user_agent_from_args(args->channel_args,
580 args->optional_transport->vtable->name));
581 return GRPC_ERROR_NONE;
584 /* Destructor for channel data */
585 static void destroy_channel_elem(grpc_channel_element* elem) {
586 channel_data* chand = static_cast<channel_data*>(elem->channel_data);
587 GRPC_MDELEM_UNREF(chand->user_agent);
590 const grpc_channel_filter grpc_http_client_filter = {
591 hc_start_transport_stream_op_batch,
592 grpc_channel_next_op,
595 grpc_call_stack_ignore_set_pollset_or_pollset_set,
597 sizeof(channel_data),
599 destroy_channel_elem,
600 grpc_channel_next_get_info,