-
-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A very basic RESP2 CLI to simplify manual testing (#162)
* skeleton redis-cli * basic redis-cli in place * fix bugs * rename cli to resp instead of redis * add simple reconnect & exit logic * Update README * fix bug in main.c
- Loading branch information
1 parent
5edcecd
commit 1af761e
Showing
17 changed files
with
548 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
-Ideps/ccommon/include |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
debug_log_level: 2 | ||
debug_log_file: resp-cli.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
add_subdirectory(network) | ||
|
||
if(TARGET_RESPCLI) | ||
add_subdirectory(resp_cli) | ||
endif() | ||
|
||
if(TARGET_MEMCACHECLI) | ||
add_subdirectory(memcache_cli) | ||
endif() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
The idea of a CLI client comes from Redis, which builds `redis-cli` for easy | ||
testing and interactive play. This is particularly useful for Redis because | ||
the RESP protocol is more verbose than Memcached's ASCII protocol. | ||
|
||
The command line prompt and CLI command format are both modeled after Redis. | ||
We may also borrow code from `redis-cli.c` in the future. We want to | ||
acknowledge the fact that redis-cli is an ongoing inspiration. | ||
|
||
Since we only shallowly support the syntax portion of the Redis protocol, which | ||
is RESP, the binary and related files are named accordingly to reflect that. | ||
Actual command supported by the server may differ from one binary to another, | ||
and may change over time as well. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
add_library(client-network cli_network.c) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#include "cli_network.h" | ||
|
||
#include "core/data/server.h" | ||
|
||
#include <channel/cc_channel.h> | ||
#include <channel/cc_tcp.h> | ||
#include <stream/cc_sockio.h> | ||
|
||
#include <netdb.h> | ||
|
||
struct addrinfo hints; | ||
struct addrinfo *ai = NULL; | ||
|
||
struct network_config network_config = {LOCAL, NULL, SERVER_PORT}; | ||
|
||
channel_handler_st tcp_handler = { | ||
.accept = NULL, | ||
.reject = NULL, | ||
.open = (channel_open_fn)tcp_connect, | ||
.term = (channel_term_fn)tcp_close, | ||
.recv = (channel_recv_fn)tcp_recv, | ||
.send = (channel_send_fn)tcp_send, | ||
.rid = (channel_id_fn)tcp_read_id, | ||
.wid = (channel_id_fn)tcp_write_id | ||
}; | ||
|
||
bool | ||
cli_connect(struct buf_sock *client) | ||
{ | ||
hints.ai_flags = AI_NUMERICSERV; | ||
hints.ai_family = PF_UNSPEC; | ||
hints.ai_socktype = SOCK_STREAM; | ||
|
||
getaddrinfo(network_config.host, network_config.port, &hints, &ai); | ||
if (client->hdl->open(ai, client->ch)) { | ||
/* TODO: make socket blocking */ | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
|
||
void | ||
cli_disconnect(struct buf_sock *client) | ||
{ | ||
client->hdl->term(client->ch); | ||
} | ||
|
||
bool | ||
cli_reconnect(struct buf_sock *client) | ||
{ | ||
cli_disconnect(client); | ||
fwrite(DISCONNECT_MSG, sizeof(DISCONNECT_MSG), 1, stdout); | ||
if (!cli_connect(client)) { | ||
network_config.mode = OFFLINE; | ||
return false; | ||
} else { | ||
fwrite(RECONNECT_MSG, sizeof(RECONNECT_MSG), 1, stdout); | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
#pragma once | ||
|
||
/* for CLI, a few things are simplified: | ||
* - we only need one connection, so we keep it as a static global variable in | ||
* the network module. | ||
* - retry and timeout policy are coded into the network module as well, since | ||
* we don't expect many edge cases (mostly used on localhost for testing or | ||
* debuggin) | ||
* - network IO should block | ||
*/ | ||
|
||
#include <stream/cc_sockio.h> | ||
|
||
#include <stdbool.h> | ||
#include <stdint.h> | ||
|
||
/* string argument in order: protocol, host, port */ | ||
#define PROMPT_FMT_OFFLINE "%s %s:%s (not connected) > " | ||
#define PROMPT_FMT_LOCAL "%s :%s > " /* show protocol & port */ | ||
#define PROMPT_FMT_REMOTE "%s %s: > " /* show protocol & host */ | ||
|
||
#define SEND_ERROR "ERROR SENDING REQUEST\r\n" | ||
#define RECV_ERROR "ERROR RECEIVING RESPONSE\r\n" | ||
#define RECV_HUP "SERVER HUNG UP (e.g. due to syntax error)\r\n" | ||
#define DISCONNECT_MSG "CLIENT DISCONNECTED\r\n" | ||
#define RECONNECT_MSG "CLIENT RECONNECTED\r\n" | ||
|
||
typedef enum cli_network { | ||
LOCAL = 0, | ||
REMOTE = 1, | ||
OFFLINE = 2, | ||
} cli_network_e; | ||
|
||
struct network_config { | ||
cli_network_e mode; | ||
char * host; | ||
char * port; | ||
}; | ||
|
||
extern channel_handler_st tcp_handler; | ||
extern struct network_config network_config; | ||
|
||
/* network_config is used for cli_connect */ | ||
bool cli_connect(struct buf_sock *client); | ||
void cli_disconnect(struct buf_sock *client); | ||
bool cli_reconnect(struct buf_sock *client); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
set(SOURCE | ||
${SOURCE} | ||
cli.c | ||
main.c | ||
setting.c) | ||
|
||
set(MODULES | ||
client-network | ||
protocol_redis | ||
util) | ||
|
||
set(LIBS | ||
ccommon-static | ||
${CMAKE_THREAD_LIBS_INIT}) | ||
|
||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/_bin) | ||
set(TARGET_NAME ${PROJECT_NAME}_resp-cli) | ||
|
||
add_executable(${TARGET_NAME} ${SOURCE}) | ||
target_link_libraries(${TARGET_NAME} ${MODULES} ${LIBS}) | ||
|
||
install(TARGETS ${TARGET_NAME} RUNTIME DESTINATION bin) | ||
add_dependencies(service ${TARGET_NAME}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
#include "cli.h" | ||
|
||
#include "../network/cli_network.h" | ||
|
||
#include <cc_debug.h> | ||
#include <cc_mm.h> | ||
#include <cc_print.h> | ||
|
||
#include <ctype.h> | ||
#include <sys/param.h> | ||
|
||
#define PROTOCOL "resp" | ||
#define IO_BUF_MAX 1024 | ||
|
||
struct iobuf { | ||
char *input; | ||
char *output; | ||
size_t ilen; | ||
size_t olen; | ||
}; | ||
|
||
bool quit = false; | ||
struct iobuf buf; | ||
|
||
struct request *req; | ||
struct response *rsp; | ||
struct buf_sock *client; | ||
|
||
void | ||
cli_setup(respcli_options_st *options) | ||
{ | ||
if (options != NULL) { | ||
network_config.host = options->server_host.val.vstr; | ||
network_config.port = options->data_port.val.vstr; | ||
if (network_config.host == NULL) { /* if host is not provided it's local */ | ||
network_config.mode = LOCAL; | ||
} else { | ||
network_config.mode = REMOTE; | ||
} | ||
} | ||
|
||
/* slacking on NULL check here because this is very unlikely to fail */ | ||
req = request_create(); | ||
rsp = response_create(); | ||
client = buf_sock_create(); | ||
client->hdl = &tcp_handler; | ||
|
||
} | ||
|
||
void | ||
cli_teardown(void) | ||
{ | ||
request_destroy(&req); | ||
response_destroy(&rsp); | ||
buf_sock_destroy(&client); | ||
} | ||
|
||
static void | ||
_cli_prompt(void) | ||
{ | ||
size_t len; | ||
|
||
if (buf.output == NULL) { | ||
buf.output = cc_alloc(IO_BUF_MAX); | ||
} | ||
|
||
switch (network_config.mode) { | ||
case LOCAL: | ||
len = cc_snprintf(buf.output, IO_BUF_MAX, PROMPT_FMT_LOCAL, | ||
PROTOCOL, network_config.port); | ||
buf.olen = MIN(len, IO_BUF_MAX - 1); | ||
break; | ||
|
||
case REMOTE: | ||
len = cc_snprintf(buf.output, IO_BUF_MAX, PROMPT_FMT_REMOTE, | ||
PROTOCOL, network_config.host); | ||
buf.olen = MIN(len, IO_BUF_MAX - 1); | ||
break; | ||
|
||
case OFFLINE: | ||
len = cc_snprintf(buf.output, IO_BUF_MAX, PROMPT_FMT_OFFLINE, | ||
PROTOCOL, (network_config.host == NULL) ? "localhost" : | ||
network_config.host, network_config.port); | ||
buf.olen = MIN(len, IO_BUF_MAX - 1); | ||
break; | ||
|
||
default: | ||
NOT_REACHED(); | ||
} | ||
} | ||
|
||
|
||
static void | ||
_cli_parse_req(void) | ||
{ | ||
char *p, *token; | ||
struct element *el; | ||
|
||
p = buf.input; | ||
/* do not parse fully, just breaking down fields/tokens by delimiter */ | ||
while ((token = strsep(&p, " \t\r\n")) != NULL) { | ||
if (isspace(*token) || *token == '\0') { | ||
continue; | ||
} | ||
el = array_push(req->token); | ||
el->type = ELEM_BULK; | ||
el->bstr.len = strlen(token); | ||
el->bstr.data = token; | ||
} | ||
} | ||
|
||
static bool | ||
_cli_onerun(void) | ||
{ | ||
int status; | ||
|
||
buf_reset(client->rbuf); | ||
buf_reset(client->wbuf); | ||
request_reset(req); | ||
response_reset(rsp); | ||
|
||
/* print prompt */ | ||
_cli_prompt(); | ||
fwrite(buf.output, buf.olen, 1, stdout); | ||
|
||
/* wait for input, quit to exit the loop */ | ||
getline(&buf.input, &buf.ilen, stdin); | ||
if (cc_strncmp(buf.input, "quit", 4) == 0) { | ||
quit = true; | ||
return true; | ||
} | ||
|
||
/* parse input buffer into request object, translate */ | ||
_cli_parse_req(); | ||
status = compose_req(&client->wbuf, req); | ||
if (status < 0) { | ||
/* TODO: handle OOM error */ | ||
} | ||
|
||
/* issue command */ | ||
do { | ||
status = buf_tcp_write(client); | ||
} while (status == CC_ERETRY || status == CC_EAGAIN); /* retry write */ | ||
if (status != CC_OK) { | ||
fwrite(SEND_ERROR, sizeof(SEND_ERROR), 1, stdout); | ||
return false; | ||
} | ||
|
||
/* wait for complete response */ | ||
do { | ||
status = buf_tcp_read(client); | ||
if (status != CC_OK && status != CC_ERETRY) { | ||
if (status == CC_ERDHUP) { | ||
fwrite(RECV_HUP, sizeof(RECV_HUP), 1, stdout); | ||
} else { | ||
fwrite(RECV_ERROR, sizeof(RECV_ERROR), 1, stdout); | ||
} | ||
return false; | ||
} | ||
status = parse_rsp(rsp, client->rbuf); | ||
} while (status == PARSE_EUNFIN); | ||
client->rbuf->rpos = client->rbuf->begin; | ||
fwrite(client->rbuf->begin, buf_rsize(client->rbuf), 1, stdout); | ||
|
||
return true; | ||
} | ||
|
||
|
||
void | ||
cli_run(void) | ||
{ | ||
if (!cli_connect(client)) { | ||
network_config.mode = OFFLINE; | ||
} | ||
|
||
while (!quit) { | ||
if (!_cli_onerun() && !cli_reconnect(client)) { | ||
/* should reconnect but it failed */ | ||
quit = true; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#include "setting.h" | ||
|
||
void cli_run(void); | ||
|
||
void cli_setup(respcli_options_st *options); | ||
void cli_teardown(void); |
Oops, something went wrong.