Skip to content

Commit 0deaf77

Browse files
Shiv Kushwahfacebook-github-bot
authored andcommitted
Move parsing logic to onIngress from onIngressEOF and add chunking support on boundaries
Summary: Context: https://docs.google.com/document/d/1rtw4JJJojO9NpUKlIUO0_oCRjWftusTXbajEJ0Zf8mo/edit Before, our assumption was that the entire binary message would be given to our codec and then `onIngressEOF` would be called at the end when the result was needed. As a result, we did all of our parsing logic in `onIngressEOF`. However, now requests will start streaming in on a chunkwise basis and `onIngress` will be called multiple times, each with a chunk of the message. In this diff, I move the parsing logic from `onIngressEOF` into `onIngress` to support this new workflow. I additionally started adding support for HTTP binary chunks. This v1 implementation for binary chunks only supports message breaks on boundaries (ex. breaking after a section like the framing indicator, but not within a section). Reviewed By: lnicco Differential Revision: D64381365 fbshipit-source-id: 3ba303b89245a6c06e03a08d794bed3e755ab77d
1 parent 2665f45 commit 0deaf77

File tree

3 files changed

+94
-65
lines changed

3 files changed

+94
-65
lines changed

proxygen/lib/http/codec/HTTPBinaryCodec.cpp

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,7 @@ ParseResult HTTPBinaryCodec::parseHeaders(folly::io::Cursor& cursor,
253253
}
254254

