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