mirror of
https://github.com/Relintai/rcpp_framework.git
synced 2024-11-14 04:57:21 +01:00
572 lines
15 KiB
C++
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;
|
|
}
|