• 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(
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