Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/brpc/redis_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,23 @@ RedisCommandConsumeState RedisCommandParser::ConsumeImpl(butil::IOBuf& buf,
*err = PARSE_ERROR_TRY_OTHERS;
return CONSUME_STATE_ERROR;
}
// The HTTP/2 connection preface ("PRI * HTTP/2.0\r\n...") is alpha-leading
// and would otherwise be consumed as an inline redis command (first token
// "PRI"), preventing protocol auto-detection from falling through to
// HTTP/2 (e.g. gRPC clients). Defer to other protocols when the input
// matches the preface, either fully or as a not-yet-complete prefix. No
// valid redis command begins with these bytes, so this is unambiguous.
// See issue #3109.
static const char h2_preface[] = "PRI * HTTP/2.0\r\n";
const size_t h2_preface_len = sizeof(h2_preface) - 1;
if (*pfc == h2_preface[0]) {
char head[h2_preface_len];
const size_t n = buf.copy_to(head, h2_preface_len);
if (memcmp(head, h2_preface, n) == 0) {
*err = PARSE_ERROR_TRY_OTHERS;
return CONSUME_STATE_ERROR;
}
}
const size_t buf_size = buf.size();
const auto copy_str = static_cast<char *>(arena->allocate(buf_size + 1));
// arena->allocate() may return NULL on allocation failure
Expand Down
36 changes: 36 additions & 0 deletions test/brpc_redis_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,42 @@ TEST_F(RedisTest, command_parser) {
}
}

// Regression test for issue #3109: the inline redis protocol must not consume
// the HTTP/2 connection preface as a command, otherwise protocol auto-detection
// never falls through to HTTP/2 and gRPC clients fail with "connection closed
// before server preface received".
TEST_F(RedisTest, inline_does_not_eat_h2_preface) {
brpc::RedisCommandParser parser;
butil::IOBuf buf;
std::vector<butil::StringPiece> command_out;
butil::Arena arena;
{
// Full HTTP/2 client connection preface: must defer to other protocols.
buf.append("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
ASSERT_EQ(brpc::PARSE_ERROR_TRY_OTHERS,
parser.Consume(buf, &command_out, &arena));
buf.clear();
parser.Reset();
}
{
// A not-yet-complete prefix of the preface must also defer, leaving the
// bytes intact for the HTTP/2 parser instead of being misparsed.
buf.append("PRI * HT");
ASSERT_EQ(brpc::PARSE_ERROR_TRY_OTHERS,
parser.Consume(buf, &command_out, &arena));
buf.clear();
parser.Reset();
}
{
// A genuine inline command sharing the leading 'P' must still parse.
buf.append("PING\r\n");
ASSERT_EQ(brpc::PARSE_OK, parser.Consume(buf, &command_out, &arena));
ASSERT_EQ("ping", GetCompleteCommand(command_out));
buf.clear();
parser.Reset();
}
}

TEST_F(RedisTest, redis_reply_codec) {
butil::Arena arena;
// status
Expand Down
Loading