1 /*
2 * Copyright 2015 gRPC authors.
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 *
16 */
17
18 #include <grpc/support/port_platform.h>
19
20 #include <grpc/support/alloc.h>
21 #include <grpc/support/log.h>
22 #include <grpc/support/string_util.h>
23 #include <stdint.h>
24 #include <string.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"
35
36 #define EXPECTED_CONTENT_TYPE "application/grpc"
37 #define EXPECTED_CONTENT_TYPE_LENGTH sizeof(EXPECTED_CONTENT_TYPE) - 1
38
39 /* default maximum size of payload eligable for GET request */
40 static const size_t kMaxPayloadSizeForGet = 2048;
41
42 namespace {
43 struct call_data {
44 grpc_call_combiner* call_combiner;
45 // State for handling send_initial_metadata ops.
46 grpc_linked_mdelem method;
47 grpc_linked_mdelem scheme;
48 grpc_linked_mdelem authority;
49 grpc_linked_mdelem te_trailers;
50 grpc_linked_mdelem content_type;
51 grpc_linked_mdelem user_agent;
52 // State for handling recv_initial_metadata ops.
53 grpc_metadata_batch* recv_initial_metadata;
54 grpc_error* recv_initial_metadata_error;
55 grpc_closure* original_recv_initial_metadata_ready;
56 grpc_closure recv_initial_metadata_ready;
57 // State for handling recv_trailing_metadata ops.
58 grpc_metadata_batch* recv_trailing_metadata;
59 grpc_closure* original_recv_trailing_metadata_ready;
60 grpc_closure recv_trailing_metadata_ready;
61 // State for handling send_message ops.
62 grpc_transport_stream_op_batch* send_message_batch;
63 size_t send_message_bytes_read;
64 grpc_core::ManualConstructor<grpc_core::ByteStreamCache> send_message_cache;
65 grpc_core::ManualConstructor<grpc_core::ByteStreamCache::CachingByteStream>
66 send_message_caching_stream;
67 grpc_closure on_send_message_next_done;
68 grpc_closure* original_send_message_on_complete;
69 grpc_closure send_message_on_complete;
70 };
71
72 struct channel_data {
73 grpc_mdelem static_scheme;
74 grpc_mdelem user_agent;
75 size_t max_payload_size_for_get;
76 };
77 } // namespace
78
client_filter_incoming_metadata(grpc_call_element * elem,grpc_metadata_batch * b)79 static grpc_error* client_filter_incoming_metadata(grpc_call_element* elem,
80 grpc_metadata_batch* b) {
81 if (b->idx.named.status != nullptr) {
82 /* If both gRPC status and HTTP status are provided in the response, we
83 * should prefer the gRPC status code, as mentioned in
84 * https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md.
85 */
86 if (b->idx.named.grpc_status != nullptr ||
87 grpc_mdelem_eq(b->idx.named.status->md, GRPC_MDELEM_STATUS_200)) {
88 grpc_metadata_batch_remove(b, b->idx.named.status);
89 } else {
90 char* val = grpc_dump_slice(GRPC_MDVALUE(b->idx.named.status->md),
91 GPR_DUMP_ASCII);
92 char* msg;
93 gpr_asprintf(&msg, "Received http2 header with status: %s", val);
94 grpc_error* e = grpc_error_set_str(
95 grpc_error_set_int(
96 grpc_error_set_str(
97 GRPC_ERROR_CREATE_FROM_STATIC_STRING(
98 "Received http2 :status header with non-200 OK status"),
99 GRPC_ERROR_STR_VALUE, grpc_slice_from_copied_string(val)),
100 GRPC_ERROR_INT_GRPC_STATUS, GRPC_STATUS_CANCELLED),
101 GRPC_ERROR_STR_GRPC_MESSAGE, grpc_slice_from_copied_string(msg));
102 gpr_free(val);
103 gpr_free(msg);
104 return e;
105 }
106 }
107
108 if (b->idx.named.grpc_message != nullptr) {
109 grpc_slice pct_decoded_msg = grpc_permissive_percent_decode_slice(
110 GRPC_MDVALUE(b->idx.named.grpc_message->md));
111 if (grpc_slice_is_equivalent(pct_decoded_msg,
112 GRPC_MDVALUE(b->idx.named.grpc_message->md))) {
113 grpc_slice_unref_internal(pct_decoded_msg);
114 } else {
115 grpc_metadata_batch_set_value(b->idx.named.grpc_message, pct_decoded_msg);
116 }
117 }
118
119 if (b->idx.named.content_type != nullptr) {
120 if (!grpc_mdelem_eq(b->idx.named.content_type->md,
121 GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC)) {
122 if (grpc_slice_buf_start_eq(GRPC_MDVALUE(b->idx.named.content_type->md),
123 EXPECTED_CONTENT_TYPE,
124 EXPECTED_CONTENT_TYPE_LENGTH) &&
125 (GRPC_SLICE_START_PTR(GRPC_MDVALUE(
126 b->idx.named.content_type->md))[EXPECTED_CONTENT_TYPE_LENGTH] ==
127 '+' ||
128 GRPC_SLICE_START_PTR(GRPC_MDVALUE(
129 b->idx.named.content_type->md))[EXPECTED_CONTENT_TYPE_LENGTH] ==
130 ';')) {
131 /* Although the C implementation doesn't (currently) generate them,
132 any custom +-suffix is explicitly valid. */
133 /* TODO(klempner): We should consider preallocating common values such
134 as +proto or +json, or at least stashing them if we see them. */
135 /* TODO(klempner): Should we be surfacing this to application code? */
136 } else {
137 /* TODO(klempner): We're currently allowing this, but we shouldn't
138 see it without a proxy so log for now. */
139 char* val = grpc_dump_slice(GRPC_MDVALUE(b->idx.named.content_type->md),
140 GPR_DUMP_ASCII);
141 gpr_log(GPR_INFO, "Unexpected content-type '%s'", val);
142 gpr_free(val);
143 }
144 }
145 grpc_metadata_batch_remove(b, b->idx.named.content_type);
146 }
147
148 return GRPC_ERROR_NONE;
149 }
150
recv_initial_metadata_ready(void * user_data,grpc_error * error)151 static void recv_initial_metadata_ready(void* user_data, grpc_error* error) {
152 grpc_call_element* elem = static_cast<grpc_call_element*>(user_data);
153 call_data* calld = static_cast<call_data*>(elem->call_data);
154 if (error == GRPC_ERROR_NONE) {
155 error = client_filter_incoming_metadata(elem, calld->recv_initial_metadata);
156 calld->recv_initial_metadata_error = GRPC_ERROR_REF(error);
157 } else {
158 GRPC_ERROR_REF(error);
159 }
160 GRPC_CLOSURE_RUN(calld->original_recv_initial_metadata_ready, error);
161 }
162
recv_trailing_metadata_ready(void * user_data,grpc_error * error)163 static void recv_trailing_metadata_ready(void* user_data, grpc_error* error) {
164 grpc_call_element* elem = static_cast<grpc_call_element*>(user_data);
165 call_data* calld = static_cast<call_data*>(elem->call_data);
166 if (error == GRPC_ERROR_NONE) {
167 error =
168 client_filter_incoming_metadata(elem, calld->recv_trailing_metadata);
169 } else {
170 GRPC_ERROR_REF(error);
171 }
172 error = grpc_error_add_child(
173 error, GRPC_ERROR_REF(calld->recv_initial_metadata_error));
174 GRPC_CLOSURE_RUN(calld->original_recv_trailing_metadata_ready, error);
175 }
176
send_message_on_complete(void * arg,grpc_error * error)177 static void send_message_on_complete(void* arg, grpc_error* error) {
178 grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
179 call_data* calld = static_cast<call_data*>(elem->call_data);
180 calld->send_message_cache.Destroy();
181 GRPC_CLOSURE_RUN(calld->original_send_message_on_complete,
182 GRPC_ERROR_REF(error));
183 }
184
185 // Pulls a slice from the send_message byte stream, updating
186 // calld->send_message_bytes_read.
pull_slice_from_send_message(call_data * calld)187 static grpc_error* pull_slice_from_send_message(call_data* calld) {
188 grpc_slice incoming_slice;
189 grpc_error* error = calld->send_message_caching_stream->Pull(&incoming_slice);
190 if (error == GRPC_ERROR_NONE) {
191 calld->send_message_bytes_read += GRPC_SLICE_LENGTH(incoming_slice);
192 grpc_slice_unref_internal(incoming_slice);
193 }
194 return error;
195 }
196
197 // Reads as many slices as possible from the send_message byte stream.
198 // Upon successful return, if calld->send_message_bytes_read ==
199 // calld->send_message_caching_stream->length(), then we have completed
200 // reading from the byte stream; otherwise, an async read has been dispatched
201 // and on_send_message_next_done() will be invoked when it is complete.
read_all_available_send_message_data(call_data * calld)202 static grpc_error* read_all_available_send_message_data(call_data* calld) {
203 while (calld->send_message_caching_stream->Next(
204 SIZE_MAX, &calld->on_send_message_next_done)) {
205 grpc_error* error = pull_slice_from_send_message(calld);
206 if (error != GRPC_ERROR_NONE) return error;
207 if (calld->send_message_bytes_read ==
208 calld->send_message_caching_stream->length()) {
209 break;
210 }
211 }
212 return GRPC_ERROR_NONE;
213 }
214
215 // Async callback for ByteStream::Next().
on_send_message_next_done(void * arg,grpc_error * error)216 static void on_send_message_next_done(void* arg, grpc_error* error) {
217 grpc_call_element* elem = static_cast<grpc_call_element*>(arg);
218 call_data* calld = static_cast<call_data*>(elem->call_data);
219 if (error != GRPC_ERROR_NONE) {
220 grpc_transport_stream_op_batch_finish_with_failure(
221 calld->send_message_batch, error, calld->call_combiner);
222 return;
223 }
224 error = pull_slice_from_send_message(calld);
225 if (error != GRPC_ERROR_NONE) {
226 grpc_transport_stream_op_batch_finish_with_failure(
227 calld->send_message_batch, error, calld->call_combiner);
228 return;
229 }
230 // There may or may not be more to read, but we don't care. If we got
231 // here, then we know that all of the data was not available
232 // synchronously, so we were not able to do a cached call. Instead,
233 // we just reset the byte stream and then send down the batch as-is.
234 calld->send_message_caching_stream->Reset();
235 grpc_call_next_op(elem, calld->send_message_batch);
236 }
237
slice_buffer_to_string(grpc_slice_buffer * slice_buffer)238 static char* slice_buffer_to_string(grpc_slice_buffer* slice_buffer) {
239 char* payload_bytes =
240 static_cast<char*>(gpr_malloc(slice_buffer->length + 1));
241 size_t offset = 0;
242 for (size_t i = 0; i < slice_buffer->count; ++i) {
243 memcpy(payload_bytes + offset,
244 GRPC_SLICE_START_PTR(slice_buffer->slices[i]),
245 GRPC_SLICE_LENGTH(slice_buffer->slices[i]));
246 offset += GRPC_SLICE_LENGTH(slice_buffer->slices[i]);
247 }
248 *(payload_bytes + offset) = '\0';
249 return payload_bytes;
250 }
251
252 // Modifies the path entry in the batch's send_initial_metadata to
253 // append the base64-encoded query for a GET request.
update_path_for_get(grpc_call_element * elem,grpc_transport_stream_op_batch * batch)254 static grpc_error* update_path_for_get(grpc_call_element* elem,
255 grpc_transport_stream_op_batch* batch) {
256 call_data* calld = static_cast<call_data*>(elem->call_data);
257 grpc_slice path_slice =
258 GRPC_MDVALUE(batch->payload->send_initial_metadata.send_initial_metadata
259 ->idx.named.path->md);
260 /* sum up individual component's lengths and allocate enough memory to
261 * hold combined path+query */
262 size_t estimated_len = GRPC_SLICE_LENGTH(path_slice);
263 estimated_len++; /* for the '?' */
264 estimated_len += grpc_base64_estimate_encoded_size(
265 batch->payload->send_message.send_message->length(), true /* url_safe */,
266 false /* multi_line */);
267 grpc_slice path_with_query_slice = GRPC_SLICE_MALLOC(estimated_len);
268 /* memcopy individual pieces into this slice */
269 char* write_ptr =
270 reinterpret_cast<char*> GRPC_SLICE_START_PTR(path_with_query_slice);
271 char* original_path =
272 reinterpret_cast<char*> GRPC_SLICE_START_PTR(path_slice);
273 memcpy(write_ptr, original_path, GRPC_SLICE_LENGTH(path_slice));
274 write_ptr += GRPC_SLICE_LENGTH(path_slice);
275 *write_ptr++ = '?';
276 char* payload_bytes =
277 slice_buffer_to_string(calld->send_message_cache->cache_buffer());
278 grpc_base64_encode_core(write_ptr, payload_bytes,
279 batch->payload->send_message.send_message->length(),
280 true /* url_safe */, false /* multi_line */);
281 gpr_free(payload_bytes);
282 /* remove trailing unused memory and add trailing 0 to terminate string */
283 char* t = reinterpret_cast<char*> GRPC_SLICE_START_PTR(path_with_query_slice);
284 /* safe to use strlen since base64_encode will always add '\0' */
285 path_with_query_slice =
286 grpc_slice_sub_no_ref(path_with_query_slice, 0, strlen(t));
287 /* substitute previous path with the new path+query */
288 grpc_mdelem mdelem_path_and_query =
289 grpc_mdelem_from_slices(GRPC_MDSTR_PATH, path_with_query_slice);
290 grpc_metadata_batch* b =
291 batch->payload->send_initial_metadata.send_initial_metadata;
292 return grpc_metadata_batch_substitute(b, b->idx.named.path,
293 mdelem_path_and_query);
294 }
295
remove_if_present(grpc_metadata_batch * batch,grpc_metadata_batch_callouts_index idx)296 static void remove_if_present(grpc_metadata_batch* batch,
297 grpc_metadata_batch_callouts_index idx) {
298 if (batch->idx.array[idx] != nullptr) {
299 grpc_metadata_batch_remove(batch, batch->idx.array[idx]);
300 }
301 }
302
hc_start_transport_stream_op_batch(grpc_call_element * elem,grpc_transport_stream_op_batch * batch)303 static void hc_start_transport_stream_op_batch(
304 grpc_call_element* elem, grpc_transport_stream_op_batch* batch) {
305 call_data* calld = static_cast<call_data*>(elem->call_data);
306 channel_data* channeld = static_cast<channel_data*>(elem->channel_data);
307 GPR_TIMER_SCOPE("hc_start_transport_stream_op_batch", 0);
308
309 if (batch->recv_initial_metadata) {
310 /* substitute our callback for the higher callback */
311 calld->recv_initial_metadata =
312 batch->payload->recv_initial_metadata.recv_initial_metadata;
313 calld->original_recv_initial_metadata_ready =
314 batch->payload->recv_initial_metadata.recv_initial_metadata_ready;
315 batch->payload->recv_initial_metadata.recv_initial_metadata_ready =
316 &calld->recv_initial_metadata_ready;
317 }
318
319 if (batch->recv_trailing_metadata) {
320 /* substitute our callback for the higher callback */
321 calld->recv_trailing_metadata =
322 batch->payload->recv_trailing_metadata.recv_trailing_metadata;
323 calld->original_recv_trailing_metadata_ready =
324 batch->payload->recv_trailing_metadata.recv_trailing_metadata_ready;
325 batch->payload->recv_trailing_metadata.recv_trailing_metadata_ready =
326 &calld->recv_trailing_metadata_ready;
327 }
328
329 grpc_error* error = GRPC_ERROR_NONE;
330 bool batch_will_be_handled_asynchronously = false;
331 if (batch->send_initial_metadata) {
332 // Decide which HTTP VERB to use. We use GET if the request is marked
333 // cacheable, and the operation contains both initial metadata and send
334 // message, and the payload is below the size threshold, and all the data
335 // for this request is immediately available.
336 grpc_mdelem method = GRPC_MDELEM_METHOD_POST;
337 if (batch->send_message &&
338 (batch->payload->send_initial_metadata.send_initial_metadata_flags &
339 GRPC_INITIAL_METADATA_CACHEABLE_REQUEST) &&
340 batch->payload->send_message.send_message->length() <
341 channeld->max_payload_size_for_get) {
342 calld->send_message_bytes_read = 0;
343 calld->send_message_cache.Init(
344 std::move(batch->payload->send_message.send_message));
345 calld->send_message_caching_stream.Init(calld->send_message_cache.get());
346 batch->payload->send_message.send_message.reset(
347 calld->send_message_caching_stream.get());
348 calld->original_send_message_on_complete = batch->on_complete;
349 batch->on_complete = &calld->send_message_on_complete;
350 calld->send_message_batch = batch;
351 error = read_all_available_send_message_data(calld);
352 if (error != GRPC_ERROR_NONE) goto done;
353 // If all the data has been read, then we can use GET.
354 if (calld->send_message_bytes_read ==
355 calld->send_message_caching_stream->length()) {
356 method = GRPC_MDELEM_METHOD_GET;
357 error = update_path_for_get(elem, batch);
358 if (error != GRPC_ERROR_NONE) goto done;
359 batch->send_message = false;
360 calld->send_message_caching_stream->Orphan();
361 } else {
362 // Not all data is available. The batch will be sent down
363 // asynchronously in on_send_message_next_done().
364 batch_will_be_handled_asynchronously = true;
365 // Fall back to POST.
366 gpr_log(GPR_DEBUG,
367 "Request is marked Cacheable but not all data is available. "
368 "Falling back to POST");
369 }
370 } else if (batch->payload->send_initial_metadata
371 .send_initial_metadata_flags &
372 GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST) {
373 method = GRPC_MDELEM_METHOD_PUT;
374 }
375
376 remove_if_present(
377 batch->payload->send_initial_metadata.send_initial_metadata,
378 GRPC_BATCH_METHOD);
379 remove_if_present(
380 batch->payload->send_initial_metadata.send_initial_metadata,
381 GRPC_BATCH_SCHEME);
382 remove_if_present(
383 batch->payload->send_initial_metadata.send_initial_metadata,
384 GRPC_BATCH_TE);
385 remove_if_present(
386 batch->payload->send_initial_metadata.send_initial_metadata,
387 GRPC_BATCH_CONTENT_TYPE);
388 remove_if_present(
389 batch->payload->send_initial_metadata.send_initial_metadata,
390 GRPC_BATCH_USER_AGENT);
391
392 /* Send : prefixed headers, which have to be before any application
393 layer headers. */
394 error = grpc_metadata_batch_add_head(
395 batch->payload->send_initial_metadata.send_initial_metadata,
396 &calld->method, method);
397 if (error != GRPC_ERROR_NONE) goto done;
398 error = grpc_metadata_batch_add_head(
399 batch->payload->send_initial_metadata.send_initial_metadata,
400 &calld->scheme, channeld->static_scheme);
401 if (error != GRPC_ERROR_NONE) goto done;
402 error = grpc_metadata_batch_add_tail(
403 batch->payload->send_initial_metadata.send_initial_metadata,
404 &calld->te_trailers, GRPC_MDELEM_TE_TRAILERS);
405 if (error != GRPC_ERROR_NONE) goto done;
406 error = grpc_metadata_batch_add_tail(
407 batch->payload->send_initial_metadata.send_initial_metadata,
408 &calld->content_type, GRPC_MDELEM_CONTENT_TYPE_APPLICATION_SLASH_GRPC);
409 if (error != GRPC_ERROR_NONE) goto done;
410 error = grpc_metadata_batch_add_tail(
411 batch->payload->send_initial_metadata.send_initial_metadata,
412 &calld->user_agent, GRPC_MDELEM_REF(channeld->user_agent));
413 if (error != GRPC_ERROR_NONE) goto done;
414 }
415
416 done:
417 if (error != GRPC_ERROR_NONE) {
418 grpc_transport_stream_op_batch_finish_with_failure(
419 calld->send_message_batch, error, calld->call_combiner);
420 } else if (!batch_will_be_handled_asynchronously) {
421 grpc_call_next_op(elem, batch);
422 }
423 }
424
425 /* Constructor for call_data */
init_call_elem(grpc_call_element * elem,const grpc_call_element_args * args)426 static grpc_error* init_call_elem(grpc_call_element* elem,
427 const grpc_call_element_args* args) {
428 call_data* calld = static_cast<call_data*>(elem->call_data);
429 calld->call_combiner = args->call_combiner;
430 GRPC_CLOSURE_INIT(&calld->recv_initial_metadata_ready,
431 recv_initial_metadata_ready, elem,
432 grpc_schedule_on_exec_ctx);
433 GRPC_CLOSURE_INIT(&calld->recv_trailing_metadata_ready,
434 recv_trailing_metadata_ready, elem,
435 grpc_schedule_on_exec_ctx);
436 GRPC_CLOSURE_INIT(&calld->send_message_on_complete, send_message_on_complete,
437 elem, grpc_schedule_on_exec_ctx);
438 GRPC_CLOSURE_INIT(&calld->on_send_message_next_done,
439 on_send_message_next_done, elem, grpc_schedule_on_exec_ctx);
440 return GRPC_ERROR_NONE;
441 }
442
443 /* Destructor for call_data */
destroy_call_elem(grpc_call_element * elem,const grpc_call_final_info * final_info,grpc_closure * ignored)444 static void destroy_call_elem(grpc_call_element* elem,
445 const grpc_call_final_info* final_info,
446 grpc_closure* ignored) {
447 call_data* calld = static_cast<call_data*>(elem->call_data);
448 GRPC_ERROR_UNREF(calld->recv_initial_metadata_error);
449 }
450
scheme_from_args(const grpc_channel_args * args)451 static grpc_mdelem scheme_from_args(const grpc_channel_args* args) {
452 unsigned i;
453 size_t j;
454 grpc_mdelem valid_schemes[] = {GRPC_MDELEM_SCHEME_HTTP,
455 GRPC_MDELEM_SCHEME_HTTPS};
456 if (args != nullptr) {
457 for (i = 0; i < args->num_args; ++i) {
458 if (args->args[i].type == GRPC_ARG_STRING &&
459 strcmp(args->args[i].key, GRPC_ARG_HTTP2_SCHEME) == 0) {
460 for (j = 0; j < GPR_ARRAY_SIZE(valid_schemes); j++) {
461 if (0 == grpc_slice_str_cmp(GRPC_MDVALUE(valid_schemes[j]),
462 args->args[i].value.string)) {
463 return valid_schemes[j];
464 }
465 }
466 }
467 }
468 }
469 return GRPC_MDELEM_SCHEME_HTTP;
470 }
471
max_payload_size_from_args(const grpc_channel_args * args)472 static size_t max_payload_size_from_args(const grpc_channel_args* args) {
473 if (args != nullptr) {
474 for (size_t i = 0; i < args->num_args; ++i) {
475 if (0 == strcmp(args->args[i].key, GRPC_ARG_MAX_PAYLOAD_SIZE_FOR_GET)) {
476 if (args->args[i].type != GRPC_ARG_INTEGER) {
477 gpr_log(GPR_ERROR, "%s: must be an integer",
478 GRPC_ARG_MAX_PAYLOAD_SIZE_FOR_GET);
479 } else {
480 return static_cast<size_t>(args->args[i].value.integer);
481 }
482 }
483 }
484 }
485 return kMaxPayloadSizeForGet;
486 }
487
user_agent_from_args(const grpc_channel_args * args,const char * transport_name)488 static grpc_slice user_agent_from_args(const grpc_channel_args* args,
489 const char* transport_name) {
490 gpr_strvec v;
491 size_t i;
492 int is_first = 1;
493 char* tmp;
494 grpc_slice result;
495
496 gpr_strvec_init(&v);
497
498 for (i = 0; args && i < args->num_args; i++) {
499 if (0 == strcmp(args->args[i].key, GRPC_ARG_PRIMARY_USER_AGENT_STRING)) {
500 if (args->args[i].type != GRPC_ARG_STRING) {
501 gpr_log(GPR_ERROR, "Channel argument '%s' should be a string",
502 GRPC_ARG_PRIMARY_USER_AGENT_STRING);
503 } else {
504 if (!is_first) gpr_strvec_add(&v, gpr_strdup(" "));
505 is_first = 0;
506 gpr_strvec_add(&v, gpr_strdup(args->args[i].value.string));
507 }
508 }
509 }
510
511 gpr_asprintf(&tmp, "%sgrpc-c/%s (%s; %s; %s)", is_first ? "" : " ",
512 grpc_version_string(), GPR_PLATFORM_STRING, transport_name,
513 grpc_g_stands_for());
514 is_first = 0;
515 gpr_strvec_add(&v, tmp);
516
517 for (i = 0; args && i < args->num_args; i++) {
518 if (0 == strcmp(args->args[i].key, GRPC_ARG_SECONDARY_USER_AGENT_STRING)) {
519 if (args->args[i].type != GRPC_ARG_STRING) {
520 gpr_log(GPR_ERROR, "Channel argument '%s' should be a string",
521 GRPC_ARG_SECONDARY_USER_AGENT_STRING);
522 } else {
523 if (!is_first) gpr_strvec_add(&v, gpr_strdup(" "));
524 is_first = 0;
525 gpr_strvec_add(&v, gpr_strdup(args->args[i].value.string));
526 }
527 }
528 }
529
530 tmp = gpr_strvec_flatten(&v, nullptr);
531 gpr_strvec_destroy(&v);
532 result = grpc_slice_intern(grpc_slice_from_static_string(tmp));
533 gpr_free(tmp);
534
535 return result;
536 }
537
538 /* Constructor for channel_data */
init_channel_elem(grpc_channel_element * elem,grpc_channel_element_args * args)539 static grpc_error* init_channel_elem(grpc_channel_element* elem,
540 grpc_channel_element_args* args) {
541 channel_data* chand = static_cast<channel_data*>(elem->channel_data);
542 GPR_ASSERT(!args->is_last);
543 GPR_ASSERT(args->optional_transport != nullptr);
544 chand->static_scheme = scheme_from_args(args->channel_args);
545 chand->max_payload_size_for_get =
546 max_payload_size_from_args(args->channel_args);
547 chand->user_agent = grpc_mdelem_from_slices(
548 GRPC_MDSTR_USER_AGENT,
549 user_agent_from_args(args->channel_args,
550 args->optional_transport->vtable->name));
551 return GRPC_ERROR_NONE;
552 }
553
554 /* Destructor for channel data */
destroy_channel_elem(grpc_channel_element * elem)555 static void destroy_channel_elem(grpc_channel_element* elem) {
556 channel_data* chand = static_cast<channel_data*>(elem->channel_data);
557 GRPC_MDELEM_UNREF(chand->user_agent);
558 }
559
560 const grpc_channel_filter grpc_http_client_filter = {
561 hc_start_transport_stream_op_batch,
562 grpc_channel_next_op,
563 sizeof(call_data),
564 init_call_elem,
565 grpc_call_stack_ignore_set_pollset_or_pollset_set,
566 destroy_call_elem,
567 sizeof(channel_data),
568 init_channel_elem,
569 destroy_channel_elem,
570 grpc_channel_next_get_info,
571 "http-client"};
572