| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618 | /* * * Copyright 2016, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * *     * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. *     * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. *     * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */#include "test/cpp/util/grpc_tool.h"#include <unistd.h>#include <fstream>#include <iostream>#include <memory>#include <sstream>#include <string>#include <gflags/gflags.h>#include <grpc++/channel.h>#include <grpc++/create_channel.h>#include <grpc++/grpc++.h>#include <grpc++/security/credentials.h>#include <grpc++/support/string_ref.h>#include <grpc/grpc.h>#include "test/cpp/util/cli_call.h"#include "test/cpp/util/proto_file_parser.h"#include "test/cpp/util/proto_reflection_descriptor_database.h"#include "test/cpp/util/service_describer.h"namespace grpc {namespace testing {DEFINE_bool(l, false, "Use a long listing format");DEFINE_bool(remotedb, true, "Use server types to parse and format messages");DEFINE_string(metadata, "",              "Metadata to send to server, in the form of key1:val1:key2:val2");DEFINE_string(proto_path, ".", "Path to look for the proto file.");DEFINE_string(protofiles, "", "Name of the proto file.");DEFINE_bool(binary_input, false, "Input in binary format");DEFINE_bool(binary_output, false, "Output in binary format");DEFINE_string(infile, "", "Input file (default is stdin)");namespace {class GrpcTool { public:  explicit GrpcTool();  virtual ~GrpcTool() {}  bool Help(int argc, const char** argv, const CliCredentials& cred,            GrpcToolOutputCallback callback);  bool CallMethod(int argc, const char** argv, const CliCredentials& cred,                  GrpcToolOutputCallback callback);  bool ListServices(int argc, const char** argv, const CliCredentials& cred,                    GrpcToolOutputCallback callback);  bool PrintType(int argc, const char** argv, const CliCredentials& cred,                 GrpcToolOutputCallback callback);  // TODO(zyc): implement the following methods  // bool ListServices(int argc, const char** argv, GrpcToolOutputCallback  // callback);  // bool PrintTypeId(int argc, const char** argv, GrpcToolOutputCallback  // callback);  bool ParseMessage(int argc, const char** argv, const CliCredentials& cred,                    GrpcToolOutputCallback callback);  bool ToText(int argc, const char** argv, const CliCredentials& cred,              GrpcToolOutputCallback callback);  bool ToBinary(int argc, const char** argv, const CliCredentials& cred,                GrpcToolOutputCallback callback);  void SetPrintCommandMode(int exit_status) {    print_command_usage_ = true;    usage_exit_status_ = exit_status;  } private:  void CommandUsage(const grpc::string& usage) const;  bool print_command_usage_;  int usage_exit_status_;  const grpc::string cred_usage_;};template <typename T>std::function<bool(GrpcTool*, int, const char**, const CliCredentials&,                   GrpcToolOutputCallback)>BindWith5Args(T&& func) {  return std::bind(std::forward<T>(func), std::placeholders::_1,                   std::placeholders::_2, std::placeholders::_3,                   std::placeholders::_4, std::placeholders::_5);}template <typename T>size_t ArraySize(T& a) {  return ((sizeof(a) / sizeof(*(a))) /          static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))));}void ParseMetadataFlag(    std::multimap<grpc::string, grpc::string>* client_metadata) {  if (FLAGS_metadata.empty()) {    return;  }  std::vector<grpc::string> fields;  const char* delim = ":";  size_t cur, next = -1;  do {    cur = next + 1;    next = FLAGS_metadata.find_first_of(delim, cur);    fields.push_back(FLAGS_metadata.substr(cur, next - cur));  } while (next != grpc::string::npos);  if (fields.size() % 2) {    fprintf(stderr, "Failed to parse metadata flag.\n");    exit(1);  }  for (size_t i = 0; i < fields.size(); i += 2) {    client_metadata->insert(        std::pair<grpc::string, grpc::string>(fields[i], fields[i + 1]));  }}template <typename T>void PrintMetadata(const T& m, const grpc::string& message) {  if (m.empty()) {    return;  }  fprintf(stderr, "%s\n", message.c_str());  grpc::string pair;  for (typename T::const_iterator iter = m.begin(); iter != m.end(); ++iter) {    pair.clear();    pair.append(iter->first.data(), iter->first.size());    pair.append(" : ");    pair.append(iter->second.data(), iter->second.size());    fprintf(stderr, "%s\n", pair.c_str());  }}struct Command {  const char* command;  std::function<bool(GrpcTool*, int, const char**, const CliCredentials&,                     GrpcToolOutputCallback)>      function;  int min_args;  int max_args;};const Command ops[] = {    {"help", BindWith5Args(&GrpcTool::Help), 0, INT_MAX},    {"ls", BindWith5Args(&GrpcTool::ListServices), 1, 3},    {"list", BindWith5Args(&GrpcTool::ListServices), 1, 3},    {"call", BindWith5Args(&GrpcTool::CallMethod), 2, 3},    {"type", BindWith5Args(&GrpcTool::PrintType), 2, 2},    {"parse", BindWith5Args(&GrpcTool::ParseMessage), 2, 3},    {"totext", BindWith5Args(&GrpcTool::ToText), 2, 3},    {"tobinary", BindWith5Args(&GrpcTool::ToBinary), 2, 3},};void Usage(const grpc::string& msg) {  fprintf(      stderr,      "%s\n"      "  grpc_cli ls ...         ; List services\n"      "  grpc_cli call ...       ; Call method\n"      "  grpc_cli type ...       ; Print type\n"      "  grpc_cli parse ...      ; Parse message\n"      "  grpc_cli totext ...     ; Convert binary message to text\n"      "  grpc_cli tobinary ...   ; Convert text message to binary\n"      "  grpc_cli help ...       ; Print this message, or per-command usage\n"      "\n",      msg.c_str());  exit(1);}const Command* FindCommand(const grpc::string& name) {  for (int i = 0; i < (int)ArraySize(ops); i++) {    if (name == ops[i].command) {      return &ops[i];    }  }  return NULL;}}  // namespaceint GrpcToolMainLib(int argc, const char** argv, const CliCredentials& cred,                    GrpcToolOutputCallback callback) {  if (argc < 2) {    Usage("No command specified");  }  grpc::string command = argv[1];  argc -= 2;  argv += 2;  const Command* cmd = FindCommand(command);  if (cmd != NULL) {    GrpcTool grpc_tool;    if (argc < cmd->min_args || argc > cmd->max_args) {      // Force the command to print its usage message      fprintf(stderr, "\nWrong number of arguments for %s\n", command.c_str());      grpc_tool.SetPrintCommandMode(1);      return cmd->function(&grpc_tool, -1, NULL, cred, callback);    }    const bool ok = cmd->function(&grpc_tool, argc, argv, cred, callback);    return ok ? 0 : 1;  } else {    Usage("Invalid command '" + grpc::string(command.c_str()) + "'");  }  return 1;}GrpcTool::GrpcTool() : print_command_usage_(false), usage_exit_status_(0) {}void GrpcTool::CommandUsage(const grpc::string& usage) const {  if (print_command_usage_) {    fprintf(stderr, "\n%s%s\n", usage.c_str(),            (usage.empty() || usage[usage.size() - 1] != '\n') ? "\n" : "");    exit(usage_exit_status_);  }}bool GrpcTool::Help(int argc, const char** argv, const CliCredentials& cred,                    GrpcToolOutputCallback callback) {  CommandUsage(      "Print help\n"      "  grpc_cli help [subcommand]\n");  if (argc == 0) {    Usage("");  } else {    const Command* cmd = FindCommand(argv[0]);    if (cmd == NULL) {      Usage("Unknown command '" + grpc::string(argv[0]) + "'");    }    SetPrintCommandMode(0);    cmd->function(this, -1, NULL, cred, callback);  }  return true;}bool GrpcTool::ListServices(int argc, const char** argv,                            const CliCredentials& cred,                            GrpcToolOutputCallback callback) {  CommandUsage(      "List services\n"      "  grpc_cli ls <address> [<service>[/<method>]]\n"      "    <address>                ; host:port\n"      "    <service>                ; Exported service name\n"      "    <method>                 ; Method name\n"      "    --l                      ; Use a long listing format\n"      "    --outfile                ; Output filename (defaults to stdout)\n" +      cred.GetCredentialUsage());  grpc::string server_address(argv[0]);  std::shared_ptr<grpc::Channel> channel =      grpc::CreateChannel(server_address, cred.GetCredentials());  grpc::ProtoReflectionDescriptorDatabase desc_db(channel);  grpc::protobuf::DescriptorPool desc_pool(&desc_db);  std::vector<grpc::string> service_list;  if (!desc_db.GetServices(&service_list)) {    return false;  }  // If no service is specified, dump the list of services.  grpc::string output;  if (argc < 2) {    // List all services, if --l is passed, then include full description,    // otherwise include a summarized list only.    if (FLAGS_l) {      output = DescribeServiceList(service_list, desc_pool);    } else {      for (auto it = service_list.begin(); it != service_list.end(); it++) {        auto const& service = *it;        output.append(service);        output.append("\n");      }    }  } else {    grpc::string service_name;    grpc::string method_name;    std::stringstream ss(argv[1]);    // Remove leading slashes.    while (ss.peek() == '/') {      ss.get();    }    // Parse service and method names. Support the following patterns:    //   Service    //   Service Method    //   Service.Method    //   Service/Method    if (argc == 3) {      std::getline(ss, service_name, '/');      method_name = argv[2];    } else {      if (std::getline(ss, service_name, '/')) {        std::getline(ss, method_name);      }    }    const grpc::protobuf::ServiceDescriptor* service =        desc_pool.FindServiceByName(service_name);    if (service != nullptr) {      if (method_name.empty()) {        output = FLAGS_l ? DescribeService(service) : SummarizeService(service);      } else {        method_name.insert(0, 1, '.');        method_name.insert(0, service_name);        const grpc::protobuf::MethodDescriptor* method =            desc_pool.FindMethodByName(method_name);        if (method != nullptr) {          output = FLAGS_l ? DescribeMethod(method) : SummarizeMethod(method);        } else {          fprintf(stderr, "Method %s not found in service %s.\n",                  method_name.c_str(), service_name.c_str());          return false;        }      }    } else {      if (!method_name.empty()) {        fprintf(stderr, "Service %s not found.\n", service_name.c_str());        return false;      } else {        const grpc::protobuf::MethodDescriptor* method =            desc_pool.FindMethodByName(service_name);        if (method != nullptr) {          output = FLAGS_l ? DescribeMethod(method) : SummarizeMethod(method);        } else {          fprintf(stderr, "Service or method %s not found.\n",                  service_name.c_str());          return false;        }      }    }  }  return callback(output);}bool GrpcTool::PrintType(int argc, const char** argv,                         const CliCredentials& cred,                         GrpcToolOutputCallback callback) {  CommandUsage(      "Print type\n"      "  grpc_cli type <address> <type>\n"      "    <address>                ; host:port\n"      "    <type>                   ; Protocol buffer type name\n" +      cred.GetCredentialUsage());  grpc::string server_address(argv[0]);  std::shared_ptr<grpc::Channel> channel =      grpc::CreateChannel(server_address, cred.GetCredentials());  grpc::ProtoReflectionDescriptorDatabase desc_db(channel);  grpc::protobuf::DescriptorPool desc_pool(&desc_db);  grpc::string output;  const grpc::protobuf::Descriptor* descriptor =      desc_pool.FindMessageTypeByName(argv[1]);  if (descriptor != nullptr) {    output = descriptor->DebugString();  } else {    fprintf(stderr, "Type %s not found.\n", argv[1]);    return false;  }  return callback(output);}bool GrpcTool::CallMethod(int argc, const char** argv,                          const CliCredentials& cred,                          GrpcToolOutputCallback callback) {  CommandUsage(      "Call method\n"      "  grpc_cli call <address> <service>[.<method>] <request>\n"      "    <address>                ; host:port\n"      "    <service>                ; Exported service name\n"      "    <method>                 ; Method name\n"      "    <request>                ; Text protobuffer (overrides infile)\n"      "    --protofiles             ; Comma separated proto files used as a"      " fallback when parsing request/response\n"      "    --proto_path             ; The search path of proto files, valid"      " only when --protofiles is given\n"      "    --metadata               ; The metadata to be sent to the server\n"      "    --infile                 ; Input filename (defaults to stdin)\n"      "    --outfile                ; Output filename (defaults to stdout)\n"      "    --binary_input           ; Input in binary format\n"      "    --binary_output          ; Output in binary format\n" +      cred.GetCredentialUsage());  std::stringstream output_ss;  grpc::string request_text;  grpc::string server_address(argv[0]);  grpc::string method_name(argv[1]);  grpc::string formatted_method_name;  std::unique_ptr<grpc::testing::ProtoFileParser> parser;  grpc::string serialized_request_proto;  if (argc == 3) {    request_text = argv[2];    if (!FLAGS_infile.empty()) {      fprintf(stderr, "warning: request given in argv, ignoring --infile\n");    }  } else {    std::stringstream input_stream;    if (FLAGS_infile.empty()) {      if (isatty(STDIN_FILENO)) {        fprintf(stderr, "reading request message from stdin...\n");      }      input_stream << std::cin.rdbuf();    } else {      std::ifstream input_file(FLAGS_infile, std::ios::in | std::ios::binary);      input_stream << input_file.rdbuf();      input_file.close();    }    request_text = input_stream.str();  }  std::shared_ptr<grpc::Channel> channel =      grpc::CreateChannel(server_address, cred.GetCredentials());  if (!FLAGS_binary_input || !FLAGS_binary_output) {    parser.reset(        new grpc::testing::ProtoFileParser(FLAGS_remotedb ? channel : nullptr,                                           FLAGS_proto_path, FLAGS_protofiles));    if (parser->HasError()) {      return false;    }  }  if (FLAGS_binary_input) {    serialized_request_proto = request_text;    formatted_method_name = method_name;  } else {    formatted_method_name = parser->GetFormattedMethodName(method_name);    serialized_request_proto = parser->GetSerializedProtoFromMethod(        method_name, request_text, true /* is_request */);    if (parser->HasError()) {      return false;    }  }  fprintf(stderr, "connecting to %s\n", server_address.c_str());  grpc::string serialized_response_proto;  std::multimap<grpc::string, grpc::string> client_metadata;  std::multimap<grpc::string_ref, grpc::string_ref> server_initial_metadata,      server_trailing_metadata;  ParseMetadataFlag(&client_metadata);  PrintMetadata(client_metadata, "Sending client initial metadata:");  grpc::Status status = grpc::testing::CliCall::Call(      channel, formatted_method_name, serialized_request_proto,      &serialized_response_proto, client_metadata, &server_initial_metadata,      &server_trailing_metadata);  PrintMetadata(server_initial_metadata,                "Received initial metadata from server:");  PrintMetadata(server_trailing_metadata,                "Received trailing metadata from server:");  if (status.ok()) {    fprintf(stderr, "Rpc succeeded with OK status\n");    if (FLAGS_binary_output) {      output_ss << serialized_response_proto;    } else {      grpc::string response_text = parser->GetTextFormatFromMethod(          method_name, serialized_response_proto, false /* is_request */);      if (parser->HasError()) {        return false;      }      output_ss << "Response: \n " << response_text << std::endl;    }  } else {    fprintf(stderr, "Rpc failed with status code %d, error message: %s\n",            status.error_code(), status.error_message().c_str());  }  return callback(output_ss.str());}bool GrpcTool::ParseMessage(int argc, const char** argv,                            const CliCredentials& cred,                            GrpcToolOutputCallback callback) {  CommandUsage(      "Parse message\n"      "  grpc_cli parse <address> <type> [<message>]\n"      "    <address>                ; host:port\n"      "    <type>                   ; Protocol buffer type name\n"      "    <message>                ; Text protobuffer (overrides --infile)\n"      "    --protofiles             ; Comma separated proto files used as a"      " fallback when parsing request/response\n"      "    --proto_path             ; The search path of proto files, valid"      " only when --protofiles is given\n"      "    --infile                 ; Input filename (defaults to stdin)\n"      "    --outfile                ; Output filename (defaults to stdout)\n"      "    --binary_input           ; Input in binary format\n"      "    --binary_output          ; Output in binary format\n" +      cred.GetCredentialUsage());  std::stringstream output_ss;  grpc::string message_text;  grpc::string server_address(argv[0]);  grpc::string type_name(argv[1]);  std::unique_ptr<grpc::testing::ProtoFileParser> parser;  grpc::string serialized_request_proto;  if (argc == 3) {    message_text = argv[2];    if (!FLAGS_infile.empty()) {      fprintf(stderr, "warning: message given in argv, ignoring --infile.\n");    }  } else {    std::stringstream input_stream;    if (FLAGS_infile.empty()) {      if (isatty(STDIN_FILENO)) {        fprintf(stderr, "reading request message from stdin...\n");      }      input_stream << std::cin.rdbuf();    } else {      std::ifstream input_file(FLAGS_infile, std::ios::in | std::ios::binary);      input_stream << input_file.rdbuf();      input_file.close();    }    message_text = input_stream.str();  }  if (!FLAGS_binary_input || !FLAGS_binary_output) {    std::shared_ptr<grpc::Channel> channel =        grpc::CreateChannel(server_address, cred.GetCredentials());    parser.reset(        new grpc::testing::ProtoFileParser(FLAGS_remotedb ? channel : nullptr,                                           FLAGS_proto_path, FLAGS_protofiles));    if (parser->HasError()) {      return false;    }  }  if (FLAGS_binary_input) {    serialized_request_proto = message_text;  } else {    serialized_request_proto =        parser->GetSerializedProtoFromMessageType(type_name, message_text);    if (parser->HasError()) {      return false;    }  }  if (FLAGS_binary_output) {    output_ss << serialized_request_proto;  } else {    grpc::string output_text = parser->GetTextFormatFromMessageType(        type_name, serialized_request_proto);    if (parser->HasError()) {      return false;    }    output_ss << output_text << std::endl;  }  return callback(output_ss.str());}bool GrpcTool::ToText(int argc, const char** argv, const CliCredentials& cred,                      GrpcToolOutputCallback callback) {  CommandUsage(      "Convert binary message to text\n"      "  grpc_cli totext <protofiles> <type>\n"      "    <protofiles>             ; Comma separated list of proto files\n"      "    <type>                   ; Protocol buffer type name\n"      "    --proto_path             ; The search path of proto files\n"      "    --infile                 ; Input filename (defaults to stdin)\n"      "    --outfile                ; Output filename (defaults to stdout)\n");  FLAGS_protofiles = argv[0];  FLAGS_remotedb = false;  FLAGS_binary_input = true;  FLAGS_binary_output = false;  return ParseMessage(argc, argv, cred, callback);}bool GrpcTool::ToBinary(int argc, const char** argv, const CliCredentials& cred,                        GrpcToolOutputCallback callback) {  CommandUsage(      "Convert text message to binary\n"      "  grpc_cli tobinary <protofiles> <type> [<message>]\n"      "    <protofiles>             ; Comma separated list of proto files\n"      "    <type>                   ; Protocol buffer type name\n"      "    --proto_path             ; The search path of proto files\n"      "    --infile                 ; Input filename (defaults to stdin)\n"      "    --outfile                ; Output filename (defaults to stdout)\n");  FLAGS_protofiles = argv[0];  FLAGS_remotedb = false;  FLAGS_binary_input = false;  FLAGS_binary_output = true;  return ParseMessage(argc, argv, cred, callback);}}  // namespace testing}  // namespace grpc
 |