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