From ac9710d5d6e1c3dba1c681168cca39e358ab73ef Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 11 Jun 2020 17:30:42 -0700 Subject: [PATCH] (ws) add bare bone redis-cli like sub-command, with command line editing powered by libnoise --- docs/CHANGELOG.md | 4 ++ ixredis/ixredis/IXRedisClient.cpp | 100 ++++++++++++++++++++++++++++++ ixredis/ixredis/IXRedisClient.h | 20 +++++- ixwebsocket/IXWebSocketVersion.h | 2 +- makefile | 3 + ws/CMakeLists.txt | 1 + ws/ws.cpp | 10 +++ ws/ws.h | 4 ++ ws/ws_redis_cli.cpp | 50 ++++++++++++--- 9 files changed, 182 insertions(+), 12 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3f08a62f..f5f86d79 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog All changes to this project will be documented in this file. +## [9.7.2] - 2020-06-11 + +(ws) add bare bone redis-cli like sub-command, with command line editing powered by libnoise + ## [9.7.1] - 2020-06-11 (redis cobra bots) ws cobra metrics to redis / hostname invalid parsing diff --git a/ixredis/ixredis/IXRedisClient.cpp b/ixredis/ixredis/IXRedisClient.cpp index 7acf6610..44d9c489 100644 --- a/ixredis/ixredis/IXRedisClient.cpp +++ b/ixredis/ixredis/IXRedisClient.cpp @@ -354,4 +354,104 @@ namespace ix return success; } + + std::pair RedisClient::send( + const std::vector& args, + std::string& errMsg) + { + std::stringstream ss; + ss << "*"; + ss << std::to_string(args.size()); + ss << "\r\n"; + + for (auto&& arg : args) + { + ss << writeString(arg); + } + + bool sent = _socket->writeBytes(ss.str(), nullptr); + if (!sent) + { + errMsg = "Cannot write bytes to socket"; + return std::make_pair(RespType::Error, ""); + } + + return readResponse(errMsg); + } + + std::pair RedisClient::readResponse(std::string& errMsg) + { + // Read result + auto pollResult = _socket->isReadyToRead(-1); + if (pollResult == PollResultType::Error) + { + errMsg = "Error while polling for result"; + return std::make_pair(RespType::Error, ""); + } + + // First line is the string length + auto lineResult = _socket->readLine(nullptr); + auto lineValid = lineResult.first; + auto line = lineResult.second; + + if (!lineValid) + { + errMsg = "Error while polling for result"; + return std::make_pair(RespType::Error, ""); + } + + std::string response; + + if (line[0] == '+') // Simple string + { + std::stringstream ss; + response = line.substr(1, line.size() - 3); + return std::make_pair(RespType::String, response); + } + else if (line[0] == '-') // Errors + { + std::stringstream ss; + response = line.substr(1, line.size() - 3); + return std::make_pair(RespType::Error, response); + } + else if (line[0] == ':') // Integers + { + std::stringstream ss; + response = line.substr(1, line.size() - 3); + return std::make_pair(RespType::Integer, response); + } + else if (line[0] == '$') // Bulk strings + { + int stringSize; + { + std::stringstream ss; + ss << line.substr(1, line.size() - 1); + ss >> stringSize; + } + + // Read the result, which is the stream id computed by the redis server + lineResult = _socket->readLine(nullptr); + lineValid = lineResult.first; + line = lineResult.second; + + std::string str = line.substr(0, stringSize); + return std::make_pair(RespType::String, str); + } + else + { + errMsg = "Unhandled response type"; + return std::make_pair(RespType::Unknown, std::string()); + } + } + + std::string RedisClient::getRespTypeDescription(RespType respType) + { + switch (respType) + { + case RespType::Integer: return "integer"; + case RespType::Error: return "error"; + case RespType::String: return "string"; + default: return "unknown"; + } + } } // namespace ix diff --git a/ixredis/ixredis/IXRedisClient.h b/ixredis/ixredis/IXRedisClient.h index 00b7c59b..5800869d 100644 --- a/ixredis/ixredis/IXRedisClient.h +++ b/ixredis/ixredis/IXRedisClient.h @@ -14,6 +14,14 @@ namespace ix { + enum class RespType : int + { + String = 0, + Error = 1, + Integer = 2, + Unknown = 3 + }; + class RedisClient { public: @@ -42,14 +50,20 @@ namespace ix const std::string& message, int maxLen, std::string& errMsg); - std::string prepareXaddCommand(const std::string& stream, const std::string& message, int maxLen); - std::string readXaddReply(std::string& errMsg); + bool sendCommand( + const std::string& commands, int commandsCount, std::string& errMsg); - bool sendCommand(const std::string& commands, int commandsCount, std::string& errMsg); + // Arbitrary commands + std::pair send( + const std::vector& args, + std::string& errMsg); + std::pair readResponse(std::string& errMsg); + + std::string getRespTypeDescription(RespType respType); void stop(); diff --git a/ixwebsocket/IXWebSocketVersion.h b/ixwebsocket/IXWebSocketVersion.h index 0afde383..e24e49ed 100644 --- a/ixwebsocket/IXWebSocketVersion.h +++ b/ixwebsocket/IXWebSocketVersion.h @@ -6,4 +6,4 @@ #pragma once -#define IX_WEBSOCKET_VERSION "9.7.1" +#define IX_WEBSOCKET_VERSION "9.7.2" diff --git a/makefile b/makefile index 93db0e83..3ab52aac 100644 --- a/makefile +++ b/makefile @@ -233,6 +233,9 @@ install_cmake_for_linux: doc: mkdocs gh-deploy +change: + vi ixwebsocket/IXWebSocketVersion.h docs/CHANGELOG.md + .PHONY: test .PHONY: build .PHONY: ws diff --git a/ws/CMakeLists.txt b/ws/CMakeLists.txt index 552a1ea3..9d364e3a 100644 --- a/ws/CMakeLists.txt +++ b/ws/CMakeLists.txt @@ -45,6 +45,7 @@ add_executable(ws ws_transfer.cpp ws_send.cpp ws_receive.cpp + ws_redis_cli.cpp ws_redis_publish.cpp ws_redis_subscribe.cpp ws_redis_server.cpp diff --git a/ws/ws.cpp b/ws/ws.cpp index f32259dd..d35a085b 100644 --- a/ws/ws.cpp +++ b/ws/ws.cpp @@ -281,6 +281,12 @@ int main(int argc, char** argv) httpClientApp->add_option("--transfer-timeout", transferTimeout, "Transfer timeout"); addTLSOptions(httpClientApp); + CLI::App* redisCliApp = app.add_subcommand("redis_cli", "Redis cli"); + redisCliApp->fallthrough(); + redisCliApp->add_option("--port", redisPort, "Port"); + redisCliApp->add_option("--host", hostname, "Hostname"); + redisCliApp->add_option("--password", password, "Password"); + CLI::App* redisPublishApp = app.add_subcommand("redis_publish", "Redis publisher"); redisPublishApp->fallthrough(); redisPublishApp->add_option("--port", redisPort, "Port"); @@ -531,6 +537,10 @@ int main(int argc, char** argv) compress, tlsOptions); } + else if (app.got_subcommand("redis_cli")) + { + ret = ix::ws_redis_cli_main(hostname, redisPort, password); + } else if (app.got_subcommand("redis_publish")) { ret = ix::ws_redis_publish_main(hostname, redisPort, password, channel, message, count); diff --git a/ws/ws.h b/ws/ws.h index 1145391e..73c04973 100644 --- a/ws/ws.h +++ b/ws/ws.h @@ -64,6 +64,10 @@ namespace ix bool disablePerMessageDeflate, const ix::SocketTLSOptions& tlsOptions); + int ws_redis_cli_main(const std::string& hostname, + int port, + const std::string& password); + int ws_redis_publish_main(const std::string& hostname, int port, const std::string& password, diff --git a/ws/ws_redis_cli.cpp b/ws/ws_redis_cli.cpp index 4333e168..8cfc3364 100644 --- a/ws/ws_redis_cli.cpp +++ b/ws/ws_redis_cli.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "linenoise.hpp" namespace ix { @@ -36,17 +37,50 @@ namespace ix while (true) { - std::string text; - std::cout << "> " << std::flush; - std::getline(std::cin, text); + // Read line + std::string line; + std::string prompt; + prompt += hostname; + prompt += ":"; + prompt += std::to_string(port); + prompt += "> "; + auto quit = linenoise::Readline(prompt.c_str(), line); -#if 0 - if (!redisClient.send(args, errMsg)) + if (quit) { - spdlog::error("Error", channel, errMsg); - return 1; + break; } -#endif + + std::stringstream ss(line); + std::vector args; + std::string arg; + + while (ss.good()) + { + ss >> arg; + args.push_back(arg); + } + + std::string errMsg; + auto response = redisClient.send(args, errMsg); + if (!errMsg.empty()) + { + spdlog::error("(error) {}", errMsg); + } + else + { + if (response.first != RespType::String) + { + std::cout << "(" + << redisClient.getRespTypeDescription(response.first) + << ")" + << " "; + } + + std::cout << response.second << std::endl; + } + + linenoise::AddHistory(line.c_str()); } return 0;