mirror of
https://github.com/Relintai/rcpp_framework.git
synced 2024-11-14 04:57:21 +01:00
700 lines
20 KiB
C++
700 lines
20 KiB
C++
/**
|
|
*
|
|
* @file HttpRequest.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 "http_request.h"
|
|
|
|
#include "http_file_upload_request.h"
|
|
//#include "HttpAppFrameworkImpl.h"
|
|
|
|
#include <core/utilities.h>
|
|
|
|
#include "core/application.h"
|
|
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
using namespace drogon;
|
|
|
|
void HttpRequest::parseJson() const
|
|
{
|
|
auto input = contentView();
|
|
if (input.empty())
|
|
return;
|
|
if (contentType_ == CT_APPLICATION_JSON ||
|
|
getHeaderBy("content-type").find("application/json") !=
|
|
std::string::npos)
|
|
{
|
|
static std::once_flag once;
|
|
static Json::CharReaderBuilder builder;
|
|
std::call_once(once, []() { builder["collectComments"] = false; });
|
|
jsonPtr_ = std::make_shared<Json::Value>();
|
|
JSONCPP_STRING errs;
|
|
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
|
|
if (!reader->parse(input.data(),
|
|
input.data() + input.size(),
|
|
jsonPtr_.get(),
|
|
&errs))
|
|
{
|
|
LOG_ERROR << errs;
|
|
jsonPtr_.reset();
|
|
jsonParsingErrorPtr_ =
|
|
std::make_unique<std::string>(std::move(errs));
|
|
}
|
|
else
|
|
{
|
|
jsonParsingErrorPtr_.reset();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
jsonPtr_.reset();
|
|
jsonParsingErrorPtr_ =
|
|
std::make_unique<std::string>("content type error");
|
|
}
|
|
}
|
|
void HttpRequest::parseParameters() const
|
|
{
|
|
auto input = queryView();
|
|
if (!input.empty())
|
|
{
|
|
string_view::size_type pos = 0;
|
|
while ((input[pos] == '?' || isspace(input[pos])) &&
|
|
pos < input.length())
|
|
{
|
|
++pos;
|
|
}
|
|
auto value = input.substr(pos);
|
|
while ((pos = value.find('&')) != string_view::npos)
|
|
{
|
|
auto coo = value.substr(0, pos);
|
|
auto epos = coo.find('=');
|
|
if (epos != string_view::npos)
|
|
{
|
|
auto key = coo.substr(0, epos);
|
|
string_view::size_type cpos = 0;
|
|
while (cpos < key.length() && isspace(key[cpos]))
|
|
++cpos;
|
|
key = key.substr(cpos);
|
|
auto pvalue = coo.substr(epos + 1);
|
|
std::string pdecode = utils::urlDecode(pvalue);
|
|
std::string keydecode = utils::urlDecode(key);
|
|
parameters_[keydecode] = pdecode;
|
|
}
|
|
value = value.substr(pos + 1);
|
|
}
|
|
if (value.length() > 0)
|
|
{
|
|
auto &coo = value;
|
|
auto epos = coo.find('=');
|
|
if (epos != string_view::npos)
|
|
{
|
|
auto key = coo.substr(0, epos);
|
|
string_view::size_type cpos = 0;
|
|
while (cpos < key.length() && isspace(key[cpos]))
|
|
++cpos;
|
|
key = key.substr(cpos);
|
|
auto pvalue = coo.substr(epos + 1);
|
|
std::string pdecode = utils::urlDecode(pvalue);
|
|
std::string keydecode = utils::urlDecode(key);
|
|
parameters_[keydecode] = pdecode;
|
|
}
|
|
}
|
|
}
|
|
|
|
input = contentView();
|
|
if (input.empty())
|
|
return;
|
|
std::string type = getHeaderBy("content-type");
|
|
std::transform(type.begin(), type.end(), type.begin(), tolower);
|
|
if (type.empty() ||
|
|
type.find("application/x-www-form-urlencoded") != std::string::npos)
|
|
{
|
|
string_view::size_type pos = 0;
|
|
while ((input[pos] == '?' || isspace(input[pos])) &&
|
|
pos < input.length())
|
|
{
|
|
++pos;
|
|
}
|
|
auto value = input.substr(pos);
|
|
while ((pos = value.find('&')) != string_view::npos)
|
|
{
|
|
auto coo = value.substr(0, pos);
|
|
auto epos = coo.find('=');
|
|
if (epos != string_view::npos)
|
|
{
|
|
auto key = coo.substr(0, epos);
|
|
string_view::size_type cpos = 0;
|
|
while (cpos < key.length() && isspace(key[cpos]))
|
|
++cpos;
|
|
key = key.substr(cpos);
|
|
auto pvalue = coo.substr(epos + 1);
|
|
std::string pdecode = utils::urlDecode(pvalue);
|
|
std::string keydecode = utils::urlDecode(key);
|
|
parameters_[keydecode] = pdecode;
|
|
}
|
|
value = value.substr(pos + 1);
|
|
}
|
|
if (value.length() > 0)
|
|
{
|
|
auto &coo = value;
|
|
auto epos = coo.find('=');
|
|
if (epos != string_view::npos)
|
|
{
|
|
auto key = coo.substr(0, epos);
|
|
string_view::size_type cpos = 0;
|
|
while (cpos < key.length() && isspace(key[cpos]))
|
|
++cpos;
|
|
key = key.substr(cpos);
|
|
auto pvalue = coo.substr(epos + 1);
|
|
std::string pdecode = utils::urlDecode(pvalue);
|
|
std::string keydecode = utils::urlDecode(key);
|
|
parameters_[keydecode] = pdecode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpRequest::appendToBuffer(trantor::MsgBuffer *output) const
|
|
{
|
|
switch (method_)
|
|
{
|
|
case Get:
|
|
output->append("GET ");
|
|
break;
|
|
case Post:
|
|
output->append("POST ");
|
|
break;
|
|
case Head:
|
|
output->append("HEAD ");
|
|
break;
|
|
case Put:
|
|
output->append("PUT ");
|
|
break;
|
|
case Delete:
|
|
output->append("DELETE ");
|
|
break;
|
|
case Options:
|
|
output->append("OPTIONS ");
|
|
break;
|
|
case Patch:
|
|
output->append("PATCH ");
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (!path_.empty())
|
|
{
|
|
output->append(utils::urlEncode(path_));
|
|
}
|
|
else
|
|
{
|
|
output->append("/");
|
|
}
|
|
|
|
std::string content;
|
|
|
|
if (!passThrough_ &&
|
|
(!parameters_.empty() && contentType_ != CT_MULTIPART_FORM_DATA))
|
|
{
|
|
for (auto const &p : parameters_)
|
|
{
|
|
content.append(utils::urlEncodeComponent(p.first));
|
|
content.append("=");
|
|
content.append(utils::urlEncodeComponent(p.second));
|
|
content.append("&");
|
|
}
|
|
content.resize(content.length() - 1);
|
|
if (method_ == Get || method_ == Delete || method_ == Head)
|
|
{
|
|
auto ret = std::find(output->peek(),
|
|
(const char *)output->beginWrite(),
|
|
'?');
|
|
if (ret != output->beginWrite())
|
|
{
|
|
if (ret != output->beginWrite() - 1)
|
|
{
|
|
output->append("&");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
output->append("?");
|
|
}
|
|
output->append(content);
|
|
content.clear();
|
|
}
|
|
else if (contentType_ == CT_APPLICATION_JSON)
|
|
{
|
|
/// Can't set parameters in content in this case
|
|
LOG_ERROR
|
|
<< "You can't set parameters in the query string when the "
|
|
"request content type is JSON and http method "
|
|
"is POST or PUT";
|
|
LOG_ERROR << "Please put these parameters into the path or "
|
|
"into the json "
|
|
"string";
|
|
content.clear();
|
|
}
|
|
}
|
|
|
|
output->append(" ");
|
|
if (version_ == Version::kHttp11)
|
|
{
|
|
output->append("HTTP/1.1");
|
|
}
|
|
else if (version_ == Version::kHttp10)
|
|
{
|
|
output->append("HTTP/1.0");
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
output->append("\r\n");
|
|
|
|
if (!passThrough_ && contentType_ == CT_MULTIPART_FORM_DATA)
|
|
{
|
|
auto mReq = dynamic_cast<const HttpFileUploadRequest *>(this);
|
|
if (mReq)
|
|
{
|
|
for (auto ¶m : mReq->getParameters())
|
|
{
|
|
content.append("--");
|
|
content.append(mReq->boundary());
|
|
content.append("\r\n");
|
|
content.append("Content-Disposition: form-data; name=\"");
|
|
content.append(param.first);
|
|
content.append("\"\r\n\r\n");
|
|
content.append(param.second);
|
|
content.append("\r\n");
|
|
}
|
|
for (auto &file : mReq->files())
|
|
{
|
|
content.append("--");
|
|
content.append(mReq->boundary());
|
|
content.append("\r\n");
|
|
content.append("Content-Disposition: form-data; name=\"");
|
|
content.append(file.itemName());
|
|
content.append("\"; filename=\"");
|
|
content.append(file.fileName());
|
|
content.append("\"\r\n\r\n");
|
|
std::ifstream infile(file.path(), std::ifstream::binary);
|
|
if (!infile)
|
|
{
|
|
LOG_ERROR << file.path() << " not found";
|
|
}
|
|
else
|
|
{
|
|
std::streambuf *pbuf = infile.rdbuf();
|
|
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
|
|
pbuf->pubseekoff(0, infile.beg); // rewind
|
|
std::string str;
|
|
str.resize(filesize);
|
|
pbuf->sgetn(&str[0], filesize);
|
|
content.append(std::move(str));
|
|
}
|
|
content.append("\r\n");
|
|
}
|
|
content.append("--");
|
|
content.append(mReq->boundary());
|
|
content.append("--");
|
|
}
|
|
}
|
|
assert(!(!content.empty() && !content_.empty()));
|
|
if (!passThrough_ && (!content.empty() || !content_.empty()))
|
|
{
|
|
char buf[64];
|
|
auto len = snprintf(buf,
|
|
sizeof(buf),
|
|
contentLengthFormatString<decltype(content.length())>(),
|
|
content.length() + content_.length());
|
|
output->append(buf, len);
|
|
if (contentTypeString_.empty())
|
|
{
|
|
auto &type = webContentTypeToString(contentType_);
|
|
output->append(type.data(), type.length());
|
|
}
|
|
}
|
|
if (!passThrough_ && !contentTypeString_.empty())
|
|
{
|
|
output->append(contentTypeString_);
|
|
}
|
|
for (auto it = headers_.begin(); it != headers_.end(); ++it)
|
|
{
|
|
output->append(it->first);
|
|
output->append(": ");
|
|
output->append(it->second);
|
|
output->append("\r\n");
|
|
}
|
|
if (cookies_.size() > 0)
|
|
{
|
|
output->append("Cookie: ");
|
|
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
|
|
{
|
|
output->append(it->first);
|
|
output->append("= ");
|
|
output->append(it->second);
|
|
output->append(";");
|
|
}
|
|
output->unwrite(1); // delete last ';'
|
|
output->append("\r\n");
|
|
}
|
|
|
|
output->append("\r\n");
|
|
if (!content.empty())
|
|
output->append(content);
|
|
if (!content_.empty())
|
|
output->append(content_);
|
|
}
|
|
|
|
void HttpRequest::addHeader(const char *start,
|
|
const char *colon,
|
|
const char *end)
|
|
{
|
|
std::string field(start, colon);
|
|
// Field name is case-insensitive.so we transform it to lower;(rfc2616-4.2)
|
|
std::transform(field.begin(), field.end(), field.begin(), ::tolower);
|
|
++colon;
|
|
while (colon < end && isspace(*colon))
|
|
{
|
|
++colon;
|
|
}
|
|
std::string value(colon, end);
|
|
while (!value.empty() && isspace(value[value.size() - 1]))
|
|
{
|
|
value.resize(value.size() - 1);
|
|
}
|
|
if (field.length() == 6 && field == "cookie")
|
|
{
|
|
LOG_TRACE << "cookies!!!:" << value;
|
|
std::string::size_type pos;
|
|
while ((pos = value.find(';')) != std::string::npos)
|
|
{
|
|
std::string coo = value.substr(0, pos);
|
|
auto epos = coo.find('=');
|
|
if (epos != std::string::npos)
|
|
{
|
|
std::string cookie_name = coo.substr(0, epos);
|
|
std::string::size_type cpos = 0;
|
|
while (cpos < cookie_name.length() &&
|
|
isspace(cookie_name[cpos]))
|
|
++cpos;
|
|
cookie_name = cookie_name.substr(cpos);
|
|
std::string cookie_value = coo.substr(epos + 1);
|
|
cookies_[std::move(cookie_name)] = std::move(cookie_value);
|
|
}
|
|
value = value.substr(pos + 1);
|
|
}
|
|
if (value.length() > 0)
|
|
{
|
|
std::string &coo = value;
|
|
auto epos = coo.find('=');
|
|
if (epos != std::string::npos)
|
|
{
|
|
std::string cookie_name = coo.substr(0, epos);
|
|
std::string::size_type cpos = 0;
|
|
while (cpos < cookie_name.length() &&
|
|
isspace(cookie_name[cpos]))
|
|
++cpos;
|
|
cookie_name = cookie_name.substr(cpos);
|
|
std::string cookie_value = coo.substr(epos + 1);
|
|
cookies_[std::move(cookie_name)] = std::move(cookie_value);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (field.length())
|
|
{
|
|
case 6:
|
|
if (field == "expect")
|
|
{
|
|
expectPtr_ = std::make_unique<std::string>(value);
|
|
}
|
|
break;
|
|
case 10:
|
|
{
|
|
if (field == "connection")
|
|
{
|
|
if (version_ == Version::kHttp11)
|
|
{
|
|
if (value.length() == 5 && value == "close")
|
|
keepAlive_ = false;
|
|
}
|
|
else if (value.length() == 10 &&
|
|
(value == "Keep-Alive" || value == "keep-alive"))
|
|
{
|
|
keepAlive_ = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
headers_.emplace(std::move(field), std::move(value));
|
|
}
|
|
}
|
|
|
|
HttpRequestPtr HttpRequest::newHttpRequest()
|
|
{
|
|
auto req = std::make_shared<HttpRequest>(nullptr);
|
|
req->setMethod(drogon::Get);
|
|
req->setVersion(drogon::Version::kHttp11);
|
|
return req;
|
|
}
|
|
|
|
HttpRequestPtr HttpRequest::newHttpFormPostRequest()
|
|
{
|
|
auto req = std::make_shared<HttpRequest>(nullptr);
|
|
req->setMethod(drogon::Post);
|
|
req->setVersion(drogon::Version::kHttp11);
|
|
req->contentType_ = CT_APPLICATION_X_FORM;
|
|
req->flagForParsingContentType_ = true;
|
|
return req;
|
|
}
|
|
|
|
HttpRequestPtr HttpRequest::newHttpJsonRequest(const Json::Value &data)
|
|
{
|
|
static std::once_flag once;
|
|
static Json::StreamWriterBuilder builder;
|
|
std::call_once(once, []() {
|
|
builder["commentStyle"] = "None";
|
|
builder["indentation"] = "";
|
|
|
|
/*
|
|
if (!Application::get_instance()->isUnicodeEscapingUsedInJson())
|
|
{
|
|
builder["emitUTF8"] = true;
|
|
}*/
|
|
|
|
});
|
|
auto req = std::make_shared<HttpRequest>(nullptr);
|
|
req->setMethod(drogon::Get);
|
|
req->setVersion(drogon::Version::kHttp11);
|
|
req->contentType_ = CT_APPLICATION_JSON;
|
|
req->setContent(writeString(builder, data));
|
|
req->flagForParsingContentType_ = true;
|
|
return req;
|
|
}
|
|
|
|
HttpRequestPtr HttpRequest::newFileUploadRequest(
|
|
const std::vector<UploadFile> &files)
|
|
{
|
|
return std::make_shared<HttpFileUploadRequest>(files);
|
|
}
|
|
|
|
void HttpRequest::swap(HttpRequest &that) noexcept
|
|
{
|
|
using std::swap;
|
|
swap(method_, that.method_);
|
|
swap(version_, that.version_);
|
|
swap(flagForParsingJson_, that.flagForParsingJson_);
|
|
swap(flagForParsingParameters_, that.flagForParsingParameters_);
|
|
swap(matchedPathPattern_, that.matchedPathPattern_);
|
|
swap(path_, that.path_);
|
|
swap(query_, that.query_);
|
|
swap(headers_, that.headers_);
|
|
swap(cookies_, that.cookies_);
|
|
swap(parameters_, that.parameters_);
|
|
swap(jsonPtr_, that.jsonPtr_);
|
|
swap(sessionPtr_, that.sessionPtr_);
|
|
swap(attributesPtr_, that.attributesPtr_);
|
|
swap(cacheFilePtr_, that.cacheFilePtr_);
|
|
swap(peer_, that.peer_);
|
|
swap(local_, that.local_);
|
|
swap(creationDate_, that.creationDate_);
|
|
swap(content_, that.content_);
|
|
swap(expectPtr_, that.expectPtr_);
|
|
swap(contentType_, that.contentType_);
|
|
swap(contentTypeString_, that.contentTypeString_);
|
|
swap(keepAlive_, that.keepAlive_);
|
|
swap(loop_, that.loop_);
|
|
swap(flagForParsingContentType_, that.flagForParsingContentType_);
|
|
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
|
|
}
|
|
|
|
const char *HttpRequest::methodString() const
|
|
{
|
|
const char *result = "UNKNOWN";
|
|
switch (method_)
|
|
{
|
|
case Get:
|
|
result = "GET";
|
|
break;
|
|
case Post:
|
|
result = "POST";
|
|
break;
|
|
case Head:
|
|
result = "HEAD";
|
|
break;
|
|
case Put:
|
|
result = "PUT";
|
|
break;
|
|
case Delete:
|
|
result = "DELETE";
|
|
break;
|
|
case Options:
|
|
result = "OPTIONS";
|
|
break;
|
|
case Patch:
|
|
result = "PATCH";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool HttpRequest::setMethod(const char *start, const char *end)
|
|
{
|
|
assert(method_ == Invalid);
|
|
string_view m(start, end - start);
|
|
switch (m.length())
|
|
{
|
|
case 3:
|
|
if (m == "GET")
|
|
{
|
|
method_ = Get;
|
|
}
|
|
else if (m == "PUT")
|
|
{
|
|
method_ = Put;
|
|
}
|
|
else
|
|
{
|
|
method_ = Invalid;
|
|
}
|
|
break;
|
|
case 4:
|
|
if (m == "POST")
|
|
{
|
|
method_ = Post;
|
|
}
|
|
else if (m == "HEAD")
|
|
{
|
|
method_ = Head;
|
|
}
|
|
else
|
|
{
|
|
method_ = Invalid;
|
|
}
|
|
break;
|
|
case 5:
|
|
if (m == "PATCH")
|
|
{
|
|
method_ = Patch;
|
|
}
|
|
else
|
|
{
|
|
method_ = Invalid;
|
|
}
|
|
break;
|
|
case 6:
|
|
if (m == "DELETE")
|
|
{
|
|
method_ = Delete;
|
|
}
|
|
else
|
|
{
|
|
method_ = Invalid;
|
|
}
|
|
break;
|
|
case 7:
|
|
if (m == "OPTIONS")
|
|
{
|
|
method_ = Options;
|
|
}
|
|
else
|
|
{
|
|
method_ = Invalid;
|
|
}
|
|
break;
|
|
default:
|
|
method_ = Invalid;
|
|
break;
|
|
}
|
|
|
|
// if (method_ != Invalid)
|
|
// {
|
|
// content_ = "";
|
|
// query_ = "";
|
|
// cookies_.clear();
|
|
// parameters_.clear();
|
|
// headers_.clear();
|
|
// }
|
|
return method_ != Invalid;
|
|
}
|
|
|
|
HttpRequest::~HttpRequest()
|
|
{
|
|
}
|
|
|
|
void HttpRequest::reserveBodySize(size_t length)
|
|
{
|
|
if (length <= Application::get_instance()->getClientMaxMemoryBodySize())
|
|
{
|
|
content_.reserve(length);
|
|
}
|
|
else
|
|
{
|
|
// Store data of body to a temperary file
|
|
createTmpFile();
|
|
}
|
|
}
|
|
|
|
void HttpRequest::appendToBody(const char *data, size_t length)
|
|
{
|
|
if (cacheFilePtr_)
|
|
{
|
|
cacheFilePtr_->append(data, length);
|
|
}
|
|
else
|
|
{
|
|
if (content_.length() + length <=
|
|
Application::get_instance()->getClientMaxMemoryBodySize())
|
|
{
|
|
content_.append(data, length);
|
|
}
|
|
else
|
|
{
|
|
createTmpFile();
|
|
cacheFilePtr_->append(content_);
|
|
cacheFilePtr_->append(data, length);
|
|
content_.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpRequest::createTmpFile()
|
|
{
|
|
/*
|
|
auto tmpfile = Application::get_instance()->getUploadPath();
|
|
|
|
|
|
auto fileName = utils::getUuid();
|
|
tmpfile.append("/tmp/")
|
|
.append(1, fileName[0])
|
|
.append(1, fileName[1])
|
|
.append("/")
|
|
.append(fileName);
|
|
cacheFilePtr_ = std::make_unique<CacheFile>(tmpfile);
|
|
*/
|
|
}
|