255255
ParseResult HTTPBinaryCodec::parseContent(folly::io::Cursor& cursor,
256-
size_t remaining,
257-
HTTPMessage& msg) {
256+
size_t remaining) {
258257
size_t parsed = 0;
259258

260259
// Parse the contentLength and advance cursor
@@ -288,20 +287,14 @@ ParseResult HTTPBinaryCodec::parseTrailers(folly::io::Cursor& cursor,
288287
}
289288

290289
size_t HTTPBinaryCodec::onIngress(const folly::IOBuf& buf) {
291-
auto len = bufferedIngress_.chainLength();
292290
bufferedIngress_.append(buf.clone());
293-
return bufferedIngress_.chainLength() - len;
294-
}
291+
const auto len = bufferedIngress_.chainLength();
295292

296-
void HTTPBinaryCodec::onIngressEOF() {
297293
size_t parsedTot = 0;
298294
folly::io::Cursor cursor(bufferedIngress_.front());
299295
auto bufLen = bufferedIngress_.chainLength();
300-
if (!bufLen) {
301-
parseError_ = "Empty buffer provided!";
302-
}
303296

304-
while (!parseError_ && parsedTot < bufLen && !parserPaused_) {
297+
while (!parseError_ && !parserPaused_ && parsedTot < bufLen) {
305298
size_t parsed = 0;
306299
ParseResult parseResult(ParseResultState::INITIALIZED);
307300
switch (state_) {
@@ -374,17 +367,20 @@ void HTTPBinaryCodec::onIngressEOF() {
374367
parsed += parseResult.bytesParsed_;
375368
state_ = ParseState::CONTENT;
376369
msg_ = std::move(decodeInfo_.msg);
370+
callback_->onHeadersComplete(ingressTxnID_, std::move(msg_));
377371
break;
378372

379373
case ParseState::CONTENT:
380-
CHECK(msg_);
381-
parseResult = parseContent(cursor, bufLen - parsedTot, *msg_);
374+
parseResult = parseContent(cursor, bufLen - parsedTot);
382375
if (parseResult.parseResultState_ == ParseResultState::ERROR) {
383376
parseError_ = parseResult.error_;
384377
break;
385378
}
386379
parsed += parseResult.bytesParsed_;
387380
state_ = ParseState::TRAILERS_SECTION;
381+
if (msgBody_) {
382+
callback_->onBody(ingressTxnID_, std::move(msgBody_), 0);
383+
}
388384
break;
389385

390386
case ParseState::TRAILERS_SECTION:
@@ -402,6 +398,9 @@ void HTTPBinaryCodec::onIngressEOF() {
402398
std::make_unique<HTTPHeaders>(decodeInfo_.msg->getHeaders());
403399
parsed += parseResult.bytesParsed_;
404400
state_ = ParseState::PADDING;
401+
if (trailers_) {
402+
callback_->onTrailersComplete(ingressTxnID_, std::move(trailers_));
403+
}
405404
break;
406405

407406
case ParseState::PADDING:
@@ -422,30 +421,33 @@ void HTTPBinaryCodec::onIngressEOF() {
422421
ingressTxnID_,
423422
HTTPException(HTTPException::Direction::INGRESS,
424423
fmt::format("Invalid Message: {}", *parseError_)));
425-
} else {
424+
}
426425

427-
if (!msg_) {
428-
if (state_ == ParseState::HEADERS_SECTION) {
429-
// Case where the sent message only contains control data
430-
msg_ = std::move(decodeInfo_.msg);
431-
} else {
432-
callback_->onError(
433-
ingressTxnID_,
434-
HTTPException(
435-
HTTPException::Direction::INGRESS,
436-
fmt::format("Message not formed (incomplete binary data)")));
437-
return;
438-
}
439-
}
440-
callback_->onHeadersComplete(ingressTxnID_, std::move(msg_));
441-
if (msgBody_) {
442-
callback_->onBody(ingressTxnID_, std::move(msgBody_), 0);
443-
}
444-
if (trailers_) {
445-
callback_->onTrailersComplete(ingressTxnID_, std::move(trailers_));
446-
}
426+
// We can trim the amount of bufferedIngress_ that we were successfully able
427+
// to parse
428+
bufferedIngress_.trimStartAtMost(parsedTot);
429+
430+
return len;
431+
}
432+
433+
void HTTPBinaryCodec::onIngressEOF() {
434+
if (!parseError_ && !bufferedIngress_.empty()) {
435+
// Case where the ingress EOF is received before the entire message is
436+
// parsed
437+
callback_->onError(ingressTxnID_,
438+
HTTPException(HTTPException::Direction::INGRESS,
439+
"Incomplete message received"));
440+
return;
441+
}
442+
if (state_ == ParseState::HEADERS_SECTION) {
443+
// Case where the sent message only contains control data and no headers
444+
// nor body
445+
callback_->onHeadersComplete(ingressTxnID_, std::move(decodeInfo_.msg));
446+
}
447+
if (!parseError_ && !parserPaused_) {
447448
callback_->onMessageComplete(ingressTxnID_, false);
448449
}
450+
return;
449451
}
450452

451453
size_t HTTPBinaryCodec::generateHeaderHelper(folly::io::QueueAppender& appender,

proxygen/lib/http/codec/HTTPBinaryCodec.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,7 @@ class HTTPBinaryCodec : public HTTPCodec {
163163
ParseResult parseHeaders(folly::io::Cursor& cursor,
164164
size_t remaining,
165165
HeaderDecodeInfo& decodeInfo);
166-
ParseResult parseContent(folly::io::Cursor& cursor,
167-
size_t remaining,
168-
HTTPMessage& msg);
166+
ParseResult parseContent(folly::io::Cursor& cursor, size_t remaining);
169167
ParseResult parseTrailers(folly::io::Cursor& cursor,
170168
size_t remaining,
171169
HeaderDecodeInfo& decodeInfo);

proxygen/lib/http/codec/test/HTTPBinaryCodecTest.cpp

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,8 @@ class HTTPBinaryCodecForTest : public HTTPBinaryCodec {
4444
return HTTPBinaryCodec::parseHeaders(cursor, remaining, decodeInfo);
4545
}
4646

47-
ParseResult parseContent(folly::io::Cursor& cursor,
48-
size_t remaining,
49-
HTTPMessage& msg) {
50-
return HTTPBinaryCodec::parseContent(cursor, remaining, msg);
47+
ParseResult parseContent(folly::io::Cursor& cursor, size_t remaining) {
48+
return HTTPBinaryCodec::parseContent(cursor, remaining);
5149
}
5250

5351
folly::IOBuf& getMsgBody() {
@@ -360,9 +358,9 @@ TEST_F(HTTPBinaryCodecTest, testParseContentSuccess) {
360358
folly::io::Cursor cursor(contentIOBuf.get());
361359

362360
HTTPMessage msg;
363-
EXPECT_EQ(upstreamBinaryCodec_->parseContent(cursor, content.size(), msg)
364-
.bytesParsed_,
365-
content.size());
361+
EXPECT_EQ(
362+
upstreamBinaryCodec_->parseContent(cursor, content.size()).bytesParsed_,
363+
content.size());
366364
EXPECT_EQ(upstreamBinaryCodec_->getMsgBody().to<std::string>(), "hello\r\n");
367365
}
368366

@@ -376,8 +374,7 @@ TEST_F(HTTPBinaryCodecTest, testParseContentFailure) {
376374

377375
HTTPMessage msg;
378376
EXPECT_EQ(
379-
upstreamBinaryCodec_->parseContent(cursor, contentInvalid.size(), msg)
380-
.error_,
377+
upstreamBinaryCodec_->parseContent(cursor, contentInvalid.size()).error_,
381378
"Failure to parse content");
382379
}
383380

@@ -449,6 +446,57 @@ TEST_F(HTTPBinaryCodecTest, testOnIngressSuccessForControlData) {
449446
EXPECT_EQ(callback.msg->getURL(), "/");
450447
}
451448

449+
TEST_F(HTTPBinaryCodecTest, testOnIngressSuccessChunkedOnBoundaryMessage) {
450+
// Format is chunk1 = `..GET`, chunk2 =
451+
// `.https.www.example.com./hello.txt..user-agent.curl/7.16.3 libcurl/7.16.3
452+
// OpenSSL/0.9.7l zlib/1.2.3.host.www.example.com.accept-language.en, mi`
453+
const std::vector<uint8_t> binaryHTTPMessageChunk1{
454+
0x00, 0x03, 0x47, 0x45, 0x54, 0x05, 0x68, 0x74, 0x74, 0x70,
455+
0x73, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d,
456+
0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x2f, 0x68,
457+
0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x74, 0x78, 0x74};
458+
const std::vector<uint8_t> binaryHTTPMessageChunk2{
459+
0x40, 0x6c, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e,
460+
0x74, 0x34, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e, 0x31, 0x36, 0x2e,
461+
0x33, 0x20, 0x6c, 0x69, 0x62, 0x63, 0x75, 0x72, 0x6c, 0x2f, 0x37, 0x2e,
462+
0x31, 0x36, 0x2e, 0x33, 0x20, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x53, 0x4c,
463+
0x2f, 0x30, 0x2e, 0x39, 0x2e, 0x37, 0x6c, 0x20, 0x7a, 0x6c, 0x69, 0x62,
464+
0x2f, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x0f,
465+
0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e,
466+
0x63, 0x6f, 0x6d, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c,
467+
0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x06, 0x65, 0x6e, 0x2c, 0x20,
468+
0x6d, 0x69, 0x00, 0x00};
469+
470+
auto binaryHTTPMessageIOBufChunk1 = folly::IOBuf::wrapBuffer(folly::ByteRange(
471+
binaryHTTPMessageChunk1.data(), binaryHTTPMessageChunk1.size()));
472+
folly::io::Cursor cursor1(binaryHTTPMessageIOBufChunk1.get());
473+
auto binaryHTTPMessageIOBufChunk2 = folly::IOBuf::wrapBuffer(folly::ByteRange(
474+
binaryHTTPMessageChunk2.data(), binaryHTTPMessageChunk2.size()));
475+
folly::io::Cursor cursor2(binaryHTTPMessageIOBufChunk2.get());
476+
477+
FakeHTTPCodecCallback callback;
478+
upstreamBinaryCodec_->setCallback(&callback);
479+
upstreamBinaryCodec_->onIngress(*binaryHTTPMessageIOBufChunk1);
480+
upstreamBinaryCodec_->onIngress(*binaryHTTPMessageIOBufChunk2);
481+
upstreamBinaryCodec_->onIngressEOF();
482+
483+
// Check onError was not called for the callback
484+
EXPECT_EQ(callback.lastParseError, nullptr);
485+
486+
// Check msg and header fields
487+
EXPECT_EQ(callback.msg->isSecure(), true);
488+
EXPECT_EQ(callback.msg->getMethod(), proxygen::HTTPMethod::GET);
489+
EXPECT_EQ(callback.msg->getURL(), "/hello.txt");
490+
HTTPHeaders httpHeaders = callback.msg->getHeaders();
491+
EXPECT_EQ(httpHeaders.exists("user-agent"), true);
492+
EXPECT_EQ(httpHeaders.exists("host"), true);
493+
EXPECT_EQ(httpHeaders.exists("accept-language"), true);
494+
EXPECT_EQ(httpHeaders.getSingleOrEmpty("user-agent"),
495+
"curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3");
496+
EXPECT_EQ(httpHeaders.getSingleOrEmpty("host"), "www.example.com");
497+
EXPECT_EQ(httpHeaders.getSingleOrEmpty("accept-language"), "en, mi");
498+
}
499+
452500
TEST_F(HTTPBinaryCodecTest, testOnIngressFailureMalformedMessage) {
453501
// Format is `..GET.https.www.example.com./hello.txt..user-agent.curl/7.16.3
454502
// libcurl/7.16.3 OpenSSL/0.9.7l
@@ -482,24 +530,6 @@ TEST_F(HTTPBinaryCodecTest, testOnIngressFailureMalformedMessage) {
482530
"Invalid Message: Failure to parse: headerValue");
483531
}
484532

485-
TEST_F(HTTPBinaryCodecTest, testOnIngressFailureIncompleteMessage) {
486-
// Message is incomplete and has only 1 byte
487-
const std::vector<uint8_t> binaryInvalidHTTPMessage{0x00};
488-
auto binaryInvalidHTTPMessageIOBuf =
489-
folly::IOBuf::wrapBuffer(folly::ByteRange(
490-
binaryInvalidHTTPMessage.data(), binaryInvalidHTTPMessage.size()));
491-
folly::io::Cursor cursor(binaryInvalidHTTPMessageIOBuf.get());
492-
493-
FakeHTTPCodecCallback callback;
494-
upstreamBinaryCodec_->setCallback(&callback);
495-
upstreamBinaryCodec_->onIngress(*binaryInvalidHTTPMessageIOBuf);
496-
upstreamBinaryCodec_->onIngressEOF();
497-
498-
// Check onError was called with the correct error
499-
EXPECT_EQ(std::string(callback.lastParseError.get()->what()),
500-
"Message not formed (incomplete binary data)");
501-
}
502-
503533
TEST_F(HTTPBinaryCodecTest, testGenerateHeaders) {
504534
// Create HTTPMessage and encode it to a buffer
505535
HTTPMessage msgEncoded;
@@ -545,8 +575,7 @@ TEST_F(HTTPBinaryCodecTest, testGenerateBody) {
545575
// Decode Test Body and check
546576
folly::io::Cursor cursor(writeBuffer.front());
547577
HTTPMessage msg;
548-
EXPECT_EQ(upstreamBinaryCodec_->parseContent(cursor, 18, msg).bytesParsed_,
549-
18);
578+
EXPECT_EQ(upstreamBinaryCodec_->parseContent(cursor, 18).bytesParsed_, 18);
550579
EXPECT_EQ(upstreamBinaryCodec_->getMsgBody().to<std::string>(),
551580
"Sample Test Body!");
552581
}

0 commit comments

Comments
 (0)