/** * * @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 #include "core/application.h" #include #include #include #include #include #include #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(k200OK, CT_TEXT_HTML); doResponseCreateAdvices(res); return res; } HttpResponsePtr HttpResponse::newHttpJsonResponse(const Json::Value &data) { auto res = std::make_shared(k200OK, CT_APPLICATION_JSON); res->setJsonObject(data); doResponseCreateAdvices(res); return res; } HttpResponsePtr HttpResponse::newHttpJsonResponse(Json::Value &&data) { auto res = std::make_shared(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( 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(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 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(); 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(); 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(), bodyLength); } else { struct stat filestat; if (stat(sendfileName_.data(), &filestat) < 0) { LOG_SYSERR << sendfileName_ << " stat error"; return; } len = snprintf(buffer.beginWrite(), buffer.writableBytes(), contentLengthFormatString(), 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 HttpResponse::renderToBuffer() { if (expriedTime_ >= 0) { if (!passThrough_ && Application::get_instance()->sendDateHeader()) { if (datePos_ != static_cast(-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(*httpString_); memcpy((void *)&(*httpString_)[datePos_], newDate, httpFullDateStringLength); return httpString_; } return httpString_; } } else { if (httpString_) return httpString_; } } auto httpString = std::make_shared(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 HttpResponse:: renderHeaderForHeadMethod() { auto httpString = std::make_shared(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!!!:"< reader(builder.newCharReader()); if (bodyPtr_) { jsonPtr_ = std::make_shared(); 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::move(errs)); } else { jsonParsingErrorPtr_.reset(); } } else { jsonPtr_.reset(); jsonParsingErrorPtr_ = std::make_shared("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; }