• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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