1 /*
2 *
3 * Copyright 2016 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 #include "test/cpp/util/grpc_tool.h"
20
21 #include <cstdio>
22 #include <fstream>
23 #include <iostream>
24 #include <memory>
25 #include <sstream>
26 #include <string>
27 #include <thread>
28
29 #include <gflags/gflags.h>
30 #include <grpc/grpc.h>
31 #include <grpc/support/port_platform.h>
32 #include <grpcpp/channel.h>
33 #include <grpcpp/create_channel.h>
34 #include <grpcpp/grpcpp.h>
35 #include <grpcpp/security/credentials.h>
36 #include <grpcpp/support/string_ref.h>
37
38 #include "test/cpp/util/cli_call.h"
39 #include "test/cpp/util/proto_file_parser.h"
40 #include "test/cpp/util/proto_reflection_descriptor_database.h"
41 #include "test/cpp/util/service_describer.h"
42
43 #if GPR_WINDOWS
44 #include <io.h>
45 #else
46 #include <unistd.h>
47 #endif
48
49 namespace grpc {
50 namespace testing {
51
52 DEFINE_bool(l, false, "Use a long listing format");
53 DEFINE_bool(remotedb, true, "Use server types to parse and format messages");
54 DEFINE_string(metadata, "",
55 "Metadata to send to server, in the form of key1:val1:key2:val2");
56 DEFINE_string(proto_path, ".", "Path to look for the proto file.");
57 DEFINE_string(protofiles, "", "Name of the proto file.");
58 DEFINE_bool(binary_input, false, "Input in binary format");
59 DEFINE_bool(binary_output, false, "Output in binary format");
60 DEFINE_string(
61 default_service_config, "",
62 "Default service config to use on the channel, if non-empty. Note "
63 "that this will be ignored if the name resolver returns a service "
64 "config.");
65 DEFINE_bool(json_input, false, "Input in json format");
66 DEFINE_bool(json_output, false, "Output in json format");
67 DEFINE_string(infile, "", "Input file (default is stdin)");
68 DEFINE_bool(batch, false,
69 "Input contains multiple requests. Please do not use this to send "
70 "more than a few RPCs. gRPC CLI has very different performance "
71 "characteristics compared with normal RPC calls which make it "
72 "unsuitable for loadtesting or significant production traffic.");
73
74 namespace {
75
76 class GrpcTool {
77 public:
78 explicit GrpcTool();
~GrpcTool()79 virtual ~GrpcTool() {}
80
81 bool Help(int argc, const char** argv, const CliCredentials& cred,
82 GrpcToolOutputCallback callback);
83 bool CallMethod(int argc, const char** argv, const CliCredentials& cred,
84 GrpcToolOutputCallback callback);
85 bool ListServices(int argc, const char** argv, const CliCredentials& cred,
86 GrpcToolOutputCallback callback);
87 bool PrintType(int argc, const char** argv, const CliCredentials& cred,
88 GrpcToolOutputCallback callback);
89 // TODO(zyc): implement the following methods
90 // bool ListServices(int argc, const char** argv, GrpcToolOutputCallback
91 // callback);
92 // bool PrintTypeId(int argc, const char** argv, GrpcToolOutputCallback
93 // callback);
94 bool ParseMessage(int argc, const char** argv, const CliCredentials& cred,
95 GrpcToolOutputCallback callback);
96 bool ToText(int argc, const char** argv, const CliCredentials& cred,
97 GrpcToolOutputCallback callback);
98 bool ToJson(int argc, const char** argv, const CliCredentials& cred,
99 GrpcToolOutputCallback callback);
100 bool ToBinary(int argc, const char** argv, const CliCredentials& cred,
101 GrpcToolOutputCallback callback);
102
SetPrintCommandMode(int exit_status)103 void SetPrintCommandMode(int exit_status) {
104 print_command_usage_ = true;
105 usage_exit_status_ = exit_status;
106 }
107
108 private:
109 void CommandUsage(const std::string& usage) const;
110 bool print_command_usage_;
111 int usage_exit_status_;
112 const std::string cred_usage_;
113 };
114
115 template <typename T>
116 std::function<bool(GrpcTool*, int, const char**, const CliCredentials&,
117 GrpcToolOutputCallback)>
BindWith5Args(T && func)118 BindWith5Args(T&& func) {
119 return std::bind(std::forward<T>(func), std::placeholders::_1,
120 std::placeholders::_2, std::placeholders::_3,
121 std::placeholders::_4, std::placeholders::_5);
122 }
123
124 template <typename T>
ArraySize(T & a)125 size_t ArraySize(T& a) {
126 return ((sizeof(a) / sizeof(*(a))) /
127 static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))));
128 }
129
ParseMetadataFlag(std::multimap<std::string,std::string> * client_metadata)130 void ParseMetadataFlag(
131 std::multimap<std::string, std::string>* client_metadata) {
132 if (FLAGS_metadata.empty()) {
133 return;
134 }
135 std::vector<std::string> fields;
136 const char delim = ':';
137 const char escape = '\\';
138 size_t cur = -1;
139 std::stringstream ss;
140 while (++cur < FLAGS_metadata.length()) {
141 switch (FLAGS_metadata.at(cur)) {
142 case escape:
143 if (cur < FLAGS_metadata.length() - 1) {
144 char c = FLAGS_metadata.at(++cur);
145 if (c == delim || c == escape) {
146 ss << c;
147 continue;
148 }
149 }
150 fprintf(stderr, "Failed to parse metadata flag.\n");
151 exit(1);
152 case delim:
153 fields.push_back(ss.str());
154 ss.str("");
155 ss.clear();
156 break;
157 default:
158 ss << FLAGS_metadata.at(cur);
159 }
160 }
161 fields.push_back(ss.str());
162 if (fields.size() % 2) {
163 fprintf(stderr, "Failed to parse metadata flag.\n");
164 exit(1);
165 }
166 for (size_t i = 0; i < fields.size(); i += 2) {
167 client_metadata->insert(
168 std::pair<std::string, std::string>(fields[i], fields[i + 1]));
169 }
170 }
171
172 template <typename T>
PrintMetadata(const T & m,const std::string & message)173 void PrintMetadata(const T& m, const std::string& message) {
174 if (m.empty()) {
175 return;
176 }
177 fprintf(stderr, "%s\n", message.c_str());
178 std::string pair;
179 for (typename T::const_iterator iter = m.begin(); iter != m.end(); ++iter) {
180 pair.clear();
181 pair.append(iter->first.data(), iter->first.size());
182 pair.append(" : ");
183 pair.append(iter->second.data(), iter->second.size());
184 fprintf(stderr, "%s\n", pair.c_str());
185 }
186 }
187
ReadResponse(CliCall * call,const std::string & method_name,GrpcToolOutputCallback callback,ProtoFileParser * parser,gpr_mu * parser_mu,bool print_mode)188 void ReadResponse(CliCall* call, const std::string& method_name,
189 GrpcToolOutputCallback callback, ProtoFileParser* parser,
190 gpr_mu* parser_mu, bool print_mode) {
191 std::string serialized_response_proto;
192 std::multimap<grpc::string_ref, grpc::string_ref> server_initial_metadata;
193
194 for (bool receive_initial_metadata = true; call->ReadAndMaybeNotifyWrite(
195 &serialized_response_proto,
196 receive_initial_metadata ? &server_initial_metadata : nullptr);
197 receive_initial_metadata = false) {
198 fprintf(stderr, "got response.\n");
199 if (!FLAGS_binary_output) {
200 gpr_mu_lock(parser_mu);
201 serialized_response_proto = parser->GetFormattedStringFromMethod(
202 method_name, serialized_response_proto, false /* is_request */,
203 FLAGS_json_output);
204 if (parser->HasError() && print_mode) {
205 fprintf(stderr, "Failed to parse response.\n");
206 }
207 gpr_mu_unlock(parser_mu);
208 }
209 if (receive_initial_metadata) {
210 PrintMetadata(server_initial_metadata,
211 "Received initial metadata from server:");
212 }
213 if (!callback(serialized_response_proto) && print_mode) {
214 fprintf(stderr, "Failed to output response.\n");
215 }
216 }
217 }
218
CreateCliChannel(const std::string & server_address,const CliCredentials & cred)219 std::shared_ptr<grpc::Channel> CreateCliChannel(
220 const std::string& server_address, const CliCredentials& cred) {
221 grpc::ChannelArguments args;
222 if (!cred.GetSslTargetNameOverride().empty()) {
223 args.SetSslTargetNameOverride(cred.GetSslTargetNameOverride());
224 }
225 if (!FLAGS_default_service_config.empty()) {
226 args.SetString(GRPC_ARG_SERVICE_CONFIG,
227 FLAGS_default_service_config.c_str());
228 }
229 return ::grpc::CreateCustomChannel(server_address, cred.GetCredentials(),
230 args);
231 }
232
233 struct Command {
234 const char* command;
235 std::function<bool(GrpcTool*, int, const char**, const CliCredentials&,
236 GrpcToolOutputCallback)>
237 function;
238 int min_args;
239 int max_args;
240 };
241
242 const Command ops[] = {
243 {"help", BindWith5Args(&GrpcTool::Help), 0, INT_MAX},
244 {"ls", BindWith5Args(&GrpcTool::ListServices), 1, 3},
245 {"list", BindWith5Args(&GrpcTool::ListServices), 1, 3},
246 {"call", BindWith5Args(&GrpcTool::CallMethod), 2, 3},
247 {"type", BindWith5Args(&GrpcTool::PrintType), 2, 2},
248 {"parse", BindWith5Args(&GrpcTool::ParseMessage), 2, 3},
249 {"totext", BindWith5Args(&GrpcTool::ToText), 2, 3},
250 {"tobinary", BindWith5Args(&GrpcTool::ToBinary), 2, 3},
251 {"tojson", BindWith5Args(&GrpcTool::ToJson), 2, 3},
252 };
253
Usage(const std::string & msg)254 void Usage(const std::string& msg) {
255 fprintf(
256 stderr,
257 "%s\n"
258 " grpc_cli ls ... ; List services\n"
259 " grpc_cli call ... ; Call method\n"
260 " grpc_cli type ... ; Print type\n"
261 " grpc_cli parse ... ; Parse message\n"
262 " grpc_cli totext ... ; Convert binary message to text\n"
263 " grpc_cli tojson ... ; Convert binary message to json\n"
264 " grpc_cli tobinary ... ; Convert text message to binary\n"
265 " grpc_cli help ... ; Print this message, or per-command usage\n"
266 "\n",
267 msg.c_str());
268
269 exit(1);
270 }
271
FindCommand(const std::string & name)272 const Command* FindCommand(const std::string& name) {
273 for (int i = 0; i < (int)ArraySize(ops); i++) {
274 if (name == ops[i].command) {
275 return &ops[i];
276 }
277 }
278 return nullptr;
279 }
280 } // namespace
281
GrpcToolMainLib(int argc,const char ** argv,const CliCredentials & cred,GrpcToolOutputCallback callback)282 int GrpcToolMainLib(int argc, const char** argv, const CliCredentials& cred,
283 GrpcToolOutputCallback callback) {
284 if (argc < 2) {
285 Usage("No command specified");
286 }
287
288 std::string command = argv[1];
289 argc -= 2;
290 argv += 2;
291
292 const Command* cmd = FindCommand(command);
293 if (cmd != nullptr) {
294 GrpcTool grpc_tool;
295 if (argc < cmd->min_args || argc > cmd->max_args) {
296 // Force the command to print its usage message
297 fprintf(stderr, "\nWrong number of arguments for %s\n", command.c_str());
298 grpc_tool.SetPrintCommandMode(1);
299 return cmd->function(&grpc_tool, -1, nullptr, cred, callback);
300 }
301 const bool ok = cmd->function(&grpc_tool, argc, argv, cred, callback);
302 return ok ? 0 : 1;
303 } else {
304 Usage("Invalid command '" + std::string(command.c_str()) + "'");
305 }
306 return 1;
307 }
308
GrpcTool()309 GrpcTool::GrpcTool() : print_command_usage_(false), usage_exit_status_(0) {}
310
CommandUsage(const std::string & usage) const311 void GrpcTool::CommandUsage(const std::string& usage) const {
312 if (print_command_usage_) {
313 fprintf(stderr, "\n%s%s\n", usage.c_str(),
314 (usage.empty() || usage[usage.size() - 1] != '\n') ? "\n" : "");
315 exit(usage_exit_status_);
316 }
317 }
318
Help(int argc,const char ** argv,const CliCredentials & cred,GrpcToolOutputCallback callback)319 bool GrpcTool::Help(int argc, const char** argv, const CliCredentials& cred,
320 GrpcToolOutputCallback callback) {
321 CommandUsage(
322 "Print help\n"
323 " grpc_cli help [subcommand]\n");
324
325 if (argc == 0) {
326 Usage("");
327 } else {
328 const Command* cmd = FindCommand(argv[0]);
329 if (cmd == nullptr) {
330 Usage("Unknown command '" + std::string(argv[0]) + "'");
331 }
332 SetPrintCommandMode(0);
333 cmd->function(this, -1, nullptr, cred, callback);
334 }
335 return true;
336 }
337
ListServices(int argc,const char ** argv,const CliCredentials & cred,GrpcToolOutputCallback callback)338 bool GrpcTool::ListServices(int argc, const char** argv,
339 const CliCredentials& cred,
340 GrpcToolOutputCallback callback) {
341 CommandUsage(
342 "List services\n"
343 " grpc_cli ls <address> [<service>[/<method>]]\n"
344 " <address> ; host:port\n"
345 " <service> ; Exported service name\n"
346 " <method> ; Method name\n"
347 " --l ; Use a long listing format\n"
348 " --outfile ; Output filename (defaults to stdout)\n" +
349 cred.GetCredentialUsage());
350
351 std::string server_address(argv[0]);
352 std::shared_ptr<grpc::Channel> channel =
353 CreateCliChannel(server_address, cred);
354 grpc::ProtoReflectionDescriptorDatabase desc_db(channel);
355 grpc::protobuf::DescriptorPool desc_pool(&desc_db);
356
357 std::vector<std::string> service_list;
358 if (!desc_db.GetServices(&service_list)) {
359 fprintf(stderr, "Received an error when querying services endpoint.\n");
360 return false;
361 }
362
363 // If no service is specified, dump the list of services.
364 std::string output;
365 if (argc < 2) {
366 // List all services, if --l is passed, then include full description,
367 // otherwise include a summarized list only.
368 if (FLAGS_l) {
369 output = DescribeServiceList(service_list, desc_pool);
370 } else {
371 for (auto it = service_list.begin(); it != service_list.end(); it++) {
372 auto const& service = *it;
373 output.append(service);
374 output.append("\n");
375 }
376 }
377 } else {
378 std::string service_name;
379 std::string method_name;
380 std::stringstream ss(argv[1]);
381
382 // Remove leading slashes.
383 while (ss.peek() == '/') {
384 ss.get();
385 }
386
387 // Parse service and method names. Support the following patterns:
388 // Service
389 // Service Method
390 // Service.Method
391 // Service/Method
392 if (argc == 3) {
393 std::getline(ss, service_name, '/');
394 method_name = argv[2];
395 } else {
396 if (std::getline(ss, service_name, '/')) {
397 std::getline(ss, method_name);
398 }
399 }
400
401 const grpc::protobuf::ServiceDescriptor* service =
402 desc_pool.FindServiceByName(service_name);
403 if (service != nullptr) {
404 if (method_name.empty()) {
405 output = FLAGS_l ? DescribeService(service) : SummarizeService(service);
406 } else {
407 method_name.insert(0, 1, '.');
408 method_name.insert(0, service_name);
409 const grpc::protobuf::MethodDescriptor* method =
410 desc_pool.FindMethodByName(method_name);
411 if (method != nullptr) {
412 output = FLAGS_l ? DescribeMethod(method) : SummarizeMethod(method);
413 } else {
414 fprintf(stderr, "Method %s not found in service %s.\n",
415 method_name.c_str(), service_name.c_str());
416 return false;
417 }
418 }
419 } else {
420 if (!method_name.empty()) {
421 fprintf(stderr, "Service %s not found.\n", service_name.c_str());
422 return false;
423 } else {
424 const grpc::protobuf::MethodDescriptor* method =
425 desc_pool.FindMethodByName(service_name);
426 if (method != nullptr) {
427 output = FLAGS_l ? DescribeMethod(method) : SummarizeMethod(method);
428 } else {
429 fprintf(stderr, "Service or method %s not found.\n",
430 service_name.c_str());
431 return false;
432 }
433 }
434 }
435 }
436 return callback(output);
437 }
438
PrintType(int,const char ** argv,const CliCredentials & cred,GrpcToolOutputCallback callback)439 bool GrpcTool::PrintType(int /*argc*/, const char** argv,
440 const CliCredentials& cred,
441 GrpcToolOutputCallback callback) {
442 CommandUsage(
443 "Print type\n"
444 " grpc_cli type <address> <type>\n"
445 " <address> ; host:port\n"
446 " <type> ; Protocol buffer type name\n" +
447 cred.GetCredentialUsage());
448
449 std::string server_address(argv[0]);
450 std::shared_ptr<grpc::Channel> channel =
451 CreateCliChannel(server_address, cred);
452 grpc::ProtoReflectionDescriptorDatabase desc_db(channel);
453 grpc::protobuf::DescriptorPool desc_pool(&desc_db);
454
455 std::string output;
456 const grpc::protobuf::Descriptor* descriptor =
457 desc_pool.FindMessageTypeByName(argv[1]);
458 if (descriptor != nullptr) {
459 output = descriptor->DebugString();
460 } else {
461 fprintf(stderr, "Type %s not found.\n", argv[1]);
462 return false;
463 }
464 return callback(output);
465 }
466
CallMethod(int argc,const char ** argv,const CliCredentials & cred,GrpcToolOutputCallback callback)467 bool GrpcTool::CallMethod(int argc, const char** argv,
468 const CliCredentials& cred,
469 GrpcToolOutputCallback callback) {
470 CommandUsage(
471 "Call method\n"
472 " grpc_cli call <address> <service>[.<method>] <request>\n"
473 " <address> ; host:port\n"
474 " <service> ; Exported service name\n"
475 " <method> ; Method name\n"
476 " <request> ; Text protobuffer (overrides infile)\n"
477 " --protofiles ; Comma separated proto files used as a"
478 " fallback when parsing request/response\n"
479 " --proto_path ; The search path of proto files, valid"
480 " only when --protofiles is given\n"
481 " --noremotedb ; Don't attempt to use reflection service"
482 " at all\n"
483 " --metadata ; The metadata to be sent to the server\n"
484 " --infile ; Input filename (defaults to stdin)\n"
485 " --outfile ; Output filename (defaults to stdout)\n"
486 " --binary_input ; Input in binary format\n"
487 " --binary_output ; Output in binary format\n"
488 " --json_input ; Input in json format\n"
489 " --json_output ; Output in json format\n" +
490 cred.GetCredentialUsage());
491
492 std::stringstream output_ss;
493 std::string request_text;
494 std::string server_address(argv[0]);
495 std::string method_name(argv[1]);
496 std::string formatted_method_name;
497 std::unique_ptr<ProtoFileParser> parser;
498 std::string serialized_request_proto;
499 bool print_mode = false;
500
501 std::shared_ptr<grpc::Channel> channel =
502 CreateCliChannel(server_address, cred);
503
504 if (!FLAGS_binary_input || !FLAGS_binary_output) {
505 parser.reset(
506 new grpc::testing::ProtoFileParser(FLAGS_remotedb ? channel : nullptr,
507 FLAGS_proto_path, FLAGS_protofiles));
508 if (parser->HasError()) {
509 fprintf(
510 stderr,
511 "Failed to find remote reflection service and local proto files.\n");
512 return false;
513 }
514 }
515
516 if (FLAGS_binary_input) {
517 formatted_method_name = method_name;
518 } else {
519 formatted_method_name = parser->GetFormattedMethodName(method_name);
520 if (parser->HasError()) {
521 fprintf(stderr, "Failed to find method %s in proto files.\n",
522 method_name.c_str());
523 }
524 }
525
526 if (argc == 3) {
527 request_text = argv[2];
528 }
529
530 if (parser->IsStreaming(method_name, true /* is_request */)) {
531 std::istream* input_stream;
532 std::ifstream input_file;
533
534 if (FLAGS_batch) {
535 fprintf(stderr, "Batch mode for streaming RPC is not supported.\n");
536 return false;
537 }
538
539 std::multimap<std::string, std::string> client_metadata;
540 ParseMetadataFlag(&client_metadata);
541 PrintMetadata(client_metadata, "Sending client initial metadata:");
542
543 CliCall call(channel, formatted_method_name, client_metadata);
544
545 if (FLAGS_infile.empty()) {
546 if (isatty(fileno(stdin))) {
547 print_mode = true;
548 fprintf(stderr, "reading streaming request message from stdin...\n");
549 }
550 input_stream = &std::cin;
551 } else {
552 input_file.open(FLAGS_infile, std::ios::in | std::ios::binary);
553 input_stream = &input_file;
554 }
555
556 gpr_mu parser_mu;
557 gpr_mu_init(&parser_mu);
558 std::thread read_thread(ReadResponse, &call, method_name, callback,
559 parser.get(), &parser_mu, print_mode);
560
561 std::stringstream request_ss;
562 std::string line;
563 while (!request_text.empty() ||
564 (!input_stream->eof() && getline(*input_stream, line))) {
565 if (!request_text.empty()) {
566 if (FLAGS_binary_input) {
567 serialized_request_proto = request_text;
568 request_text.clear();
569 } else {
570 gpr_mu_lock(&parser_mu);
571 serialized_request_proto = parser->GetSerializedProtoFromMethod(
572 method_name, request_text, true /* is_request */,
573 FLAGS_json_input);
574 request_text.clear();
575 if (parser->HasError()) {
576 if (print_mode) {
577 fprintf(stderr, "Failed to parse request.\n");
578 }
579 gpr_mu_unlock(&parser_mu);
580 continue;
581 }
582 gpr_mu_unlock(&parser_mu);
583 }
584
585 call.WriteAndWait(serialized_request_proto);
586 if (print_mode) {
587 fprintf(stderr, "Request sent.\n");
588 }
589 } else {
590 if (line.length() == 0) {
591 request_text = request_ss.str();
592 request_ss.str(std::string());
593 request_ss.clear();
594 } else {
595 request_ss << line << ' ';
596 }
597 }
598 }
599 if (input_file.is_open()) {
600 input_file.close();
601 }
602
603 call.WritesDoneAndWait();
604 read_thread.join();
605 gpr_mu_destroy(&parser_mu);
606
607 std::multimap<grpc::string_ref, grpc::string_ref> server_trailing_metadata;
608 Status status = call.Finish(&server_trailing_metadata);
609 PrintMetadata(server_trailing_metadata,
610 "Received trailing metadata from server:");
611
612 if (status.ok()) {
613 fprintf(stderr, "Stream RPC succeeded with OK status\n");
614 return true;
615 } else {
616 fprintf(stderr, "Rpc failed with status code %d, error message: %s\n",
617 status.error_code(), status.error_message().c_str());
618 return false;
619 }
620
621 } else { // parser->IsStreaming(method_name, true /* is_request */)
622 if (FLAGS_batch) {
623 if (parser->IsStreaming(method_name, false /* is_request */)) {
624 fprintf(stderr, "Batch mode for streaming RPC is not supported.\n");
625 return false;
626 }
627
628 std::istream* input_stream;
629 std::ifstream input_file;
630
631 if (FLAGS_infile.empty()) {
632 if (isatty(fileno(stdin))) {
633 print_mode = true;
634 fprintf(stderr, "reading request messages from stdin...\n");
635 }
636 input_stream = &std::cin;
637 } else {
638 input_file.open(FLAGS_infile, std::ios::in | std::ios::binary);
639 input_stream = &input_file;
640 }
641
642 std::multimap<std::string, std::string> client_metadata;
643 ParseMetadataFlag(&client_metadata);
644 if (print_mode) {
645 PrintMetadata(client_metadata, "Sending client initial metadata:");
646 }
647
648 std::stringstream request_ss;
649 std::string line;
650 while (!request_text.empty() ||
651 (!input_stream->eof() && getline(*input_stream, line))) {
652 if (!request_text.empty()) {
653 if (FLAGS_binary_input) {
654 serialized_request_proto = request_text;
655 request_text.clear();
656 } else {
657 serialized_request_proto = parser->GetSerializedProtoFromMethod(
658 method_name, request_text, true /* is_request */,
659 FLAGS_json_input);
660 request_text.clear();
661 if (parser->HasError()) {
662 if (print_mode) {
663 fprintf(stderr, "Failed to parse request.\n");
664 }
665 continue;
666 }
667 }
668
669 std::string serialized_response_proto;
670 std::multimap<grpc::string_ref, grpc::string_ref>
671 server_initial_metadata, server_trailing_metadata;
672 CliCall call(channel, formatted_method_name, client_metadata);
673 call.Write(serialized_request_proto);
674 call.WritesDone();
675 if (!call.Read(&serialized_response_proto,
676 &server_initial_metadata)) {
677 fprintf(stderr, "Failed to read response.\n");
678 }
679 Status status = call.Finish(&server_trailing_metadata);
680
681 if (status.ok()) {
682 if (print_mode) {
683 fprintf(stderr, "Rpc succeeded with OK status.\n");
684 PrintMetadata(server_initial_metadata,
685 "Received initial metadata from server:");
686 PrintMetadata(server_trailing_metadata,
687 "Received trailing metadata from server:");
688 }
689
690 if (FLAGS_binary_output) {
691 if (!callback(serialized_response_proto)) {
692 break;
693 }
694 } else {
695 std::string response_text = parser->GetFormattedStringFromMethod(
696 method_name, serialized_response_proto,
697 false /* is_request */, FLAGS_json_output);
698
699 if (parser->HasError() && print_mode) {
700 fprintf(stderr, "Failed to parse response.\n");
701 } else {
702 if (!callback(response_text)) {
703 break;
704 }
705 }
706 }
707 } else {
708 if (print_mode) {
709 fprintf(stderr,
710 "Rpc failed with status code %d, error message: %s\n",
711 status.error_code(), status.error_message().c_str());
712 }
713 }
714 } else {
715 if (line.length() == 0) {
716 request_text = request_ss.str();
717 request_ss.str(std::string());
718 request_ss.clear();
719 } else {
720 request_ss << line << ' ';
721 }
722 }
723 }
724
725 if (input_file.is_open()) {
726 input_file.close();
727 }
728
729 return true;
730 }
731
732 if (argc == 3) {
733 if (!FLAGS_infile.empty()) {
734 fprintf(stderr, "warning: request given in argv, ignoring --infile\n");
735 }
736 } else {
737 std::stringstream input_stream;
738 if (FLAGS_infile.empty()) {
739 if (isatty(fileno(stdin))) {
740 fprintf(stderr, "reading request message from stdin...\n");
741 }
742 input_stream << std::cin.rdbuf();
743 } else {
744 std::ifstream input_file(FLAGS_infile, std::ios::in | std::ios::binary);
745 input_stream << input_file.rdbuf();
746 input_file.close();
747 }
748 request_text = input_stream.str();
749 }
750
751 if (FLAGS_binary_input) {
752 serialized_request_proto = request_text;
753 } else {
754 serialized_request_proto = parser->GetSerializedProtoFromMethod(
755 method_name, request_text, true /* is_request */, FLAGS_json_input);
756 if (parser->HasError()) {
757 fprintf(stderr, "Failed to parse request.\n");
758 return false;
759 }
760 }
761 fprintf(stderr, "connecting to %s\n", server_address.c_str());
762
763 std::string serialized_response_proto;
764 std::multimap<std::string, std::string> client_metadata;
765 std::multimap<grpc::string_ref, grpc::string_ref> server_initial_metadata,
766 server_trailing_metadata;
767 ParseMetadataFlag(&client_metadata);
768 PrintMetadata(client_metadata, "Sending client initial metadata:");
769
770 CliCall call(channel, formatted_method_name, client_metadata);
771 call.Write(serialized_request_proto);
772 call.WritesDone();
773
774 for (bool receive_initial_metadata = true; call.Read(
775 &serialized_response_proto,
776 receive_initial_metadata ? &server_initial_metadata : nullptr);
777 receive_initial_metadata = false) {
778 if (!FLAGS_binary_output) {
779 serialized_response_proto = parser->GetFormattedStringFromMethod(
780 method_name, serialized_response_proto, false /* is_request */,
781 FLAGS_json_output);
782 if (parser->HasError()) {
783 fprintf(stderr, "Failed to parse response.\n");
784 return false;
785 }
786 }
787
788 if (receive_initial_metadata) {
789 PrintMetadata(server_initial_metadata,
790 "Received initial metadata from server:");
791 }
792 if (!callback(serialized_response_proto)) {
793 return false;
794 }
795 }
796 Status status = call.Finish(&server_trailing_metadata);
797 PrintMetadata(server_trailing_metadata,
798 "Received trailing metadata from server:");
799 if (status.ok()) {
800 fprintf(stderr, "Rpc succeeded with OK status\n");
801 return true;
802 } else {
803 fprintf(stderr, "Rpc failed with status code %d, error message: %s\n",
804 status.error_code(), status.error_message().c_str());
805 return false;
806 }
807 }
808 GPR_UNREACHABLE_CODE(return false);
809 }
810
ParseMessage(int argc,const char ** argv,const CliCredentials & cred,GrpcToolOutputCallback callback)811 bool GrpcTool::ParseMessage(int argc, const char** argv,
812 const CliCredentials& cred,
813 GrpcToolOutputCallback callback) {
814 CommandUsage(
815 "Parse message\n"
816 " grpc_cli parse <address> <type> [<message>]\n"
817 " <address> ; host:port\n"
818 " <type> ; Protocol buffer type name\n"
819 " <message> ; Text protobuffer (overrides --infile)\n"
820 " --protofiles ; Comma separated proto files used as a"
821 " fallback when parsing request/response\n"
822 " --proto_path ; The search path of proto files, valid"
823 " only when --protofiles is given\n"
824 " --noremotedb ; Don't attempt to use reflection service"
825 " at all\n"
826 " --infile ; Input filename (defaults to stdin)\n"
827 " --outfile ; Output filename (defaults to stdout)\n"
828 " --binary_input ; Input in binary format\n"
829 " --binary_output ; Output in binary format\n"
830 " --json_input ; Input in json format\n"
831 " --json_output ; Output in json format\n" +
832 cred.GetCredentialUsage());
833
834 std::stringstream output_ss;
835 std::string message_text;
836 std::string server_address(argv[0]);
837 std::string type_name(argv[1]);
838 std::unique_ptr<grpc::testing::ProtoFileParser> parser;
839 std::string serialized_request_proto;
840
841 if (argc == 3) {
842 message_text = argv[2];
843 if (!FLAGS_infile.empty()) {
844 fprintf(stderr, "warning: message given in argv, ignoring --infile.\n");
845 }
846 } else {
847 std::stringstream input_stream;
848 if (FLAGS_infile.empty()) {
849 if (isatty(fileno(stdin))) {
850 fprintf(stderr, "reading request message from stdin...\n");
851 }
852 input_stream << std::cin.rdbuf();
853 } else {
854 std::ifstream input_file(FLAGS_infile, std::ios::in | std::ios::binary);
855 input_stream << input_file.rdbuf();
856 input_file.close();
857 }
858 message_text = input_stream.str();
859 }
860
861 if (!FLAGS_binary_input || !FLAGS_binary_output) {
862 std::shared_ptr<grpc::Channel> channel =
863 CreateCliChannel(server_address, cred);
864 parser.reset(
865 new grpc::testing::ProtoFileParser(FLAGS_remotedb ? channel : nullptr,
866 FLAGS_proto_path, FLAGS_protofiles));
867 if (parser->HasError()) {
868 fprintf(
869 stderr,
870 "Failed to find remote reflection service and local proto files.\n");
871 return false;
872 }
873 }
874
875 if (FLAGS_binary_input) {
876 serialized_request_proto = message_text;
877 } else {
878 serialized_request_proto = parser->GetSerializedProtoFromMessageType(
879 type_name, message_text, FLAGS_json_input);
880 if (parser->HasError()) {
881 fprintf(stderr, "Failed to serialize the message.\n");
882 return false;
883 }
884 }
885
886 if (FLAGS_binary_output) {
887 output_ss << serialized_request_proto;
888 } else {
889 std::string output_text;
890 output_text = parser->GetFormattedStringFromMessageType(
891 type_name, serialized_request_proto, FLAGS_json_output);
892 if (parser->HasError()) {
893 fprintf(stderr, "Failed to deserialize the message.\n");
894 return false;
895 }
896
897 output_ss << output_text << std::endl;
898 }
899
900 return callback(output_ss.str());
901 }
902
ToText(int argc,const char ** argv,const CliCredentials & cred,GrpcToolOutputCallback callback)903 bool GrpcTool::ToText(int argc, const char** argv, const CliCredentials& cred,
904 GrpcToolOutputCallback callback) {
905 CommandUsage(
906 "Convert binary message to text\n"
907 " grpc_cli totext <protofiles> <type>\n"
908 " <protofiles> ; Comma separated list of proto files\n"
909 " <type> ; Protocol buffer type name\n"
910 " --proto_path ; The search path of proto files\n"
911 " --infile ; Input filename (defaults to stdin)\n"
912 " --outfile ; Output filename (defaults to stdout)\n");
913
914 FLAGS_protofiles = argv[0];
915 FLAGS_remotedb = false;
916 FLAGS_binary_input = true;
917 FLAGS_binary_output = false;
918 return ParseMessage(argc, argv, cred, callback);
919 }
920
ToJson(int argc,const char ** argv,const CliCredentials & cred,GrpcToolOutputCallback callback)921 bool GrpcTool::ToJson(int argc, const char** argv, const CliCredentials& cred,
922 GrpcToolOutputCallback callback) {
923 CommandUsage(
924 "Convert binary message to json\n"
925 " grpc_cli tojson <protofiles> <type>\n"
926 " <protofiles> ; Comma separated list of proto files\n"
927 " <type> ; Protocol buffer type name\n"
928 " --proto_path ; The search path of proto files\n"
929 " --infile ; Input filename (defaults to stdin)\n"
930 " --outfile ; Output filename (defaults to stdout)\n");
931
932 FLAGS_protofiles = argv[0];
933 FLAGS_remotedb = false;
934 FLAGS_binary_input = true;
935 FLAGS_binary_output = false;
936 FLAGS_json_output = true;
937 return ParseMessage(argc, argv, cred, callback);
938 }
939
ToBinary(int argc,const char ** argv,const CliCredentials & cred,GrpcToolOutputCallback callback)940 bool GrpcTool::ToBinary(int argc, const char** argv, const CliCredentials& cred,
941 GrpcToolOutputCallback callback) {
942 CommandUsage(
943 "Convert text message to binary\n"
944 " grpc_cli tobinary <protofiles> <type> [<message>]\n"
945 " <protofiles> ; Comma separated list of proto files\n"
946 " <type> ; Protocol buffer type name\n"
947 " --proto_path ; The search path of proto files\n"
948 " --infile ; Input filename (defaults to stdin)\n"
949 " --outfile ; Output filename (defaults to stdout)\n");
950
951 FLAGS_protofiles = argv[0];
952 FLAGS_remotedb = false;
953 FLAGS_binary_input = false;
954 FLAGS_binary_output = true;
955 return ParseMessage(argc, argv, cred, callback);
956 }
957
958 } // namespace testing
959 } // namespace grpc
960