492 lines
17 KiB
C++
492 lines
17 KiB
C++
/**
|
|
*
|
|
* 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();
|
|
}
|
|
}
|