Skip to content

Commit 8c8ec21

Browse files
author
gophergogo
committed
Surface numeric :status pseudo-header from HTTP client codec (#213)
The codec was only publishing the reason phrase in headers["status"] on the client path. That is good for logging but useless for callers that need to branch on the actual HTTP status (e.g. to treat a 2xx as success and a 4xx as a client error). Mirror the :method pseudo-header the server path already writes and populate headers[":status"] with the numeric response code before firing onHeaders. Comes with two unit tests that feed a minimal HTTP/1.1 response through the filter and assert both :status and the original status reason land on the callbacks' header map — guarding against future regressions in onHeadersComplete's client branch.
1 parent f7a4c46 commit 8c8ec21

2 files changed

Lines changed: 63 additions & 0 deletions

File tree

src/filter/http_codec_filter.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,12 @@ HttpCodecFilter::ParserCallbacks::onHeadersComplete() {
621621
}
622622
parent_.current_stream_->headers[":method"] = method_str;
623623
parent_.current_stream_->method = method_str;
624+
} else {
625+
// Client mode: surface numeric response status as :status pseudo-header.
626+
// Callers (HttpAsyncClient, etc.) need the numeric code, not just the
627+
// reason phrase that onStatus already captures into headers["status"].
628+
auto status_code = static_cast<uint16_t>(parent_.parser_->statusCode());
629+
parent_.current_stream_->headers[":status"] = std::to_string(status_code);
624630
}
625631

626632
// Check keep-alive

tests/filter/test_http_codec_filter.cc

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,63 @@ TEST_F(HttpCodecFilterBodyTimeoutTest, ClientModeDisablesBodyTimeout) {
589589
"normal idle gaps, then crash the state machine on a live stream";
590590
}
591591

592+
// ---------------------------------------------------------------------------
593+
// Client-mode response parsing surfaces numeric status as :status.
594+
//
595+
// The upstream onStatus callback only captures the reason phrase into
596+
// headers["status"] ("OK", "Not Found", ...). Callers that need to branch
597+
// on the actual HTTP status code (the HttpAsyncClient does) need the
598+
// numeric value. The codec populates headers[":status"] in onHeadersComplete
599+
// mirroring how server mode populates headers[":method"]; this test pins
600+
// that contract.
601+
// ---------------------------------------------------------------------------
602+
603+
class HttpCodecFilterClientStatusTest : public ::testing::Test {
604+
protected:
605+
void SetUp() override {
606+
auto factory = event::createLibeventDispatcherFactory();
607+
dispatcher_ = factory->createDispatcher("codec-status");
608+
dispatcher_->run(event::RunType::NonBlock);
609+
callbacks_ = std::make_unique<TestRequestCallbacks>();
610+
}
611+
612+
void feedResponse(HttpCodecFilter& filter, const std::string& bytes) {
613+
OwnedBuffer buf;
614+
buf.add(bytes.c_str(), bytes.length());
615+
filter.onNewConnection();
616+
filter.onData(buf, false);
617+
}
618+
619+
std::unique_ptr<event::Dispatcher> dispatcher_;
620+
std::unique_ptr<TestRequestCallbacks> callbacks_;
621+
};
622+
623+
TEST_F(HttpCodecFilterClientStatusTest, PopulatesNumericStatusPseudoHeader) {
624+
HttpCodecFilter filter(*callbacks_, *dispatcher_, /*is_server=*/false);
625+
feedResponse(filter,
626+
"HTTP/1.1 404 Not Found\r\n"
627+
"Content-Length: 0\r\n"
628+
"Connection: close\r\n"
629+
"\r\n");
630+
ASSERT_TRUE(callbacks_->waitForHeaders());
631+
auto headers = callbacks_->getHeaders();
632+
EXPECT_EQ(headers[":status"], "404")
633+
<< "client mode must expose the numeric status as :status";
634+
EXPECT_EQ(headers["status"], "Not Found")
635+
<< "reason phrase should still land in headers[\"status\"]";
636+
}
637+
638+
TEST_F(HttpCodecFilterClientStatusTest, SurfacesSuccessStatus) {
639+
HttpCodecFilter filter(*callbacks_, *dispatcher_, /*is_server=*/false);
640+
feedResponse(filter,
641+
"HTTP/1.1 202 Accepted\r\n"
642+
"Content-Length: 0\r\n"
643+
"Connection: close\r\n"
644+
"\r\n");
645+
ASSERT_TRUE(callbacks_->waitForHeaders());
646+
EXPECT_EQ(callbacks_->getHeaders()[":status"], "202");
647+
}
648+
592649
} // namespace
593650
} // namespace filter
594651
} // namespace mcp

0 commit comments

Comments
 (0)