rcpp_framework/core/http_response.cpp
2021-02-03 19:43:54 +01:00

572 lines
15 KiB
C++

/**
*
* @file HttpResponse.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 "core/http_response.h"
//#include "HttpAppFrameworkImpl.h"
#include "core/http_utils.h"
#include <core/http_view_data.h>
#include "core/application.h"
#include <core/io_thread_storage.h>
#include <stdio.h>
#include <sys/stat.h>
#include <trantor/utils/Logger.h>
#include <fstream>
#include <memory>
#ifdef _WIN32
#define stat _stati64
#endif
using namespace trantor;
using namespace drogon;
namespace drogon {
// "Fri, 23 Aug 2019 12:58:03 GMT" length = 29
static const size_t httpFullDateStringLength = 29;
static inline void doResponseCreateAdvices(
const HttpResponsePtr &responsePtr) {
/*
auto &advices =
Application::get_instance()->getResponseCreationAdvices();
if (!advices.empty())
{
for (auto &advice : advices)
{
advice(responsePtr);
}
}*/
}
static inline HttpResponsePtr genHttpResponse(std::string viewName,
const HttpViewData &data) {
/*
auto templ = DrTemplateBase::newTemplate(viewName);
if (templ)
{
auto res = HttpResponse::newHttpResponse();
res->setBody(templ->genText(data));
return res;
}*/
return drogon::HttpResponse::newNotFoundResponse();
}
} // namespace drogon
HttpResponsePtr HttpResponse::newHttpResponse() {
auto res = std::make_shared<HttpResponse>(k200OK, CT_TEXT_HTML);
doResponseCreateAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpJsonResponse(const Json::Value &data) {
auto res = std::make_shared<HttpResponse>(k200OK, CT_APPLICATION_JSON);
res->setJsonObject(data);
doResponseCreateAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpJsonResponse(Json::Value &&data) {
auto res = std::make_shared<HttpResponse>(k200OK, CT_APPLICATION_JSON);
res->setJsonObject(std::move(data));
doResponseCreateAdvices(res);
return res;
}
void HttpResponse::generateBodyFromJson() const {
if (!jsonPtr_ || flagForSerializingJson_) {
return;
}
flagForSerializingJson_ = true;
static std::once_flag once;
static Json::StreamWriterBuilder builder;
std::call_once(once, []() {
builder["commentStyle"] = "None";
builder["indentation"] = "";
/*
if (!app().isUnicodeEscapingUsedInJson())
{
builder["emitUTF8"] = true;
}*/
});
bodyPtr_ = std::make_shared<HttpMessageStringBody>(
writeString(builder, *jsonPtr_));
}
HttpResponsePtr HttpResponse::newNotFoundResponse() {
auto loop = trantor::EventLoop::getEventLoopOfCurrentThread();
/*
auto &resp = Application::get_instance()->getCustom404Page();
if (resp) {
if (loop && loop->index() < Application::get_instance()->threadNum_) {
return resp;
} else {
return HttpResponsePtr{ new HttpResponse(
*static_cast<HttpResponse *>(resp.get())) };
}
} else {
if (loop && loop->index() < Application::get_instance()->threadNum_) {
// If the current thread is an IO thread
static std::once_flag threadOnce;
static IOThreadStorage<HttpResponsePtr> thread404Pages;
std::call_once(threadOnce, [] {
thread404Pages.init(
[](drogon::HttpResponsePtr &resp, size_t index) {
if (Application::get_instance()->isUsingCustomErrorHandler()) {
//resp = app().getCustomErrorHandler()(k404NotFound);
//resp->setExpiredTime(0);
} else {
HttpViewData data;
data.insert("version", drogon::getVersion());
resp = HttpResponse::newHttpViewResponse(
"drogon::NotFound", data);
resp->setStatusCode(k404NotFound);
resp->setExpiredTime(0);
}
});
});
LOG_TRACE << "Use cached 404 response";
return thread404Pages.getThreadData();
} else {
//if (Application::get_instance()->isUsingCustomErrorHandler())
//{
//auto resp = app().getCustomErrorHandler()(k404NotFound);
// return resp;
// }
HttpViewData data;
data.insert("version", drogon::getVersion());
auto notFoundResp =
HttpResponse::newHttpViewResponse("drogon::NotFound", data);
notFoundResp->setStatusCode(k404NotFound);
return notFoundResp;
}
}
*/
//temp
HttpViewData data;
//data.insert("version", drogon::getVersion());
auto notFoundResp =
HttpResponse::newHttpViewResponse("drogon::NotFound", data);
notFoundResp->setStatusCode(k404NotFound);
return notFoundResp;
}
HttpResponsePtr HttpResponse::newRedirectionResponse(
const std::string &location,
HttpStatusCode status) {
auto res = std::make_shared<HttpResponse>();
res->setStatusCode(status);
res->redirect(location);
doResponseCreateAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpViewResponse(const std::string &viewName,
const HttpViewData &data) {
return genHttpResponse(viewName, data);
}
HttpResponsePtr HttpResponse::newFileResponse(
const std::string &fullPath,
const std::string &attachmentFileName,
ContentType type) {
std::ifstream infile(fullPath, std::ifstream::binary);
LOG_TRACE << "send http file:" << fullPath;
if (!infile) {
auto resp = HttpResponse::newNotFoundResponse();
return resp;
}
auto resp = std::make_shared<HttpResponse>();
std::streambuf *pbuf = infile.rdbuf();
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
pbuf->pubseekoff(0, infile.beg); // rewind
/*
if (Application::get_instance()->useSendfile() && filesize > 1024 * 200)
// TODO : Is 200k an appropriate value? Or set it to be configurable
{
// The advantages of sendfile() can only be reflected in sending large
// files.
resp->setSendfile(fullPath);
} else {
std::string str;
str.resize(filesize);
pbuf->sgetn(&str[0], filesize);
resp->setBody(std::move(str));
}
*/
resp->setStatusCode(k200OK);
if (type == CT_NONE) {
if (!attachmentFileName.empty()) {
resp->setContentTypeCode(
drogon::getContentType(attachmentFileName));
} else {
resp->setContentTypeCode(drogon::getContentType(fullPath));
}
} else {
resp->setContentTypeCode(type);
}
if (!attachmentFileName.empty()) {
resp->addHeader("Content-Disposition",
"attachment; filename=" + attachmentFileName);
}
doResponseCreateAdvices(resp);
return resp;
}
void HttpResponse::makeHeaderString(trantor::MsgBuffer &buffer) {
buffer.ensureWritableBytes(128);
int len{ 0 };
if (version_ == Version::kHttp11) {
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.1 %d ",
statusCode_);
} else {
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
"HTTP/1.0 %d ",
statusCode_);
}
buffer.hasWritten(len);
if (!statusMessage_.empty())
buffer.append(statusMessage_.data(), statusMessage_.length());
buffer.append("\r\n");
generateBodyFromJson();
if (!passThrough_) {
buffer.ensureWritableBytes(64);
if (sendfileName_.empty()) {
auto bodyLength = bodyPtr_ ? bodyPtr_->length() : 0;
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
contentLengthFormatString<decltype(bodyLength)>(),
bodyLength);
} else {
struct stat filestat;
if (stat(sendfileName_.data(), &filestat) < 0) {
LOG_SYSERR << sendfileName_ << " stat error";
return;
}
len = snprintf(buffer.beginWrite(),
buffer.writableBytes(),
contentLengthFormatString<decltype(filestat.st_size)>(),
filestat.st_size);
}
buffer.hasWritten(len);
if (headers_.find("Connection") == headers_.end()) {
if (closeConnection_) {
buffer.append("Connection: close\r\n");
} else if (version_ == Version::kHttp10) {
buffer.append("Connection: Keep-Alive\r\n");
}
}
buffer.append(contentTypeString_.data(), contentTypeString_.length());
if (Application::get_instance()->sendServerHeader()) {
buffer.append(Application::get_instance()->getServerHeaderString());
}
}
for (auto it = headers_.begin(); it != headers_.end(); ++it) {
buffer.append(it->first);
buffer.append(": ");
buffer.append(it->second);
buffer.append("\r\n");
}
}
void HttpResponse::renderToBuffer(trantor::MsgBuffer &buffer) {
if (expriedTime_ >= 0) {
auto strPtr = renderToBuffer();
buffer.append(strPtr->peek(), strPtr->readableBytes());
return;
}
if (!fullHeaderString_) {
makeHeaderString(buffer);
} else {
buffer.append(*fullHeaderString_);
}
// output cookies
if (cookies_.size() > 0) {
for (auto it = cookies_.begin(); it != cookies_.end(); ++it) {
buffer.append(it->second.cookieString());
}
}
// output Date header
if (!passThrough_ && Application::get_instance()->sendDateHeader()) {
buffer.append("Date: ");
buffer.append(utils::getHttpFullDate(trantor::Date::date()),
httpFullDateStringLength);
buffer.append("\r\n\r\n");
} else {
buffer.append("\r\n");
}
if (bodyPtr_)
buffer.append(bodyPtr_->data(), bodyPtr_->length());
}
std::shared_ptr<trantor::MsgBuffer> HttpResponse::renderToBuffer() {
if (expriedTime_ >= 0) {
if (!passThrough_ && Application::get_instance()->sendDateHeader()) {
if (datePos_ != static_cast<size_t>(-1)) {
auto now = trantor::Date::now();
bool isDateChanged =
((now.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC) !=
httpStringDate_);
assert(httpString_);
if (isDateChanged) {
httpStringDate_ =
now.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC;
auto newDate = utils::getHttpFullDate(now);
httpString_ =
std::make_shared<trantor::MsgBuffer>(*httpString_);
memcpy((void *)&(*httpString_)[datePos_],
newDate,
httpFullDateStringLength);
return httpString_;
}
return httpString_;
}
} else {
if (httpString_)
return httpString_;
}
}
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
if (!fullHeaderString_) {
makeHeaderString(*httpString);
} else {
httpString->append(*fullHeaderString_);
}
// output cookies
if (cookies_.size() > 0) {
for (auto it = cookies_.begin(); it != cookies_.end(); ++it) {
httpString->append(it->second.cookieString());
}
}
// output Date header
if (!passThrough_ &&
Application::get_instance()->sendDateHeader()) {
httpString->append("Date: ");
auto datePos = httpString->readableBytes();
httpString->append(utils::getHttpFullDate(trantor::Date::date()),
httpFullDateStringLength);
httpString->append("\r\n\r\n");
datePos_ = datePos;
} else {
httpString->append("\r\n");
}
LOG_TRACE << "reponse(no body):"
<< string_view{ httpString->peek(), httpString->readableBytes() };
if (bodyPtr_)
httpString->append(bodyPtr_->data(), bodyPtr_->length());
if (expriedTime_ >= 0) {
httpString_ = httpString;
}
return httpString;
}
std::shared_ptr<trantor::MsgBuffer> HttpResponse::
renderHeaderForHeadMethod() {
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
if (!fullHeaderString_) {
makeHeaderString(*httpString);
} else {
httpString->append(*fullHeaderString_);
}
// output cookies
if (cookies_.size() > 0) {
for (auto it = cookies_.begin(); it != cookies_.end(); ++it) {
httpString->append(it->second.cookieString());
}
}
// output Date header
if (!passThrough_ &&
Application::get_instance()->sendDateHeader()) {
httpString->append("Date: ");
httpString->append(utils::getHttpFullDate(trantor::Date::date()),
httpFullDateStringLength);
httpString->append("\r\n\r\n");
} else {
httpString->append("\r\n");
}
return httpString;
}
void HttpResponse::addHeader(const char *start,
const char *colon,
const char *end) {
fullHeaderString_.reset();
std::string field(start, colon);
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 == "set-cookie") {
// LOG_INFO<<"cookies!!!:"<<value;
auto values = utils::splitString(value, ";");
Cookie cookie;
cookie.setHttpOnly(false);
for (size_t i = 0; i < values.size(); ++i) {
std::string &coo = values[i];
std::string cookie_name;
std::string cookie_value;
auto epos = coo.find('=');
if (epos != std::string::npos) {
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);
++epos;
while (epos < coo.length() && isspace(coo[epos]))
++epos;
cookie_value = coo.substr(epos);
} else {
std::string::size_type cpos = 0;
while (cpos < coo.length() && isspace(coo[cpos]))
++cpos;
cookie_name = coo.substr(cpos);
}
if (i == 0) {
cookie.setKey(cookie_name);
cookie.setValue(cookie_value);
} else {
std::transform(cookie_name.begin(),
cookie_name.end(),
cookie_name.begin(),
tolower);
if (cookie_name == "path") {
cookie.setPath(cookie_value);
} else if (cookie_name == "domain") {
cookie.setDomain(cookie_value);
} else if (cookie_name == "expires") {
cookie.setExpiresDate(utils::getHttpDate(cookie_value));
} else if (cookie_name == "secure") {
cookie.setSecure(true);
} else if (cookie_name == "httponly") {
cookie.setHttpOnly(true);
}
}
}
if (!cookie.key().empty()) {
cookies_[cookie.key()] = cookie;
}
} else {
headers_[std::move(field)] = std::move(value);
}
}
void HttpResponse::swap(HttpResponse &that) noexcept {
using std::swap;
headers_.swap(that.headers_);
cookies_.swap(that.cookies_);
swap(statusCode_, that.statusCode_);
swap(version_, that.version_);
swap(statusMessage_, that.statusMessage_);
swap(closeConnection_, that.closeConnection_);
bodyPtr_.swap(that.bodyPtr_);
swap(contentType_, that.contentType_);
swap(flagForParsingContentType_, that.flagForParsingContentType_);
swap(flagForParsingJson_, that.flagForParsingJson_);
swap(sendfileName_, that.sendfileName_);
jsonPtr_.swap(that.jsonPtr_);
fullHeaderString_.swap(that.fullHeaderString_);
httpString_.swap(that.httpString_);
swap(datePos_, that.datePos_);
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
}
void HttpResponse::clear() {
statusCode_ = kUnknown;
version_ = Version::kHttp11;
statusMessage_ = string_view{};
fullHeaderString_.reset();
jsonParsingErrorPtr_.reset();
sendfileName_.clear();
headers_.clear();
cookies_.clear();
bodyPtr_.reset();
jsonPtr_.reset();
expriedTime_ = -1;
datePos_ = std::string::npos;
flagForParsingContentType_ = false;
flagForParsingJson_ = false;
}
void HttpResponse::parseJson() const {
static std::once_flag once;
static Json::CharReaderBuilder builder;
std::call_once(once, []() { builder["collectComments"] = false; });
JSONCPP_STRING errs;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
if (bodyPtr_) {
jsonPtr_ = std::make_shared<Json::Value>();
if (!reader->parse(bodyPtr_->data(),
bodyPtr_->data() + bodyPtr_->length(),
jsonPtr_.get(),
&errs)) {
LOG_ERROR << errs;
LOG_ERROR << "body: " << bodyPtr_->getString();
jsonPtr_.reset();
jsonParsingErrorPtr_ =
std::make_shared<std::string>(std::move(errs));
} else {
jsonParsingErrorPtr_.reset();
}
} else {
jsonPtr_.reset();
jsonParsingErrorPtr_ =
std::make_shared<std::string>("empty response body");
}
}
HttpResponse::~HttpResponse() {
}
bool HttpResponse::shouldBeCompressed() const {
if (!sendfileName_.empty() ||
contentType() >= CT_APPLICATION_OCTET_STREAM ||
getBody().length() < 1024 || !(getHeaderBy("content-encoding").empty())) {
return false;
}
return true;
}