复现已有算法

This commit is contained in:
cloud
2026-07-14 15:43:18 +08:00
parent abebd2a683
commit 50b8111fd9
860 changed files with 182250 additions and 18 deletions
+491
View File
@@ -0,0 +1,491 @@
/**
*
* HttpRequestParser.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "HttpRequestParser.h"
#include <drogon/HttpTypes.h>
#include <trantor/utils/Logger.h>
#include <trantor/utils/MsgBuffer.h>
#include <iostream>
#include "HttpAppFrameworkImpl.h"
#include "HttpRequestImpl.h"
#include "HttpResponseImpl.h"
#include "HttpUtils.h"
using namespace trantor;
using namespace drogon;
static constexpr size_t CRLF_LEN = 2; // strlen("crlf")
static constexpr size_t METHOD_MAX_LEN = 7; // strlen("OPTIONS")
static constexpr size_t TRUNK_LEN_MAX_LEN = 16; // 0xFFFFFFFF,FFFFFFFF
HttpRequestParser::HttpRequestParser(const trantor::TcpConnectionPtr &connPtr)
: status_(HttpRequestParseStatus::kExpectMethod),
loop_(connPtr->getLoop()),
conn_(connPtr)
{
}
bool HttpRequestParser::processRequestLine(const char *begin, const char *end)
{
bool succeed = false;
const char *start = begin;
const char *space = std::find(start, end, ' ');
if (space != end)
{
const char *slash = std::find(start, space, '/');
if (slash != start && slash + 1 < space && *(slash + 1) == '/')
{
// scheme precedents
slash = std::find(slash + 2, space, '/');
}
const char *question = std::find(slash, space, '?');
if (slash != space)
{
request_->setPath(slash, question);
}
else
{
// An empty abs_path is equivalent to an abs_path of "/"
request_->setPath("/");
}
if (question != space)
{
request_->setQuery(question + 1, space);
}
start = space + 1;
succeed = end - start == 8 && std::equal(start, end - 1, "HTTP/1.");
if (succeed)
{
if (*(end - 1) == '1')
{
request_->setVersion(Version::kHttp11);
}
else if (*(end - 1) == '0')
{
request_->setVersion(Version::kHttp10);
}
else
{
succeed = false;
}
}
}
return succeed;
}
HttpRequestImplPtr HttpRequestParser::makeRequestForPool(HttpRequestImpl *ptr)
{
return std::shared_ptr<HttpRequestImpl>(
ptr, [weakPtr = weak_from_this()](HttpRequestImpl *p) {
auto thisPtr = weakPtr.lock();
if (thisPtr)
{
if (thisPtr->loop_->isInLoopThread())
{
p->reset();
thisPtr->requestsPool_.emplace_back(
thisPtr->makeRequestForPool(p));
}
else
{
auto &loop = thisPtr->loop_;
loop->queueInLoop([thisPtr = std::move(thisPtr), p]() {
p->reset();
thisPtr->requestsPool_.emplace_back(
thisPtr->makeRequestForPool(p));
});
}
}
else
{
delete p;
}
});
}
void HttpRequestParser::reset()
{
assert(loop_->isInLoopThread());
remainContentLength_ = 0;
status_ = HttpRequestParseStatus::kExpectMethod;
if (requestsPool_.empty())
{
request_ = makeRequestForPool(new HttpRequestImpl(loop_));
}
else
{
auto req = std::move(requestsPool_.back());
requestsPool_.pop_back();
request_ = std::move(req);
request_->setCreationDate(trantor::Date::now());
}
}
/**
* @return return -HttpStatusCode if encounters any http errors in request
* @return return -1 if encounters any other errors in request
* @return return 0 if request is not ready
* @return return 1 if request is ready
* @return return 2 if request is ready and entering stream mode
* @return return 3 if request header is ready and entering stream mode
*/
int HttpRequestParser::parseRequest(MsgBuffer *buf)
{
while (true)
{
switch (status_)
{
case (HttpRequestParseStatus::kExpectMethod):
{
auto *space = std::find(buf->peek(),
(const char *)buf->beginWrite(),
' ');
// no space in buffer
if (space == buf->beginWrite())
{
if (buf->readableBytes() > METHOD_MAX_LEN)
{
return -k400BadRequest;
}
return 0;
}
// try read method
if (!request_->setMethod(buf->peek(), space))
{
return -k405MethodNotAllowed;
}
status_ = HttpRequestParseStatus::kExpectRequestLine;
buf->retrieveUntil(space + 1);
continue;
}
case HttpRequestParseStatus::kExpectRequestLine:
{
const char *crlf = buf->findCRLF();
if (!crlf)
{
if (buf->readableBytes() >= 64 * 1024)
{
/// The limit for request line is 64K bytes. response
/// k414RequestURITooLarge
/// TODO: Make this configurable?
return -k414RequestURITooLarge;
}
return 0;
}
if (!processRequestLine(buf->peek(), crlf))
{
// error
return -k400BadRequest;
}
buf->retrieveUntil(crlf + CRLF_LEN);
status_ = HttpRequestParseStatus::kExpectHeaders;
continue;
}
case HttpRequestParseStatus::kExpectHeaders:
{
const char *crlf = buf->findCRLF();
if (!crlf)
{
if (buf->readableBytes() >= 64 * 1024)
{
/// The limit for every request header is 64K bytes;
/// TODO: Make this configurable?
return -k400BadRequest;
}
return 0;
}
const char *colon = std::find(buf->peek(), crlf, ':');
// found colon
if (colon != crlf)
{
request_->addHeader(buf->peek(), colon, crlf);
buf->retrieveUntil(crlf + CRLF_LEN);
continue;
}
buf->retrieveUntil(crlf + CRLF_LEN);
// end of headers
// We might want a kProcessHeaders status for code readability
// and maintainability.
// process header information
auto &len = request_->getHeaderBy("content-length");
if (!len.empty())
{
try
{
remainContentLength_ =
static_cast<size_t>(std::stoull(len));
}
catch (...)
{
return -k400BadRequest;
}
request_->contentLengthHeaderValue_ = remainContentLength_;
if (remainContentLength_ == 0)
{
// content-length = 0, request is over.
status_ = HttpRequestParseStatus::kGotAll;
}
else
{
status_ = HttpRequestParseStatus::kExpectBody;
}
}
else
{
const std::string &encode =
request_->getHeaderBy("transfer-encoding");
if (encode.empty())
{
// no content-length and no transfer-encoding,
// request is over.
status_ = HttpRequestParseStatus::kGotAll;
}
else if (encode == "chunked")
{
status_ = HttpRequestParseStatus::kExpectChunkLen;
}
else
{
return -k501NotImplemented;
}
}
// Check max body size
if (remainContentLength_ >
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
{
return -k413RequestEntityTooLarge;
}
// Check expect:100-continue
auto &expect = request_->expect();
if (expect == "100-continue" &&
request_->getVersion() >= Version::kHttp11)
{
if (remainContentLength_ == 0)
{
// error
return -k400BadRequest;
}
else
{
// rfc2616-8.2.3
// TODO: consider adding an AOP for expect header
auto connPtr = conn_.lock(); // ugly
if (!connPtr)
{
return -1;
}
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k100Continue);
auto httpString =
static_cast<HttpResponseImpl *>(resp.get())
->renderToBuffer();
connPtr->send(std::move(*httpString));
}
}
else if (!expect.empty())
{
LOG_WARN << "417ExpectationFailed for \"" << expect << "\"";
return -k417ExpectationFailed;
}
assert(status_ == HttpRequestParseStatus::kGotAll ||
status_ == HttpRequestParseStatus::kExpectBody ||
status_ == HttpRequestParseStatus::kExpectChunkLen);
if (app().isRequestStreamEnabled())
{
request_->streamStart();
if (status_ == HttpRequestParseStatus::kGotAll)
{
++requestsCounter_;
return 2;
}
else
{
return 3;
}
}
// Reserve space for full body in non-stream mode.
// For stream mode requests that match a non-stream handler,
// we will reserve full body before waitForStreamFinish().
if (remainContentLength_)
{
request_->reserveBodySize(remainContentLength_);
}
continue;
}
case HttpRequestParseStatus::kExpectBody:
{
size_t bytesToConsume =
remainContentLength_ <= buf->readableBytes()
? remainContentLength_
: buf->readableBytes();
if (bytesToConsume)
{
request_->appendToBody(buf->peek(), bytesToConsume);
buf->retrieve(bytesToConsume);
remainContentLength_ -= bytesToConsume;
}
if (remainContentLength_ == 0)
{
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
return 1;
}
// readableBytes() == 0, function should return.
return 0;
}
case HttpRequestParseStatus::kExpectChunkLen:
{
const char *crlf = buf->findCRLF();
if (!crlf)
{
if (buf->readableBytes() > TRUNK_LEN_MAX_LEN + CRLF_LEN)
{
return -k400BadRequest;
}
return 0;
}
// chunk length line
std::string len(buf->peek(), crlf - buf->peek());
char *end;
currentChunkLength_ = strtol(len.c_str(), &end, 16);
if (currentChunkLength_ != 0)
{
if (currentChunkLength_ + remainContentLength_ >
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
{
return -k413RequestEntityTooLarge;
}
status_ = HttpRequestParseStatus::kExpectChunkBody;
}
else
{
status_ = HttpRequestParseStatus::kExpectLastEmptyChunk;
}
buf->retrieveUntil(crlf + CRLF_LEN);
continue;
}
case HttpRequestParseStatus::kExpectChunkBody:
{
if (buf->readableBytes() < (currentChunkLength_ + CRLF_LEN))
{
return 0;
}
if (*(buf->peek() + currentChunkLength_) != '\r' ||
*(buf->peek() + currentChunkLength_ + 1) != '\n')
{
// error!
return -k400BadRequest;
}
request_->appendToBody(buf->peek(), currentChunkLength_);
buf->retrieve(currentChunkLength_ + CRLF_LEN);
remainContentLength_ += currentChunkLength_;
currentChunkLength_ = 0;
status_ = HttpRequestParseStatus::kExpectChunkLen;
continue;
}
case HttpRequestParseStatus::kExpectLastEmptyChunk:
{
// last empty chunk
if (buf->readableBytes() < CRLF_LEN)
{
return 0;
}
if (*(buf->peek()) != '\r' || *(buf->peek() + 1) != '\n')
{
// error!
return -k400BadRequest;
}
buf->retrieve(CRLF_LEN);
if (!request_->isStreamMode())
{
// Previously we only have non-stream mode, drogon handled
// chunked encoding internally, and give user a regular
// request as if it has a content-length header.
//
// We have to keep compatibility for non-stream mode.
//
// But I don't think it's a good implementation. We should
// instead add an api to access real content-length of
// requests.
// Now HttpRequest::realContentLength() is added, and user
// should no longer parse content-length header by
// themselves.
//
// NOTE: request forward behavior may be infected in stream
// mode, we should check it out.
request_->addHeader("content-length",
std::to_string(
request_->realContentLength()));
request_->removeHeaderBy("transfer-encoding");
}
status_ = HttpRequestParseStatus::kGotAll;
++requestsCounter_;
return 1;
}
case HttpRequestParseStatus::kGotAll:
{
++requestsCounter_;
return 1;
}
}
}
return -1; // won't reach here, just to make compiler happy
}
void HttpRequestParser::pushRequestToPipelining(const HttpRequestPtr &req,
bool isHeadMethod)
{
assert(loop_->isInLoopThread());
requestPipelining_.push_back({req, {nullptr, isHeadMethod}});
}
/**
* @return returns true if the the response is the first in pipeline
*/
bool HttpRequestParser::pushResponseToPipelining(const HttpRequestPtr &req,
HttpResponsePtr resp)
{
assert(loop_->isInLoopThread());
for (size_t i = 0; i != requestPipelining_.size(); ++i)
{
if (requestPipelining_[i].first == req)
{
requestPipelining_[i].second.first = std::move(resp);
return i == 0;
}
}
assert(false); // Should always find a match
return false;
}
void HttpRequestParser::popReadyResponses(
std::vector<std::pair<HttpResponsePtr, bool>> &buffer)
{
while (!requestPipelining_.empty() &&
requestPipelining_.front().second.first)
{
buffer.push_back(std::move(requestPipelining_.front().second));
requestPipelining_.pop_front();
}
}