diff --git a/SConstruct b/SConstruct index 728a6f8..49f06a9 100644 --- a/SConstruct +++ b/SConstruct @@ -138,6 +138,7 @@ env_base.Prepend(CPPPATH=["#libs/trantor/trantor/net/inner"]) env_base.Prepend(CPPPATH=["#libs/trantor/trantor/utils"]) env_base.Prepend(LINKFLAGS=["-lpthread"]) +env_base.Append(CXX=["-std=c++17"]) env_base.Append(CXX=["-o3"]) #env_base.Append(CXX=["-g"]) #env_base.Append(CXX=["-g2"]) diff --git a/core/HttpFileImpl.cc b/core/HttpFileImpl.cc new file mode 100644 index 0000000..18921c2 --- /dev/null +++ b/core/HttpFileImpl.cc @@ -0,0 +1,161 @@ +/** + * + * HttpFileImpl.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 "HttpFileImpl.h" +#include "HttpAppFrameworkImpl.h" +#include +#include +#include + +using namespace drogon; + +int HttpFileImpl::save(const std::string &path) const +{ + assert(!path.empty()); + if (fileName_ == "") + return -1; + std::string filename; + auto tmpPath = path; + if (path[0] == '/' || + (path.length() >= 2 && path[0] == '.' && path[1] == '/') || + (path.length() >= 3 && path[0] == '.' && path[1] == '.' && + path[2] == '/') || + path == "." || path == "..") + { + // Absolute or relative path + } + else + { + auto &uploadPath = HttpAppFrameworkImpl::instance().getUploadPath(); + if (uploadPath[uploadPath.length() - 1] == '/') + tmpPath = uploadPath + path; + else + tmpPath = uploadPath + "/" + path; + } + + if (utils::createPath(tmpPath) < 0) + return -1; + + if (tmpPath[tmpPath.length() - 1] != '/') + { + filename = tmpPath + "/"; + filename.append(fileName_.data(), fileName_.length()); + } + else + filename = tmpPath.append(fileName_.data(), fileName_.length()); + + return saveTo(filename); +} +int HttpFileImpl::save() const +{ + return save(HttpAppFrameworkImpl::instance().getUploadPath()); +} +int HttpFileImpl::saveAs(const std::string &filename) const +{ + assert(!filename.empty()); + auto pathAndFileName = filename; + if (filename[0] == '/' || + (filename.length() >= 2 && filename[0] == '.' && filename[1] == '/') || + (filename.length() >= 3 && filename[0] == '.' && filename[1] == '.' && + filename[2] == '/')) + { + // Absolute or relative path + } + else + { + auto &uploadPath = HttpAppFrameworkImpl::instance().getUploadPath(); + if (uploadPath[uploadPath.length() - 1] == '/') + pathAndFileName = uploadPath + filename; + else + pathAndFileName = uploadPath + "/" + filename; + } + auto pathPos = pathAndFileName.rfind('/'); + if (pathPos != std::string::npos) + { + std::string path = pathAndFileName.substr(0, pathPos); + if (utils::createPath(path) < 0) + return -1; + } + return saveTo(pathAndFileName); +} +int HttpFileImpl::saveTo(const std::string &pathAndFilename) const +{ + LOG_TRACE << "save uploaded file:" << pathAndFilename; + std::ofstream file(pathAndFilename, std::ios::binary); + if (file.is_open()) + { + file.write(fileContent_.data(), fileContent_.size()); + file.close(); + return 0; + } + else + { + LOG_ERROR << "save failed!"; + return -1; + } +} +std::string HttpFileImpl::getMd5() const +{ + return utils::getMd5(fileContent_.data(), fileContent_.size()); +} + +const std::string &HttpFile::getFileName() const +{ + return implPtr_->getFileName(); +} + +void HttpFile::setFileName(const std::string &filename) +{ + implPtr_->setFileName(filename); +} + +void HttpFile::setFile(const char *data, size_t length) +{ + implPtr_->setFile(data, length); +} + +int HttpFile::save() const +{ + return implPtr_->save(); +} + +int HttpFile::save(const std::string &path) const +{ + return implPtr_->save(path); +} + +int HttpFile::saveAs(const std::string &filename) const +{ + return implPtr_->saveAs(filename); +} + +size_t HttpFile::fileLength() const noexcept +{ + return implPtr_->fileLength(); +} + +const char *HttpFile::fileData() const noexcept +{ + return implPtr_->fileData(); +} + +std::string HttpFile::getMd5() const +{ + return implPtr_->getMd5(); +} + +HttpFile::HttpFile(std::shared_ptr &&implPtr) + : implPtr_(std::move(implPtr)) +{ +} diff --git a/core/HttpFileImpl.h b/core/HttpFileImpl.h new file mode 100644 index 0000000..8e0cb36 --- /dev/null +++ b/core/HttpFileImpl.h @@ -0,0 +1,100 @@ +/** + * + * HttpFileImpl.h + * 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 + * + */ + +#pragma once +#include +#include + +#include +#include +#include +#include +namespace drogon +{ +class HttpFileImpl +{ + public: + /// Return the file name; + const std::string &getFileName() const + { + return fileName_; + }; + + /// Set the file name, usually called by the MultiPartParser parser. + void setFileName(const std::string &filename) + { + fileName_ = filename; + }; + + /// Set the contents of the file, usually called by the MultiPartParser + /// parser. + void setFile(const char *data, size_t length) + { + fileContent_ = string_view{data, length}; + }; + + /// Save the file to the file system. + /** + * The folder saving the file is app().getUploadPath(). + * The full path is app().getUploadPath()+"/"+this->getFileName() + */ + int save() const; + + /// Save the file to @param path + /** + * @param path if the parameter is prefixed with "/", "./" or "../", or is + * "." or "..", the full path is path+"/"+this->getFileName(), + * otherwise the file is saved as + * app().getUploadPath()+"/"+path+"/"+this->getFileName() + */ + int save(const std::string &path) const; + + /// Save the file to file system with a new name + /** + * @param filename if the parameter isn't prefixed with "/", "./" or "../", + * the full path is app().getUploadPath()+"/"+filename, otherwise the file + * is saved as the filename + */ + int saveAs(const std::string &filename) const; + + /// Return the file length. + size_t fileLength() const noexcept + { + return fileContent_.length(); + }; + + const char *fileData() const noexcept + { + return fileContent_.data(); + } + + const string_view &fileContent() const noexcept + { + return fileContent_; + } + + /// Return the md5 string of the file + std::string getMd5() const; + int saveTo(const std::string &pathAndFilename) const; + void setRequest(const HttpRequestPtr &req) + { + requestPtr_ = req; + } + + private: + std::string fileName_; + string_view fileContent_; + HttpRequestPtr requestPtr_; +}; +} // namespace drogon \ No newline at end of file diff --git a/core/HttpResponseParser.cc b/core/HttpResponseParser.cc new file mode 100644 index 0000000..ce2cba5 --- /dev/null +++ b/core/HttpResponseParser.cc @@ -0,0 +1,292 @@ +/** + * + * HttpResponseParser.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 "HttpResponseParser.h" +#include "HttpResponseImpl.h" +#include +#include +#include +using namespace trantor; +using namespace drogon; + +void HttpResponseParser::reset() +{ + status_ = HttpResponseParseStatus::kExpectResponseLine; + responsePtr_.reset(new HttpResponseImpl); + parseResponseForHeadMethod_ = false; + leftBodyLength_ = 0; + currentChunkLength_ = 0; +} + +HttpResponseParser::HttpResponseParser() + : status_(HttpResponseParseStatus::kExpectResponseLine), + responsePtr_(new HttpResponseImpl) +{ +} + +bool HttpResponseParser::processResponseLine(const char *begin, const char *end) +{ + const char *start = begin; + const char *space = std::find(start, end, ' '); + if (space != end) + { + LOG_TRACE << *(space - 1); + if (*(space - 1) == '1') + { + responsePtr_->setVersion(Version::kHttp11); + } + else if (*(space - 1) == '0') + { + responsePtr_->setVersion(Version::kHttp10); + } + else + { + return false; + } + } + + start = space + 1; + space = std::find(start, end, ' '); + if (space != end) + { + std::string status_code(start, space - start); + std::string status_message(space + 1, end - space - 1); + LOG_TRACE << status_code << " " << status_message; + auto code = atoi(status_code.c_str()); + responsePtr_->setStatusCode(HttpStatusCode(code)); + + return true; + } + return false; +} + +// return false if any error +bool HttpResponseParser::parseResponse(MsgBuffer *buf) +{ + bool ok = true; + bool hasMore = true; + while (hasMore) + { + if (status_ == HttpResponseParseStatus::kExpectResponseLine) + { + const char *crlf = buf->findCRLF(); + if (crlf) + { + ok = processResponseLine(buf->peek(), crlf); + if (ok) + { + // responsePtr_->setReceiveTime(receiveTime); + buf->retrieveUntil(crlf + 2); + status_ = HttpResponseParseStatus::kExpectHeaders; + } + else + { + hasMore = false; + } + } + else + { + hasMore = false; + } + } + else if (status_ == HttpResponseParseStatus::kExpectHeaders) + { + const char *crlf = buf->findCRLF(); + if (crlf) + { + const char *colon = std::find(buf->peek(), crlf, ':'); + if (colon != crlf) + { + responsePtr_->addHeader(buf->peek(), colon, crlf); + } + else + { + const std::string &len = + responsePtr_->getHeaderBy("content-length"); + // LOG_INFO << "content len=" << len; + if (!len.empty()) + { + leftBodyLength_ = atoi(len.c_str()); + status_ = HttpResponseParseStatus::kExpectBody; + } + else + { + const std::string &encode = + responsePtr_->getHeaderBy("transfer-encoding"); + if (encode == "chunked") + { + status_ = HttpResponseParseStatus::kExpectChunkLen; + hasMore = true; + } + else + { + if (responsePtr_->statusCode() == k204NoContent || + (responsePtr_->statusCode() == + k101SwitchingProtocols && + responsePtr_->getHeaderBy("upgrade") == + "websocket")) + { + // The Websocket response may not have a + // content-length header. + status_ = HttpResponseParseStatus::kGotAll; + hasMore = false; + } + else + { + status_ = HttpResponseParseStatus::kExpectClose; + hasMore = true; + } + } + } + if (parseResponseForHeadMethod_) + { + leftBodyLength_ = 0; + status_ = HttpResponseParseStatus::kGotAll; + hasMore = false; + } + } + buf->retrieveUntil(crlf + 2); + } + else + { + hasMore = false; + } + } + else if (status_ == HttpResponseParseStatus::kExpectBody) + { + // LOG_INFO << "expectBody:len=" << request_->contentLen; + // LOG_INFO << "expectBody:buf=" << buf; + if (buf->readableBytes() == 0) + { + if (leftBodyLength_ == 0) + { + status_ = HttpResponseParseStatus::kGotAll; + } + break; + } + if (!responsePtr_->bodyPtr_) + { + responsePtr_->bodyPtr_ = + std::make_shared(); + } + if (leftBodyLength_ >= buf->readableBytes()) + { + leftBodyLength_ -= buf->readableBytes(); + + responsePtr_->bodyPtr_->append(buf->peek(), + buf->readableBytes()); + buf->retrieveAll(); + } + else + { + responsePtr_->bodyPtr_->append(buf->peek(), leftBodyLength_); + buf->retrieve(leftBodyLength_); + leftBodyLength_ = 0; + } + if (leftBodyLength_ == 0) + { + status_ = HttpResponseParseStatus::kGotAll; + LOG_TRACE << "post got all:len=" << leftBodyLength_; + // LOG_INFO<<"content:"<content_; + LOG_TRACE << "content(END)"; + hasMore = false; + } + } + else if (status_ == HttpResponseParseStatus::kExpectClose) + { + if (!responsePtr_->bodyPtr_) + { + responsePtr_->bodyPtr_ = + std::make_shared(); + } + responsePtr_->bodyPtr_->append(buf->peek(), buf->readableBytes()); + buf->retrieveAll(); + break; + } + else if (status_ == HttpResponseParseStatus::kExpectChunkLen) + { + const char *crlf = buf->findCRLF(); + if (crlf) + { + // chunk length line + std::string len(buf->peek(), crlf - buf->peek()); + char *end; + currentChunkLength_ = strtol(len.c_str(), &end, 16); + // LOG_TRACE << "chun length : " << + // currentChunkLength_; + if (currentChunkLength_ != 0) + { + status_ = HttpResponseParseStatus::kExpectChunkBody; + } + else + { + status_ = HttpResponseParseStatus::kExpectLastEmptyChunk; + } + buf->retrieveUntil(crlf + 2); + } + else + { + hasMore = false; + } + } + else if (status_ == HttpResponseParseStatus::kExpectChunkBody) + { + // LOG_TRACE<<"expect chunk + // len="<readableBytes() >= (currentChunkLength_ + 2)) + { + if (*(buf->peek() + currentChunkLength_) == '\r' && + *(buf->peek() + currentChunkLength_ + 1) == '\n') + { + if (!responsePtr_->bodyPtr_) + { + responsePtr_->bodyPtr_ = + std::make_shared(); + } + responsePtr_->bodyPtr_->append(buf->peek(), + currentChunkLength_); + buf->retrieve(currentChunkLength_ + 2); + currentChunkLength_ = 0; + status_ = HttpResponseParseStatus::kExpectChunkLen; + } + else + { + // error! + buf->retrieveAll(); + return false; + } + } + else + { + hasMore = false; + } + } + else if (status_ == HttpResponseParseStatus::kExpectLastEmptyChunk) + { + // last empty chunk + const char *crlf = buf->findCRLF(); + if (crlf) + { + buf->retrieveUntil(crlf + 2); + status_ = HttpResponseParseStatus::kGotAll; + break; + } + else + { + hasMore = false; + } + } + } + return ok; +} diff --git a/core/HttpResponseParser.h b/core/HttpResponseParser.h new file mode 100644 index 0000000..85e546d --- /dev/null +++ b/core/HttpResponseParser.h @@ -0,0 +1,75 @@ +/** + * + * HttpResponseParser.h + * 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 + * + */ + +#pragma once + +#include "impl_forwards.h" +#include +#include +#include +#include +#include + +namespace drogon +{ +class HttpResponseParser : public trantor::NonCopyable +{ + public: + enum class HttpResponseParseStatus + { + kExpectResponseLine, + kExpectHeaders, + kExpectBody, + kExpectChunkLen, + kExpectChunkBody, + kExpectLastEmptyChunk, + kExpectClose, + kGotAll, + }; + + HttpResponseParser(); + + // default copy-ctor, dtor and assignment are fine + + // return false if any error + bool parseResponse(trantor::MsgBuffer *buf); + + bool gotAll() const + { + return status_ == HttpResponseParseStatus::kGotAll; + } + + void setForHeadMethod() + { + parseResponseForHeadMethod_ = true; + } + + void reset(); + + const HttpResponseImplPtr &responseImpl() const + { + return responsePtr_; + } + + private: + bool processResponseLine(const char *begin, const char *end); + + HttpResponseParseStatus status_; + HttpResponseImplPtr responsePtr_; + bool parseResponseForHeadMethod_{false}; + size_t leftBodyLength_{0}; + size_t currentChunkLength_{0}; +}; + +} // namespace drogon diff --git a/core/SessionManager.cc b/core/SessionManager.cc new file mode 100644 index 0000000..00bd7fb --- /dev/null +++ b/core/SessionManager.cc @@ -0,0 +1,81 @@ +/** + * + * SessionManager.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 "SessionManager.h" +#include + +using namespace drogon; + +SessionManager::SessionManager(trantor::EventLoop *loop, size_t timeout) + : loop_(loop), timeout_(timeout) +{ + assert(timeout_ >= 0); + if (timeout_ > 0) + { + size_t wheelNum = 1; + size_t bucketNum = 0; + if (timeout_ < 500) + { + bucketNum = timeout_ + 1; + } + else + { + auto tmpTimeout = timeout_; + bucketNum = 100; + while (tmpTimeout > 100) + { + ++wheelNum; + tmpTimeout = tmpTimeout / 100; + } + } + sessionMapPtr_ = std::unique_ptr>( + new CacheMap( + loop_, 1.0, wheelNum, bucketNum)); + } + else if (timeout_ == 0) + { + sessionMapPtr_ = std::unique_ptr>( + new CacheMap(loop_, 0, 0, 0)); + } +} + +SessionPtr SessionManager::getSession(const std::string &sessionID, + bool needToSet) +{ + assert(!sessionID.empty()); + SessionPtr sessionPtr; + std::lock_guard lock(mapMutex_); + if (sessionMapPtr_->findAndFetch(sessionID, sessionPtr) == false) + { + sessionPtr = + std::shared_ptr(new Session(sessionID, needToSet)); + sessionMapPtr_->insert(sessionID, sessionPtr, timeout_); + return sessionPtr; + } + return sessionPtr; +} + +void SessionManager::changeSessionId(const SessionPtr &sessionPtr) +{ + auto oldId = sessionPtr->sessionId(); + auto newId = utils::getUuid(); + sessionPtr->setSessionId(newId); + sessionMapPtr_->insert(newId, sessionPtr, timeout_); + // For requests sent before setting the new session ID to the client, we + // reserve the old session slot for a period of time. + sessionMapPtr_->runAfter(10, [this, oldId = std::move(oldId)]() { + LOG_TRACE << "remove the old slot of the session"; + sessionMapPtr_->erase(oldId); + }); +} \ No newline at end of file diff --git a/core/SessionManager.h b/core/SessionManager.h new file mode 100644 index 0000000..2355594 --- /dev/null +++ b/core/SessionManager.h @@ -0,0 +1,44 @@ +/** + * + * SessionManager.h + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace drogon +{ +class SessionManager : public trantor::NonCopyable +{ + public: + SessionManager(trantor::EventLoop *loop, size_t timeout); + ~SessionManager() + { + sessionMapPtr_.reset(); + } + SessionPtr getSession(const std::string &sessionID, bool needToSet); + void changeSessionId(const SessionPtr &sessionPtr); + + private: + std::unique_ptr> sessionMapPtr_; + std::mutex mapMutex_; + trantor::EventLoop *loop_; + size_t timeout_; +}; +} // namespace drogon diff --git a/core/any.h b/core/any.h new file mode 100644 index 0000000..63abd2e --- /dev/null +++ b/core/any.h @@ -0,0 +1,31 @@ +/** + * + * any.h + * 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 + * + */ + +#pragma once +#if __cplusplus >= 201703L || (defined _MSC_VER && _MSC_VER > 1900) +#include +#else +#include +#endif + +namespace drogon +{ +#if __cplusplus >= 201703L || (defined _MSC_VER && _MSC_VER > 1900) +using std::any; +using std::any_cast; +#else +using boost::any; +using boost::any_cast; +#endif +} // namespace drogon diff --git a/core/application.cpp b/core/application.cpp index 2a7602b..acbd4d9 100644 --- a/core/application.cpp +++ b/core/application.cpp @@ -12,6 +12,12 @@ #include #include +#include + +#include "core/listener_manager.h" + +using namespace std::placeholders; + void Application::load_settings() { } @@ -119,8 +125,308 @@ void Application::send_file(const std::string &path, Request *request) { void Application::migrate() { } +//drogon -> void HttpAppFrameworkImpl::run() +void Application::run() { + + if (!get_loop()->isInLoopThread()) { + get_loop()->moveToCurrentThread(); + } + + LOG_TRACE << "Start to run..."; + + /* + trantor::AsyncFileLogger asyncFileLogger; + + // Create dirs for cache files + for (int i = 0; i < 256; ++i) { + char dirName[4]; + snprintf(dirName, sizeof(dirName), "%02x", i); + std::transform(dirName, dirName + 2, dirName, toupper); + utils::createPath(getUploadPath() + "/tmp/" + dirName); + } + */ + + /* + if (runAsDaemon_) { + // go daemon! + godaemon(); +#ifdef __linux__ + get_loop()->resetTimerQueue(); +#endif + get_loop()->resetAfterFork(); + } + */ + + /* + // set relaunching + if (relaunchOnError_) { +#ifndef _WIN32 + while (true) { + int child_status = 0; + auto child_pid = fork(); + if (child_pid < 0) { + LOG_ERROR << "fork error"; + abort(); + } else if (child_pid == 0) { + // child + break; + } + waitpid(child_pid, &child_status, 0); + sleep(1); + LOG_INFO << "start new process"; + } + get_loop()->resetAfterFork(); +#endif + }*/ + + //signal(SIGTERM, TERMFunction); + + /* + // set logger + if (!logPath_.empty()) { +#ifdef _WIN32 + if (_access(logPath_.c_str(), 06) != 0) +#else + if (access(logPath_.c_str(), R_OK | W_OK) != 0) +#endif + { + LOG_ERROR << "log file path not exist"; + abort(); + } else { + std::string baseName = logfileBaseName_; + if (baseName == "") { + baseName = "drogon"; + } + asyncFileLogger.setFileName(baseName, ".log", logPath_); + asyncFileLogger.startLogging(); + trantor::Logger::setOutputFunction( + [&](const char *msg, const uint64_t len) { + asyncFileLogger.output(msg, len); + }, + [&]() { asyncFileLogger.flush(); }); + asyncFileLogger.setFileSizeLimit(logfileSize_); + } + } +*/ + + /* + if (relaunchOnError_) { + LOG_INFO << "Start child process"; + } +*/ + + // now start runing!! + + _running = true; + + /* +#ifndef _WIN32 + if (!libFilePaths_.empty()) { + sharedLibManagerPtr_ = std::unique_ptr( + new SharedLibManager(libFilePaths_, libFileOutputPath_)); + } +#endif +*/ + + // Create all listeners. + /* + auto ioLoops = listenerManagerPtr_->createListeners( + std::bind(&Application::onAsyncRequest, this, _1, _2), + std::bind(&Application::onNewWebsockRequest, this, _1, _2, _3), + std::bind(&Application::onConnection, this, _1), + idleConnectionTimeout_, + sslCertPath_, + sslKeyPath_, + threadNum_, + syncAdvices_); + + assert(ioLoops.size() == threadNum_); + for (size_t i = 0; i < threadNum_; ++i) { + ioLoops[i]->setIndex(i); + } + + get_loop()->setIndex(threadNum_); + */ + + // A fast database client instance should be created in the main event + // loop, so put the main loop into ioLoops. + + ioLoops.push_back(get_loop()); + + /* + dbClientManagerPtr_->createDbClients(ioLoops); + httpCtrlsRouterPtr_->init(ioLoops); + httpSimpleCtrlsRouterPtr_->init(ioLoops); + staticFileRouterPtr_->init(ioLoops); + websockCtrlsRouterPtr_->init(); + */ + +/* + get_loop()->queueInLoop([this]() { + // Let listener event loops run when everything is ready. + listenerManagerPtr_->startListening(); + + for (auto &adv : beginningAdvices_) { + adv(); + } + + beginningAdvices_.clear(); + }); +*/ + get_loop()->loop(); +} + +trantor::EventLoop *Application::get_loop() const { + static trantor::EventLoop loop; + return &loop; +} + +void Application::onAsyncRequest(const HttpRequestPtr &req, std::function &&callback) { + /* + LOG_TRACE << "new request:" << req->peerAddr().toIpPort() << "->" + << req->localAddr().toIpPort(); + LOG_TRACE << "Headers " << req->methodString() << " " << req->path(); + LOG_TRACE << "http path=" << req->path(); + if (req->method() == Options && (req->path() == "*" || req->path() == "/*")) { + auto resp = HttpResponse::newHttpResponse(); + resp->setContentTypeCode(ContentType::CT_TEXT_PLAIN); + resp->addHeader("ALLOW", "GET,HEAD,POST,PUT,DELETE,OPTIONS,PATCH"); + resp->setExpiredTime(0); + callback(resp); + return; + } + findSessionForRequest(req); + // Route to controller + if (!preRoutingObservers_.empty()) { + for (auto &observer : preRoutingObservers_) { + observer(req); + } + } + if (preRoutingAdvices_.empty()) { + httpSimpleCtrlsRouterPtr_->route(req, std::move(callback)); + } else { + auto callbackPtr = + std::make_shared >( + std::move(callback)); + doAdvicesChain( + preRoutingAdvices_, + 0, + req, + std::make_shared >( + [req, callbackPtr, this](const HttpResponsePtr &resp) { + callCallback(req, resp, *callbackPtr); + }), + [this, callbackPtr, req]() { + httpSimpleCtrlsRouterPtr_->route(req, std::move(*callbackPtr)); + }); + }*/ +} + +void Application::onNewWebsockRequest(const HttpRequestPtr &req, + std::function &&callback, + const WebSocketConnectionPtr &wsConnPtr) { +/* + findSessionForRequest(req); + // Route to controller + if (!preRoutingObservers_.empty()) { + for (auto &observer : preRoutingObservers_) { + observer(req); + } + } + if (preRoutingAdvices_.empty()) { + websockCtrlsRouterPtr_->route(req, std::move(callback), wsConnPtr); + } else { + auto callbackPtr = + std::make_shared >( + std::move(callback)); + doAdvicesChain( + preRoutingAdvices_, + 0, + req, + std::make_shared >( + [req, callbackPtr, this](const HttpResponsePtr &resp) { + callCallback(req, resp, *callbackPtr); + }), + [this, callbackPtr, req, wsConnPtr]() { + websockCtrlsRouterPtr_->route(req, + std::move(*callbackPtr), + wsConnPtr); + }); + }*/ +} + +void Application::onConnection(const trantor::TcpConnectionPtr &conn) +{ + /* + static std::mutex mtx; + LOG_TRACE << "connect!!!" << maxConnectionNum_ + << " num=" << connectionNum_.load(); + if (conn->connected()) + { + if (connectionNum_.fetch_add(1, std::memory_order_relaxed) >= + maxConnectionNum_) + { + LOG_ERROR << "too much connections!force close!"; + conn->forceClose(); + return; + } + else if (maxConnectionNumPerIP_ > 0) + { + { + std::lock_guard lock(mtx); + auto iter = connectionsNumMap_.find(conn->peerAddr().toIp()); + if (iter == connectionsNumMap_.end()) + { + connectionsNumMap_[conn->peerAddr().toIp()] = 1; + } + else if (iter->second++ > maxConnectionNumPerIP_) + { + conn->getLoop()->queueInLoop( + [conn]() { conn->forceClose(); }); + return; + } + } + } + for (auto &advice : newConnectionAdvices_) + { + if (!advice(conn->peerAddr(), conn->localAddr())) + { + conn->forceClose(); + return; + } + } + } + else + { + if (!conn->hasContext()) + { + // If the connection is connected to the SSL port and then + // disconnected before the SSL handshake. + return; + } + connectionNum_.fetch_sub(1, std::memory_order_relaxed); + if (maxConnectionNumPerIP_ > 0) + { + std::lock_guard lock(mtx); + auto iter = connectionsNumMap_.find(conn->peerAddr().toIp()); + if (iter != connectionsNumMap_.end()) + { + --iter->second; + if (iter->second <= 0) + { + connectionsNumMap_.erase(iter); + } + } + } + } + */ +} + + Application::Application() { _instance = this; + threadNum_ = 1; + idleConnectionTimeout_ = 60; } Application::~Application() { diff --git a/core/application.h b/core/application.h index 9374eaf..d8dc163 100644 --- a/core/application.h +++ b/core/application.h @@ -7,9 +7,16 @@ #include #include +#include +#include +#include + #include "handler_instance.h" +#include "core/http_server_callbacks.h" + class Request; +class ListenerManager; class Application { public: @@ -31,11 +38,22 @@ public: virtual void migrate(); + virtual void run(); + + trantor::EventLoop *get_loop() const; + + void onAsyncRequest(const HttpRequestPtr &req, std::function &&callback); + void onNewWebsockRequest(const HttpRequestPtr &req, std::function &&callback, + const WebSocketConnectionPtr &wsConnPtr); + void onConnection(const trantor::TcpConnectionPtr &conn); + Application(); virtual ~Application(); static Application *get_instance(); + std::unique_ptr listenerManagerPtr_; + public: static HandlerInstance index_func; static std::map main_route_map; @@ -44,8 +62,15 @@ public: static std::map > error_handler_map; static std::function default_error_handler_func; + size_t idleConnectionTimeout_; + size_t threadNum_; + std::string sslCertPath_; + std::string sslKeyPath_; + std::vector > syncAdvices_; + private: static Application *_instance; + bool _running; }; #endif \ No newline at end of file diff --git a/core/attribute.h b/core/attribute.h new file mode 100644 index 0000000..b350372 --- /dev/null +++ b/core/attribute.h @@ -0,0 +1,130 @@ +/** + * + * Attribute.h + * armstrong@sweelia.com + * + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace drogon +{ +/** + * @brief This class represents the attributes stored in the HTTP request. + * One can add/get any type of data to/from an Attributes object. + */ +class Attributes +{ + public: + /** + * @brief Get the data identified by the key parameter. + * @note if the data is not found, a default value is returned. + * For example: + * @code + auto &userName = attributesPtr->get("user name"); + @endcode + */ + template + const T &get(const std::string &key) const + { + const static T nullVal = T(); + auto it = attributesMap_.find(key); + if (it != attributesMap_.end()) + { + if (typeid(T) == it->second.type()) + { + return *(any_cast(&(it->second))); + } + else + { + LOG_ERROR << "Bad type"; + } + } + return nullVal; + } + + /** + * @brief Get the 'any' object identified by the given key + */ + any &operator[](const std::string &key) + { + return attributesMap_[key]; + } + + /** + * @brief Insert a key-value pair + * @note here the any object can be created implicitly. for example + * @code + attributesPtr->insert("user name", userNameString); + @endcode + */ + void insert(const std::string &key, const any &obj) + { + attributesMap_[key] = obj; + } + + /** + * @brief Insert a key-value pair + * @note here the any object can be created implicitly. for example + * @code + attributesPtr->insert("user name", userNameString); + @endcode + */ + void insert(const std::string &key, any &&obj) + { + attributesMap_[key] = std::move(obj); + } + + /** + * @brief Erase the data identified by the given key. + */ + void erase(const std::string &key) + { + attributesMap_.erase(key); + } + + /** + * @brief Retrun true if the data identified by the key exists. + */ + bool find(const std::string &key) + { + if (attributesMap_.find(key) == attributesMap_.end()) + { + return false; + } + return true; + } + + /** + * @brief Clear all attributes. + */ + void clear() + { + attributesMap_.clear(); + } + + /** + * @brief Constructor, usually called by the framework + */ + Attributes() = default; + + private: + using AttributesMap = std::map; + AttributesMap attributesMap_; +}; + +using AttributesPtr = std::shared_ptr; + +} // namespace drogon diff --git a/core/cache_file.cpp b/core/cache_file.cpp new file mode 100644 index 0000000..28c772d --- /dev/null +++ b/core/cache_file.cpp @@ -0,0 +1,95 @@ +/** + * + * CacheFile.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 "cache_file.h" +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +using namespace drogon; + +CacheFile::CacheFile(const std::string &path, bool autoDelete) + : autoDelete_(autoDelete), path_(path) +{ +#ifndef _MSC_VER + file_ = fopen(path_.data(), "wb+"); +#else + if (fopen_s(&file_, path_.data(), "wb+") != 0) + { + file_ = nullptr; + } +#endif +} + +CacheFile::~CacheFile() +{ + if (data_) + { + munmap(data_, dataLength_); + } + if (autoDelete_ && file_) + { + fclose(file_); +#ifdef _WIN32 + _unlink(path_.data()); +#else + unlink(path_.data()); +#endif + } + else if (file_) + { + fclose(file_); + } +} + +void CacheFile::append(const char *data, size_t length) +{ + if (file_) + fwrite(data, length, 1, file_); +} + +size_t CacheFile::length() +{ + if (file_) + return ftell(file_); + return 0; +} + +char *CacheFile::data() +{ + if (!file_) + return nullptr; + if (!data_) + { + fflush(file_); +#ifdef _WIN32 + auto fd = _fileno(file_); +#else + auto fd = fileno(file_); +#endif + dataLength_ = length(); + data_ = static_cast( + mmap(nullptr, dataLength_, PROT_READ, MAP_SHARED, fd, 0)); + if (data_ == MAP_FAILED) + { + data_ = nullptr; + LOG_SYSERR << "mmap:"; + } + } + return data_; +} diff --git a/core/cache_file.h b/core/cache_file.h new file mode 100644 index 0000000..93df52a --- /dev/null +++ b/core/cache_file.h @@ -0,0 +1,50 @@ +/** + * + * CacheFile.h + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace drogon +{ +class CacheFile : public trantor::NonCopyable +{ + public: + explicit CacheFile(const std::string &path, bool autoDelete = true); + ~CacheFile(); + void append(const std::string &data) + { + append(data.data(), data.length()); + } + void append(const char *data, size_t length); + string_view getStringView() + { + if (data()) + return string_view(data_, dataLength_); + return string_view(); + } + + private: + char *data(); + size_t length(); + FILE *file_{nullptr}; + bool autoDelete_{true}; + const std::string path_; + char *data_{nullptr}; + size_t dataLength_{0}; +}; +} // namespace drogon diff --git a/core/cookie.cpp b/core/cookie.cpp new file mode 100644 index 0000000..1af4f31 --- /dev/null +++ b/core/cookie.cpp @@ -0,0 +1,50 @@ +/** + * + * Cookie.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 +#include + +using namespace drogon; +std::string Cookie::cookieString() const +{ + std::string ret = "Set-Cookie: "; + ret.append(key_).append("= ").append(value_).append("; "); + if (expiresDate_.microSecondsSinceEpoch() != + (std::numeric_limits::max)() && + expiresDate_.microSecondsSinceEpoch() >= 0) + { + ret.append("Expires= ") + .append(utils::getHttpFullDate(expiresDate_)) + .append("; "); + } + if (!domain_.empty()) + { + ret.append("Domain= ").append(domain_).append("; "); + } + if (!path_.empty()) + { + ret.append("Path= ").append(path_).append("; "); + } + if (secure_) + { + ret.append("Secure; "); + } + if (httpOnly_) + { + ret.append("HttpOnly; "); + } + ret.resize(ret.length() - 2); // delete last semicolon + ret.append("\r\n"); + return ret; +} diff --git a/core/cookie.h b/core/cookie.h new file mode 100644 index 0000000..c054b19 --- /dev/null +++ b/core/cookie.h @@ -0,0 +1,252 @@ +/** + * + * Cookis.h + * 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 + * + */ +#pragma once + +#include +#include +#include + +namespace drogon +{ +/** + * @brief this class represents a cookie entity. + */ +class Cookie +{ + public: + /// Constructor + /** + * @param key key of the cookie + * @param value value of the cookie + */ + Cookie(const std::string &key, const std::string &value) + : key_(key), value_(value) + { + } + Cookie(std::string &&key, std::string &&value) + : key_(std::move(key)), value_(std::move(value)) + { + } + Cookie() = default; + + /** + * @brief Set the Expires Date + * + * @param date The expiration date + */ + void setExpiresDate(const trantor::Date &date) + { + expiresDate_ = date; + } + + /** + * @brief Set if the cookie is HTTP only. + */ + void setHttpOnly(bool only) + { + httpOnly_ = only; + } + + /** + * @brief Set if the cookie is secure. + */ + void setSecure(bool secure) + { + secure_ = secure; + } + + /** + * @brief Set the domain of the cookie. + */ + void setDomain(const std::string &domain) + { + domain_ = domain; + } + void setDomain(std::string &&domain) + { + domain_ = std::move(domain); + } + + /** + * @brief Set the path of the cookie. + */ + void setPath(const std::string &path) + { + path_ = path; + } + void setPath(std::string &&path) + { + path_ = std::move(path); + } + + /** + * @brief Set the key of the cookie. + */ + void setKey(const std::string &key) + { + key_ = key; + } + void setKey(std::string &&key) + { + key_ = std::move(key); + } + /** + * @brief Set the value of the cookie. + */ + void setValue(const std::string &value) + { + value_ = value; + } + void setValue(std::string &&value) + { + value_ = std::move(value); + } + + /** + * @brief Get the string value of the cookie + */ + std::string cookieString() const; + + /** + * @brief Get the string value of the cookie + */ + std::string getCookieString() const + { + return cookieString(); + } + + /** + * @brief Get the expiration date of the cookie + */ + const trantor::Date &expiresDate() const + { + return expiresDate_; + } + + /** + * @brief Get the expiration date of the cookie + */ + const trantor::Date &getExpiresDate() const + { + return expiresDate_; + } + + /** + * @brief Get the domain of the cookie + */ + const std::string &domain() const + { + return domain_; + } + + /** + * @brief Get the domain of the cookie + */ + const std::string &getDomain() const + { + return domain_; + } + + /** + * @brief Get the path of the cookie + */ + const std::string &path() const + { + return path_; + } + + /** + * @brief Get the path of the cookie + */ + const std::string &getPath() const + { + return path_; + } + + /** + * @brief Get the keyword of the cookie + */ + const std::string &key() const + { + return key_; + } + + /** + * @brief Get the keyword of the cookie + */ + const std::string &getKey() const + { + return key_; + } + + /** + * @brief Get the value of the cookie + */ + const std::string &value() const + { + return value_; + } + + /** + * @brief Get the value of the cookie + */ + const std::string &getValue() const + { + return value_; + } + + /** + * @brief Check if the cookie is empty + * + * @return true means the cookie is not empty + * @return false means the cookie is empty + */ + operator bool() const + { + return (!key_.empty()) && (!value_.empty()); + } + + /** + * @brief Check if the cookie is HTTP only + * + * @return true means the cookie is HTTP only + * @return false means the cookie is not HTTP only + */ + bool isHttpOnly() const + { + return httpOnly_; + } + + /** + * @brief Check if the cookie is secure. + * + * @return true means the cookie is secure. + * @return false means the cookie is not secure. + */ + bool isSecure() const + { + return secure_; + } + + private: + trantor::Date expiresDate_{(std::numeric_limits::max)()}; + bool httpOnly_{true}; + bool secure_{false}; + std::string domain_; + std::string path_; + std::string key_; + std::string value_; +}; + +} // namespace drogon diff --git a/core/http_file_upload_request.cpp b/core/http_file_upload_request.cpp new file mode 100644 index 0000000..46e408f --- /dev/null +++ b/core/http_file_upload_request.cpp @@ -0,0 +1,32 @@ +/** + * + * HttpFileUploadRequest.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_file_upload_request.h" +#include +#include + +using namespace drogon; + +HttpFileUploadRequest::HttpFileUploadRequest( + const std::vector &files) + : HttpRequest(nullptr), + boundary_(utils::genRandomString(32)), + files_(files) +{ + setMethod(drogon::Post); + setVersion(drogon::Version::kHttp11); + setContentType("Content-Type: multipart/form-data; boundary=" + boundary_ + + "\r\n"); + contentType_ = CT_MULTIPART_FORM_DATA; +} \ No newline at end of file diff --git a/core/http_file_upload_request.h b/core/http_file_upload_request.h new file mode 100644 index 0000000..2097dc8 --- /dev/null +++ b/core/http_file_upload_request.h @@ -0,0 +1,40 @@ +/** + * + * HttpFileUploadRequest.h + * 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 + * + */ + +#pragma once +#include "core/http_request.h" +#include +#include + +namespace drogon +{ +class HttpFileUploadRequest : public HttpRequest +{ + public: + const std::string &boundary() const + { + return boundary_; + } + const std::vector &files() const + { + return files_; + } + explicit HttpFileUploadRequest(const std::vector &files); + + private: + std::string boundary_; + std::vector files_; +}; + +} // namespace drogon diff --git a/core/http_message_body.h b/core/http_message_body.h new file mode 100644 index 0000000..104802d --- /dev/null +++ b/core/http_message_body.h @@ -0,0 +1,159 @@ +/** + * + * HttpMessageBody.h + * 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 + * + */ + +#pragma once +#include "core/string_view.h" +#include + +namespace drogon +{ +class HttpMessageBody +{ + public: + enum class BodyType + { + kNone = 0, + kString, + kStringView + }; + BodyType bodyType() + { + return type_; + } + virtual const char *data() const + { + return nullptr; + } + virtual char *data() + { + return nullptr; + } + virtual size_t length() const + { + return 0; + } + virtual const std::string &getString() const = 0; + virtual std::string &getString() = 0; + virtual void append(const char *buf, size_t len) + { + } + virtual ~HttpMessageBody() + { + } + + protected: + BodyType type_{BodyType::kNone}; +}; +class HttpMessageStringBody : public HttpMessageBody +{ + public: + HttpMessageStringBody() + { + type_ = BodyType::kString; + } + HttpMessageStringBody(const std::string &body) : body_(body) + { + type_ = BodyType::kString; + } + HttpMessageStringBody(std::string &&body) : body_(std::move(body)) + { + type_ = BodyType::kString; + } + virtual const char *data() const override + { + return body_.data(); + } + virtual char *data() override + { + return const_cast(body_.data()); + } + virtual size_t length() const override + { + return body_.length(); + } + virtual const std::string &getString() const override + { + return body_; + } + virtual std::string &getString() override + { + return body_; + } + virtual void append(const char *buf, size_t len) override + { + body_.append(buf, len); + } + + private: + std::string body_; +}; + +class HttpMessageStringViewBody : public HttpMessageBody +{ + public: + HttpMessageStringViewBody(const char *buf, size_t len) : body_(buf, len) + { + type_ = BodyType::kStringView; + } + virtual const char *data() const override + { + return body_.data(); + } + virtual char *data() override + { + return const_cast(body_.data()); + } + virtual size_t length() const override + { + return body_.length(); + } + virtual const std::string &getString() const override + { + if (!bodyString_) + { + if (!body_.empty()) + { + bodyString_ = + std::make_unique(body_.data(), body_.length()); + } + else + { + bodyString_ = std::make_unique(); + } + } + return *bodyString_; + } + virtual std::string &getString() override + { + if (!bodyString_) + { + if (!body_.empty()) + { + bodyString_ = + std::make_unique(body_.data(), body_.length()); + } + else + { + bodyString_ = std::make_unique(); + } + } + return *bodyString_; + } + + private: + string_view body_; + mutable std::unique_ptr bodyString_; +}; + +} // namespace drogon \ No newline at end of file diff --git a/core/http_request.cpp b/core/http_request.cpp new file mode 100644 index 0000000..fc67aa2 --- /dev/null +++ b/core/http_request.cpp @@ -0,0 +1,699 @@ +/** + * + * @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 + +#include "core/application.h" + +#include +#include +#ifndef _WIN32 +#include +#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(); + JSONCPP_STRING errs; + std::unique_ptr 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::move(errs)); + } + else + { + jsonParsingErrorPtr_.reset(); + } + } + else + { + jsonPtr_.reset(); + jsonParsingErrorPtr_ = + std::make_unique("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(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(), + 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(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(nullptr); + req->setMethod(drogon::Get); + req->setVersion(drogon::Version::kHttp11); + return req; +} + +HttpRequestPtr HttpRequest::newHttpFormPostRequest() +{ + auto req = std::make_shared(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(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 &files) +{ + return std::make_shared(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 <= HttpAppFrameworkImpl::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 <= + HttpAppFrameworkImpl::instance().getClientMaxMemoryBodySize()) + { + content_.append(data, length); + } + else + { + createTmpFile(); + cacheFilePtr_->append(content_); + cacheFilePtr_->append(data, length); + content_.clear(); + } + } +} + +void HttpRequest::createTmpFile() +{ + /* + auto tmpfile = HttpAppFrameworkImpl::instance().getUploadPath(); + + + auto fileName = utils::getUuid(); + tmpfile.append("/tmp/") + .append(1, fileName[0]) + .append(1, fileName[1]) + .append("/") + .append(fileName); + cacheFilePtr_ = std::make_unique(tmpfile); + */ +} diff --git a/core/http_request.h b/core/http_request.h new file mode 100644 index 0000000..bd33fb7 --- /dev/null +++ b/core/http_request.h @@ -0,0 +1,723 @@ +/** + * + * @file HttpRequest.h + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cache_file.h" +#include "core/http_utils.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace drogon { +class HttpRequest; +using HttpRequestPtr = std::shared_ptr; + +/** + * @brief This template is used to convert a request object to a custom + * type object. Users must specialize the template for a particular type. + */ +template +T fromRequest(const HttpRequest &req) { + //LOG_ERROR << "You must specialize the fromRequest template for the type of " + // << DrClassMap::demangle(typeid(T).name()); + exit(1); +} + +/** + * @brief This template is used to create a request object from a custom + * type object by calling the newCustomHttpRequest(). Users must specialize + * the template for a particular type. + */ +template +HttpRequestPtr toRequest(T &&) { + //LOG_ERROR << "You must specialize the toRequest template for the type of " + // << DrClassMap::demangle(typeid(T).name()); + exit(1); +} + +template <> +HttpRequestPtr toRequest(const Json::Value &pJson); +template <> +HttpRequestPtr toRequest(Json::Value &&pJson); +template <> +inline HttpRequestPtr toRequest(Json::Value &pJson) { + return toRequest((const Json::Value &)pJson); +} + +template <> +std::shared_ptr fromRequest(const HttpRequest &req); + +/// Abstract class for webapp developer to get or set the Http request; +class HttpRequest { +public: + friend class HttpRequestParser; + + explicit HttpRequest(trantor::EventLoop *loop) : + creationDate_(trantor::Date::now()), loop_(loop) { + } + + void reset() { + method_ = Invalid; + version_ = Version::kUnknown; + flagForParsingJson_ = false; + headers_.clear(); + cookies_.clear(); + flagForParsingParameters_ = false; + path_.clear(); + matchedPathPattern_ = ""; + query_.clear(); + parameters_.clear(); + jsonPtr_.reset(); + sessionPtr_.reset(); + attributesPtr_.reset(); + cacheFilePtr_.reset(); + expectPtr_.reset(); + content_.clear(); + contentType_ = CT_TEXT_PLAIN; + flagForParsingContentType_ = false; + contentTypeString_.clear(); + keepAlive_ = true; + jsonParsingErrorPtr_.reset(); + } + + trantor::EventLoop *getLoop() { + return loop_; + } + + void setVersion(Version v) { + version_ = v; + if (version_ == Version::kHttp10) { + keepAlive_ = false; + } + } + + void setPath(const char *start, const char *end) { + if (utils::needUrlDecoding(start, end)) { + path_ = utils::urlDecode(start, end); + } else { + path_.append(start, end); + } + } + + virtual void setPath(const std::string &path) { + path_ = path; + } + + /** + * @brief This template enables implicit type conversion. For using this + * template, user must specialize the fromRequest template. For example a + * shared_ptr specialization version is available above, so + * we can use the following code to get a json object: + * @code + std::shared_ptr jsonPtr = *requestPtr; + @endcode + * With this template, user can use their favorite JSON library instead of + * the default jsoncpp library or convert the request to an object of any + * custom type. + */ + template + operator T() const { + return fromRequest(*this); + } + + /** + * @brief This template enables explicit type conversion, see the above + * template. + */ + template + T as() const { + return fromRequest(*this); + } + + /// Return the method string of the request, such as GET, POST, etc. + virtual const char *methodString() const; + const char *getMethodString() const { + return methodString(); + } + + /// Return the enum type method of the request. + virtual HttpMethod method() const { + return method_; + } + + HttpMethod getMethod() const { + return method(); + } + + /// Get the header string identified by the field parameter + virtual const std::string &getHeader(const std::string &field) const { + auto lowField = field; + std::transform(lowField.begin(), + lowField.end(), + lowField.begin(), + tolower); + return getHeaderBy(lowField); + } + /// Get the header string identified by the field parameter + virtual const std::string &getHeader(std::string &&field) const { + std::transform(field.begin(), field.end(), field.begin(), tolower); + return getHeaderBy(field); + } + + /// Set the header string identified by the field parameter + virtual void addHeader(const std::string &key, + const std::string &value) { + headers_[key] = value; + } + + /// Get the cookie string identified by the field parameter + virtual const std::string &getCookie(const std::string &field) const { + const static std::string defaultVal; + auto it = cookies_.find(field); + if (it != cookies_.end()) { + return it->second; + } + return defaultVal; + } + + /// Get all headers of the request + virtual const std::unordered_map &headers() const { + return headers_; + } + + /// Get all headers of the request + const std::unordered_map &getHeaders() const { + return headers(); + } + + /// Get all cookies of the request + virtual const std::unordered_map &cookies() const { + return cookies_; + } + + /// Get all cookies of the request + const std::unordered_map &getCookies() const { + return cookies(); + } + + /// Get the query string of the request. + /** + * The query string is the substring after the '?' in the URL string. + */ + virtual const std::string &query() const { + return query_; + } + + /// Get the query string of the request. + const std::string &getQuery() const { + return query(); + } + + /// Get the content string of the request, which is the body part of the + /// request. + string_view body() const { + return string_view(bodyData(), bodyLength()); + } + + /// Get the content string of the request, which is the body part of the + /// request. + string_view getBody() const { + return body(); + } + + void setQuery(const char *start, const char *end) { + query_.assign(start, end); + } + + void setQuery(const std::string &query) { + query_ = query; + } + + string_view bodyView() const { + if (cacheFilePtr_) { + return cacheFilePtr_->getStringView(); + } + return content_; + } + + virtual const char *bodyData() const { + if (cacheFilePtr_) { + return cacheFilePtr_->getStringView().data(); + } + return content_.data(); + } + virtual size_t bodyLength() const { + if (cacheFilePtr_) { + return cacheFilePtr_->getStringView().length(); + } + return content_.length(); + } + + void appendToBody(const char *data, size_t length); + + void reserveBodySize(size_t length); + + string_view queryView() const { + return query_; + } + + string_view contentView() const { + if (cacheFilePtr_) + return cacheFilePtr_->getStringView(); + return content_; + } + + /// Set the content string of the request. + virtual void setBody(const std::string &body) { + content_ = body; + } + + /// Set the content string of the request. + virtual void setBody(std::string &&body) { + content_ = std::move(body); + } + + /// Get the path of the request. + virtual const std::string &path() const { + return path_; + } + + /// Get the path of the request. + const std::string &getPath() const { + return path(); + } + + /// Get the matched path pattern after routing + string_view getMatchedPathPattern() const { + return matchedPathPattern(); + } + + /// Get the matched path pattern after routing + string_view matchedPathPattern() const { + return string_view(matchedPathPatternData(), + matchedPathPatternLength()); + } + virtual const char *matchedPathPatternData() const { + return matchedPathPattern_.data(); + } + virtual size_t matchedPathPatternLength() const { + return matchedPathPattern_.length(); + } + + void setMatchedPathPattern(const std::string &pathPattern) { + matchedPathPattern_ = pathPattern; + } + const std::string &expect() const { + const static std::string none{ "" }; + if (expectPtr_) + return *expectPtr_; + return none; + } + bool keepAlive() const { + return keepAlive_; + } + + /// Return the enum type version of the request. + /** + * kHttp10 means Http version is 1.0 + * kHttp11 means Http verison is 1.1 + */ + virtual Version version() const { + return version_; + } + /// Return the enum type version of the request. + Version getVersion() const { + return version(); + } + + /// Get the session to which the request belongs. + + virtual SessionPtr session() const { + return sessionPtr_; + } + + void setSession(const SessionPtr &session) { + sessionPtr_ = session; + } + + /// Get the session to which the request belongs. + SessionPtr getSession() const { + return session(); + } + + /// Get the attributes store, users can add/get any type of data to/from + /// this store + virtual const AttributesPtr &attributes() const { + if (!attributesPtr_) { + attributesPtr_ = std::make_shared(); + } + return attributesPtr_; + } + + /// Get the attributes store, users can add/get any type of data to/from + /// this store + const AttributesPtr &getAttributes() const { + return attributes(); + } + + /// Get parameters of the request. + virtual const std::unordered_map ¶meters() + const { + parseParametersOnce(); + return parameters_; + } + + /// Get parameters of the request. + const std::unordered_map &getParameters() const { + return parameters(); + } + + /// Get a parameter identified by the @param key + virtual const std::string &getParameter( + const std::string &key) const { + const static std::string defaultVal; + parseParametersOnce(); + auto iter = parameters_.find(key); + if (iter != parameters_.end()) + return iter->second; + return defaultVal; + } + + /// Return the remote IP address and port + virtual const trantor::InetAddress &peerAddr() const { + return peer_; + } + + const trantor::InetAddress &getPeerAddr() const { + return peerAddr(); + } + + /// Return the local IP address and port + virtual const trantor::InetAddress &localAddr() const { + return local_; + } + + const trantor::InetAddress &getLocalAddr() const { + return localAddr(); + } + + /// Return the creation timestamp set by the framework. + virtual const trantor::Date &creationDate() const { + return creationDate_; + } + + const trantor::Date &getCreationDate() const { + return creationDate(); + } + + void setCreationDate(const trantor::Date &date) { + creationDate_ = date; + } + + void setPeerAddr(const trantor::InetAddress &peer) { + peer_ = peer; + } + + void setLocalAddr(const trantor::InetAddress &local) { + local_ = local; + } + + void addHeader(const char *start, const char *colon, const char *end); + + const std::string &getHeaderBy(const std::string &lowerField) const { + const static std::string defaultVal; + auto it = headers_.find(lowerField); + if (it != headers_.end()) { + return it->second; + } + return defaultVal; + } + + /// Get the Json object of the request + /** + * The content type of the request must be 'application/json', and the query + * string (the part after the question mark in the URI) must be empty, + * otherwise the method returns an empty shared_ptr object. + */ + virtual const std::shared_ptr &jsonObject() const { + // Not multi-thread safe but good, because we basically call this + // function in a single thread + if (!flagForParsingJson_) { + flagForParsingJson_ = true; + parseJson(); + } + return jsonPtr_; + } + + /// Get the Json object of the request + const std::shared_ptr &getJsonObject() const { + return jsonObject(); + } + + /** + * @brief Get the error message of parsing the JSON body received from peer. + * This method usually is called after getting a empty shared_ptr object + * by the getJsonObject() method. + * + * @return const std::string& The error message. An empty string is returned + * when no error occurs. + */ + virtual const std::string &getJsonError() const { + const static std::string none{ "" }; + if (jsonParsingErrorPtr_) + return *jsonParsingErrorPtr_; + return none; + } + + /// Get the content type + virtual ContentType contentType() const { + if (!flagForParsingContentType_) { + flagForParsingContentType_ = true; + auto &contentTypeString = getHeaderBy("content-type"); + if (contentTypeString == "") { + contentType_ = CT_NONE; + } else { + auto pos = contentTypeString.find(';'); + if (pos != std::string::npos) { + contentType_ = parseContentType( + string_view(contentTypeString.data(), pos)); + } else { + contentType_ = + parseContentType(string_view(contentTypeString)); + } + } + } + return contentType_; + } + ContentType getContentType() const { + return contentType(); + } + + /// Set the Http method + virtual void setMethod(const HttpMethod method) { + method_ = method; + return; + } + + /// Set the path of the request + + void setSecure(bool secure) { + isOnSecureConnection_ = secure; + } + + /// Set the parameter of the request + + virtual void setParameter(const std::string &key, + const std::string &value) { + flagForParsingParameters_ = true; + parameters_[key] = value; + } + + const std::string &getContent() const { + return content_; + } + + void swap(HttpRequest &that) noexcept; + + void setContent(const std::string &content) { + content_ = content; + } + + /// Set or get the content type + + virtual void setContentTypeCode(const ContentType type) { + contentType_ = type; + flagForParsingContentType_ = true; + auto &typeStr = webContentTypeToString(type); + setContentType(std::string(typeStr.data(), typeStr.length())); + } + + /// Set the request content-type string, The string + /// must contain the header name and CRLF. + /// For example, "content-type: text/plain\r\n" + + virtual void setCustomContentTypeString(const std::string &type) { + contentType_ = CT_NONE; + flagForParsingContentType_ = true; + contentTypeString_ = type; + } + + // virtual void setContentTypeCodeAndCharacterSet(ContentType type, const + // std::string &charSet = "utf-8") + // { + // contentType_ = type; + // setContentType(webContentTypeAndCharsetToString(type, charSet)); + // } + + /// Add a cookie + + virtual void addCookie(const std::string &key, + const std::string &value) { + cookies_[key] = value; + } + + /** + * @brief Set the request object to the pass-through mode or not. It's not + * by default when a new request object is created. + * In pass-through mode, no addtional headers (including server, date, + * content-type and content-length, etc.) are added to the request. This + * mode is useful for some applications such as a proxy. + * + * @param flag + */ + virtual void setPassThrough(bool flag) { + passThrough_ = flag; + } + + bool passThrough() const { + return passThrough_; + } + + void appendToBuffer(trantor::MsgBuffer *output) const; + + virtual ~HttpRequest(); + + /// The following methods are a series of factory methods that help users + /// create request objects. + + /// Create a normal request with http method Get and version Http1.1. + static HttpRequestPtr newHttpRequest(); + + /// Create a http request with: + /// Method: Get + /// Version: Http1.1 + /// Content type: application/json, the @param data is serialized into the + /// content of the request. + static HttpRequestPtr newHttpJsonRequest(const Json::Value &data); + + /// Create a http request with: + /// Method: Post + /// Version: Http1.1 + /// Content type: application/x-www-form-urlencoded + static HttpRequestPtr newHttpFormPostRequest(); + + /// Create a http file upload request with: + /// Method: Post + /// Version: Http1.1 + /// Content type: multipart/form-data + /// The @param files represents pload files which are transferred to the + /// server via the multipart/form-data format + static HttpRequestPtr newFileUploadRequest( + const std::vector &files); + + /** + * @brief Create a custom HTTP request object. For using this template, + * users must specialize the toRequest template. + */ + template + static HttpRequestPtr newCustomHttpRequest(T &&obj) { + return toRequest(std::forward(obj)); + } + + virtual bool isOnSecureConnection() const noexcept { + return isOnSecureConnection_; + } + +protected: + void setContentType(const std::string &contentType) { + contentTypeString_ = contentType; + } + void setContentType(std::string &&contentType) { + contentTypeString_ = std::move(contentType); + } + +private: + void parseParameters() const; + void parseParametersOnce() const { + // Not multi-thread safe but good, because we basically call this + // function in a single thread + if (!flagForParsingParameters_) { + flagForParsingParameters_ = true; + parseParameters(); + } + } + void createTmpFile(); + void parseJson() const; + mutable bool flagForParsingParameters_{ false }; + mutable bool flagForParsingJson_{ false }; + HttpMethod method_{ Invalid }; + Version version_{ Version::kUnknown }; + std::string path_; + string_view matchedPathPattern_{ "" }; + std::string query_; + std::unordered_map headers_; + std::unordered_map cookies_; + mutable std::unordered_map parameters_; + mutable std::shared_ptr jsonPtr_; + SessionPtr sessionPtr_; + mutable AttributesPtr attributesPtr_; + trantor::InetAddress peer_; + trantor::InetAddress local_; + trantor::Date creationDate_; + std::unique_ptr cacheFilePtr_; + mutable std::unique_ptr jsonParsingErrorPtr_; + std::unique_ptr expectPtr_; + bool keepAlive_{ true }; + bool isOnSecureConnection_{ false }; + bool passThrough_{ false }; + +protected: + std::string content_; + trantor::EventLoop *loop_; + mutable ContentType contentType_{ CT_TEXT_PLAIN }; + mutable bool flagForParsingContentType_{ false }; + std::string contentTypeString_; +}; + +template <> +inline HttpRequestPtr toRequest(const Json::Value &pJson) { + return HttpRequest::newHttpJsonRequest(pJson); +} + +template <> +inline HttpRequestPtr toRequest(Json::Value &&pJson) { + return HttpRequest::newHttpJsonRequest(std::move(pJson)); +} + +template <> +inline std::shared_ptr fromRequest(const HttpRequest &req) { + return req.getJsonObject(); +} + +using HttpRequestPtr = std::shared_ptr; + +inline void swap(HttpRequest &one, HttpRequest &two) noexcept { + one.swap(two); +} + +} // namespace drogon diff --git a/core/http_request_parser.cpp b/core/http_request_parser.cpp new file mode 100644 index 0000000..03bd039 --- /dev/null +++ b/core/http_request_parser.cpp @@ -0,0 +1,510 @@ +/** + * + * 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 "core/http_request_parser.h" + +#include "core/http_response.h" +#include "http_request.h" +#include "core/http_utils.h" +#include + +#include +#include +#include + +using namespace trantor; +using namespace drogon; + +HttpRequestParser::HttpRequestParser(const trantor::TcpConnectionPtr &connPtr) + : status_(HttpRequestParseStatus::kExpectMethod), + loop_(connPtr->getLoop()), + conn_(connPtr) +{ +} + +void HttpRequestParser::shutdownConnection(HttpStatusCode code) +{ + auto connPtr = conn_.lock(); + if (connPtr) + { + connPtr->send(utils::formattedString( + "HTTP/1.1 %d %s\r\nConnection: close\r\n\r\n", + code, + statusCodeToString(code).data())); + connPtr->shutdown(); + } +} + +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 *question = std::find(start, space, '?'); + if (question != space) + { + request_->setPath(start, question); + request_->setQuery(question + 1, space); + } + else + { + request_->setPath(start, 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; +} +HttpRequestPtr HttpRequestParser::makeRequestForPool(HttpRequestImpl *ptr) +{ + std::weak_ptr weakPtr = shared_from_this(); + return std::shared_ptr(ptr, [weakPtr](HttpRequestImpl *p) { + auto thisPtr = weakPtr.lock(); + if (thisPtr) + { + if (thisPtr->loop_->isInLoopThread()) + { + p->reset(); + thisPtr->requestsPool_.emplace_back( + thisPtr->makeRequestForPool(p)); + } + else + { + thisPtr->loop_->queueInLoop([thisPtr, p]() { + p->reset(); + thisPtr->requestsPool_.emplace_back( + thisPtr->makeRequestForPool(p)); + }); + } + } + else + { + delete p; + } + }); +} +void HttpRequestParser::reset() +{ + assert(loop_->isInLoopThread()); + currentContentLength_ = 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 false if any error +bool HttpRequestParser::parseRequest(MsgBuffer *buf) +{ + bool ok = true; + bool hasMore = true; + // std::cout<peek(),buf->readableBytes())<peek(), (const char *)buf->beginWrite(), ' '); + if (space != buf->beginWrite()) + { + if (request_->setMethod(buf->peek(), space)) + { + status_ = HttpRequestParseStatus::kExpectRequestLine; + buf->retrieveUntil(space + 1); + continue; + } + else + { + buf->retrieveAll(); + shutdownConnection(k405MethodNotAllowed); + return false; + } + } + else + { + if (buf->readableBytes() >= 7) + { + buf->retrieveAll(); + shutdownConnection(k400BadRequest); + return false; + } + hasMore = false; + } + } + else if (status_ == HttpRequestParseStatus::kExpectRequestLine) + { + const char *crlf = buf->findCRLF(); + if (crlf) + { + ok = processRequestLine(buf->peek(), crlf); + if (ok) + { + buf->retrieveUntil(crlf + 2); + status_ = HttpRequestParseStatus::kExpectHeaders; + } + else + { + buf->retrieveAll(); + shutdownConnection(k400BadRequest); + return false; + } + } + else + { + if (buf->readableBytes() >= 64 * 1024) + { + /// The limit for request line is 64K bytes. respone + /// k414RequestURITooLarge + /// TODO: Make this configurable? + buf->retrieveAll(); + shutdownConnection(k414RequestURITooLarge); + return false; + } + hasMore = false; + } + } + else if (status_ == HttpRequestParseStatus::kExpectHeaders) + { + const char *crlf = buf->findCRLF(); + if (crlf) + { + const char *colon = std::find(buf->peek(), crlf, ':'); + if (colon != crlf) + { + request_->addHeader(buf->peek(), colon, crlf); + } + else + { + // empty line, end of header + const std::string &len = + request_->getHeaderBy("content-length"); + if (!len.empty()) + { + currentContentLength_ = std::stoull(len.c_str()); + if (currentContentLength_ == 0) + { + status_ = HttpRequestParseStatus::kGotAll; + ++requestsCounter_; + hasMore = false; + } + else + { + status_ = HttpRequestParseStatus::kExpectBody; + } + } + else + { + const std::string &encode = + request_->getHeaderBy("transfer-encoding"); + if (encode.empty()) + { + status_ = HttpRequestParseStatus::kGotAll; + ++requestsCounter_; + hasMore = false; + } + else if (encode == "chunked") + { + status_ = HttpRequestParseStatus::kExpectChunkLen; + } + else + { + buf->retrieveAll(); + shutdownConnection(k501NotImplemented); + return false; + } + } + + auto &expect = request_->expect(); + if (expect == "100-continue" && + request_->getVersion() >= Version::kHttp11) + { + if (currentContentLength_ == 0) + { + buf->retrieveAll(); + shutdownConnection(k400BadRequest); + return false; + } + // rfc2616-8.2.3 + auto connPtr = conn_.lock(); + if (connPtr) + { + auto resp = HttpResponse::newHttpResponse(); + if (currentContentLength_ > + HttpAppFrameworkImpl::instance() + .getClientMaxBodySize()) + { + resp->setStatusCode(k413RequestEntityTooLarge); + auto httpString = + static_cast(resp.get()) + ->renderToBuffer(); + reset(); + connPtr->send(std::move(*httpString)); + } + else + { + resp->setStatusCode(k100Continue); + auto httpString = + static_cast(resp.get()) + ->renderToBuffer(); + connPtr->send(std::move(*httpString)); + } + } + } + else if (!expect.empty()) + { + LOG_WARN << "417ExpectationFailed for \"" << expect + << "\""; + auto connPtr = conn_.lock(); + if (connPtr) + { + buf->retrieveAll(); + shutdownConnection(k417ExpectationFailed); + return false; + } + } + else if (currentContentLength_ > + HttpAppFrameworkImpl::instance() + .getClientMaxBodySize()) + { + buf->retrieveAll(); + shutdownConnection(k413RequestEntityTooLarge); + return false; + } + request_->reserveBodySize(currentContentLength_); + } + buf->retrieveUntil(crlf + 2); + } + else + { + if (buf->readableBytes() >= 64 * 1024) + { + /// The limit for every request header is 64K bytes; + /// TODO: Make this configurable? + buf->retrieveAll(); + shutdownConnection(k400BadRequest); + return false; + } + hasMore = false; + } + } + else if (status_ == HttpRequestParseStatus::kExpectBody) + { + if (buf->readableBytes() == 0) + { + if (currentContentLength_ == 0) + { + status_ = HttpRequestParseStatus::kGotAll; + ++requestsCounter_; + } + break; + } + if (currentContentLength_ >= buf->readableBytes()) + { + currentContentLength_ -= buf->readableBytes(); + request_->appendToBody(buf->peek(), buf->readableBytes()); + buf->retrieveAll(); + } + else + { + request_->appendToBody(buf->peek(), currentContentLength_); + buf->retrieve(currentContentLength_); + currentContentLength_ = 0; + } + if (currentContentLength_ == 0) + { + status_ = HttpRequestParseStatus::kGotAll; + ++requestsCounter_; + hasMore = false; + } + } + else if (status_ == HttpRequestParseStatus::kExpectChunkLen) + { + const char *crlf = buf->findCRLF(); + if (crlf) + { + // chunk length line + std::string len(buf->peek(), crlf - buf->peek()); + char *end; + currentChunkLength_ = strtol(len.c_str(), &end, 16); + // LOG_TRACE << "chun length : " << + // responsePtr_->currentChunkLength_; + if (currentChunkLength_ != 0) + { + if (currentChunkLength_ + currentContentLength_ > + HttpAppFrameworkImpl::instance().getClientMaxBodySize()) + { + buf->retrieveAll(); + shutdownConnection(k413RequestEntityTooLarge); + return false; + } + status_ = HttpRequestParseStatus::kExpectChunkBody; + } + else + { + status_ = HttpRequestParseStatus::kExpectLastEmptyChunk; + } + buf->retrieveUntil(crlf + 2); + } + else + { + hasMore = false; + } + } + else if (status_ == HttpRequestParseStatus::kExpectChunkBody) + { + // LOG_TRACE<<"expect chunk + // len="<currentChunkLength_; + if (buf->readableBytes() >= (currentChunkLength_ + 2)) + { + if (*(buf->peek() + currentChunkLength_) == '\r' && + *(buf->peek() + currentChunkLength_ + 1) == '\n') + { + request_->appendToBody(buf->peek(), currentChunkLength_); + buf->retrieve(currentChunkLength_ + 2); + currentContentLength_ += currentChunkLength_; + currentChunkLength_ = 0; + status_ = HttpRequestParseStatus::kExpectChunkLen; + } + else + { + // error! + buf->retrieveAll(); + return false; + } + } + else + { + hasMore = false; + } + } + else if (status_ == HttpRequestParseStatus::kExpectLastEmptyChunk) + { + // last empty chunk + const char *crlf = buf->findCRLF(); + if (crlf) + { + buf->retrieveUntil(crlf + 2); + status_ = HttpRequestParseStatus::kGotAll; + ++requestsCounter_; + break; + } + else + { + hasMore = false; + } + } + } + return ok; +} + +void HttpRequestParser::pushRequestToPipelining(const HttpRequestPtr &req) +{ +#ifndef NDEBUG + auto conn = conn_.lock(); + if (conn) + { + conn->getLoop()->assertInLoopThread(); + } +#endif + requestPipelining_.push_back({req, {nullptr, false}}); +} + +HttpRequestPtr HttpRequestParser::getFirstRequest() const +{ +#ifndef NDEBUG + auto conn = conn_.lock(); + if (conn) + { + conn->getLoop()->assertInLoopThread(); + } +#endif + if (!requestPipelining_.empty()) + { + return requestPipelining_.front().first; + } + return nullptr; +} + +std::pair HttpRequestParser::getFirstResponse() const +{ +#ifndef NDEBUG + auto conn = conn_.lock(); + if (conn) + { + conn->getLoop()->assertInLoopThread(); + } +#endif + if (!requestPipelining_.empty()) + { + return requestPipelining_.front().second; + } + return {nullptr, false}; +} + +void HttpRequestParser::popFirstRequest() +{ +#ifndef NDEBUG + auto conn = conn_.lock(); + if (conn) + { + conn->getLoop()->assertInLoopThread(); + } +#endif + requestPipelining_.pop_front(); +} + +void HttpRequestParser::pushResponseToPipelining(const HttpRequestPtr &req, + const HttpResponsePtr &resp, + bool isHeadMethod) +{ +#ifndef NDEBUG + auto conn = conn_.lock(); + if (conn) + { + conn->getLoop()->assertInLoopThread(); + } +#endif + for (auto &iter : requestPipelining_) + { + if (iter.first == req) + { + iter.second = {resp, isHeadMethod}; + return; + } + } +} \ No newline at end of file diff --git a/core/http_request_parser.h b/core/http_request_parser.h new file mode 100644 index 0000000..286ac27 --- /dev/null +++ b/core/http_request_parser.h @@ -0,0 +1,156 @@ +/** + * + * HttpRequestParser.h + * 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 + * + */ + +#pragma once + +#include "core/http_server_callbacks.h" + +#include + +#include +#include +#include +#include +#include + +namespace drogon +{ +class HttpRequestParser : public trantor::NonCopyable, + public std::enable_shared_from_this +{ + public: + enum class HttpRequestParseStatus + { + kExpectMethod, + kExpectRequestLine, + kExpectHeaders, + kExpectBody, + kExpectChunkLen, + kExpectChunkBody, + kExpectLastEmptyChunk, + kGotAll, + }; + + explicit HttpRequestParser(const trantor::TcpConnectionPtr &connPtr); + + // return false if any error + bool parseRequest(trantor::MsgBuffer *buf); + + bool gotAll() const + { + return status_ == HttpRequestParseStatus::kGotAll; + } + + void reset(); + + const HttpRequestPtr &requestImpl() const + { + return request_; + } + + bool firstReq() + { + if (firstRequest_) + { + firstRequest_ = false; + return true; + } + return false; + } + const WebSocketConnectionPtr &webSocketConn() const + { + return websockConnPtr_; + } + void setWebsockConnection(const WebSocketConnectionPtr &conn) + { + websockConnPtr_ = conn; + } + // to support request pipelining(rfc2616-8.1.2.2) + void pushRequestToPipelining(const HttpRequestPtr &req); + HttpRequestPtr getFirstRequest() const; + std::pair getFirstResponse() const; + void popFirstRequest(); + void pushResponseToPipelining(const HttpRequestPtr &req, + const HttpResponsePtr &resp, + bool isHeadMethod); + size_t numberOfRequestsInPipelining() const + { + return requestPipelining_.size(); + } + bool emptyPipelining() + { + return requestPipelining_.empty(); + } + bool isStop() const + { + return stopWorking_; + } + void stop() + { + stopWorking_ = true; + } + size_t numberOfRequestsParsed() const + { + return requestsCounter_; + } + trantor::MsgBuffer &getBuffer() + { + return sendBuffer_; + } + std::vector> &getResponseBuffer() + { + assert(loop_->isInLoopThread()); + if (!responseBuffer_) + { + responseBuffer_ = + std::unique_ptr>>( + new std::vector>); + } + return *responseBuffer_; + } + std::vector &getRequestBuffer() + { + assert(loop_->isInLoopThread()); + if (!requestBuffer_) + { + requestBuffer_ = std::unique_ptr>( + new std::vector); + } + return *requestBuffer_; + } + + private: + HttpRequestPtr makeRequestForPool(HttpRequest *p); + void shutdownConnection(HttpStatusCode code); + bool processRequestLine(const char *begin, const char *end); + HttpRequestParseStatus status_; + trantor::EventLoop *loop_; + HttpRequestPtr request_; + bool firstRequest_{true}; + WebSocketConnectionPtr websockConnPtr_; + std::deque>> + requestPipelining_; + size_t requestsCounter_{0}; + std::weak_ptr conn_; + bool stopWorking_{false}; + trantor::MsgBuffer sendBuffer_; + std::unique_ptr>> + responseBuffer_; + std::unique_ptr> requestBuffer_; + std::vector requestsPool_; + size_t currentChunkLength_; + size_t currentContentLength_{0}; +}; + +} // namespace drogon diff --git a/core/http_response.cpp b/core/http_response.cpp new file mode 100644 index 0000000..27ffaf6 --- /dev/null +++ b/core/http_response.cpp @@ -0,0 +1,677 @@ +/** + * + * @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 +#include "core/application.h" + +#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 = + HttpAppFrameworkImpl::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 = HttpAppFrameworkImpl::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 (HttpAppFrameworkImpl::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 (HttpAppFrameworkImpl::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; + } + } +} +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 (HttpAppFrameworkImpl::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 (HttpAppFrameworkImpl::instance().sendServerHeader()) + { + buffer.append( + HttpAppFrameworkImpl::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_ && + drogon::HttpAppFrameworkImpl::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_ && + drogon::HttpAppFrameworkImpl::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_ && + drogon::HttpAppFrameworkImpl::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_ && + drogon::HttpAppFrameworkImpl::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; +} diff --git a/core/http_response.h b/core/http_response.h new file mode 100644 index 0000000..6b2d0ce --- /dev/null +++ b/core/http_response.h @@ -0,0 +1,670 @@ +/** + * @file HttpResponse.h + * 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 + * + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + + +#include "core/http_utils.h" + +#include "http_message_body.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace drogon { +/// Abstract class for webapp developer to get or set the Http response; +class HttpResponse; +using HttpResponsePtr = std::shared_ptr; + +/** + * @brief This template is used to convert a response object to a custom + * type object. Users must specialize the template for a particular type. + */ +template +T fromResponse(const HttpResponse &resp) { + // LOG_ERROR + // << "You must specialize the fromResponse template for the type of " + // << DrClassMap::demangle(typeid(T).name()); + exit(1); +} + +/** + * @brief This template is used to create a response object from a custom + * type object by calling the newCustomHttpResponse(). Users must specialize + * the template for a particular type. + */ +template +HttpResponsePtr toResponse(T &&) { + // LOG_ERROR << "You must specialize the toResponse template for the type of " + // << DrClassMap::demangle(typeid(T).name()); + exit(1); +} +template <> +HttpResponsePtr toResponse(const Json::Value &pJson); +template <> +HttpResponsePtr toResponse(Json::Value &&pJson); +template <> +inline HttpResponsePtr toResponse(Json::Value &pJson) { + return toResponse((const Json::Value &)pJson); +} + +class HttpResponse { + friend class HttpResponseParser; +public: + /** + * @brief This template enables automatic type conversion. For using this + * template, user must specialize the fromResponse template. For example a + * shared_ptr specialization version is available above, so + * we can use the following code to get a json object: + * @code + * std::shared_ptr jsonPtr = *responsePtr; + * @endcode + * With this template, user can use their favorite JSON library instead of + * the default jsoncpp library or convert the response to an object of any + * custom type. + */ + template + operator T() const { + return fromResponse(*this); + } + + /** + * @brief This template enables explicit type conversion, see the above + * template. + */ + template + T as() const { + } + + /// Get the status code such as 200, 404 + virtual HttpStatusCode statusCode() const { + return statusCode_; + } + + HttpStatusCode getStatusCode() const { + return statusCode(); + } + + /// Set the status code of the response. + virtual void setStatusCode(HttpStatusCode code) { + statusCode_ = code; + setStatusMessage(statusCodeToString(code)); + } + + /// Get the creation timestamp of the response. + virtual const trantor::Date &creationDate() const { + return creationDate_; + } + + const trantor::Date &getCreationDate() const { + return creationDate(); + } + + /// Set the http version, http1.0 or http1.1 + virtual void setVersion(const Version v) { + version_ = v; + if (version_ == Version::kHttp10) { + closeConnection_ = true; + } + } + + /// Set if close the connection after the request is sent. + /** + * @param on if the parameter is false, the connection keeps alive on the + * condition that the client request has a 'keep-alive' head, otherwise it + * is closed immediately after sending the last byte of the response. It's + * false by default when the response is created. + */ + virtual void setCloseConnection(bool on) { + closeConnection_ = on; + } + + /// Get the status set by the setCloseConnetion() method. + + virtual bool ifCloseConnection() const { + return closeConnection_; + } + + /// Set the response content type, such as text/html, text/plaint, image/png + /// and so on. If the content type + /// is a text type, the character set is utf8. + virtual void setContentTypeCode(ContentType type) { + contentType_ = type; + setContentType(webContentTypeToString(type)); + flagForParsingContentType_ = true; + } + + + /// Set the response content type and the content-type string, The string + /// must contain the header name and CRLF. + /// For example, "content-type: text/plain\r\n" + void setContentTypeCodeAndCustomString(ContentType type, + const string_view &typeString) { + setContentTypeCodeAndCustomString(type, + typeString.data(), + typeString.length()); + } + template + void setContentTypeCodeAndCustomString(ContentType type, + const char (&typeString)[N]) { + assert(N > 0); + setContentTypeCodeAndCustomString(type, typeString, N - 1); + } + + /// Set the reponse content type and the character set. + /// virtual void setContentTypeCodeAndCharacterSet(ContentType type, const + /// std::string &charSet = "utf-8") = 0; + + /// Get the response content type. + virtual ContentType contentType() const { + if (!flagForParsingContentType_) { + flagForParsingContentType_ = true; + auto &contentTypeString = getHeaderBy("content-type"); + if (contentTypeString == "") { + contentType_ = CT_NONE; + } else { + auto pos = contentTypeString.find(';'); + if (pos != std::string::npos) { + contentType_ = parseContentType( + string_view(contentTypeString.data(), pos)); + } else { + contentType_ = + parseContentType(string_view(contentTypeString)); + } + } + } + return contentType_; + } + + ContentType getContentType() const { + return contentType(); + } + + + const std::string &getHeaderBy(const std::string &lowerKey) const { + const static std::string defaultVal; + auto iter = headers_.find(lowerKey); + if (iter == headers_.end()) { + return defaultVal; + } + return iter->second; + } + + + /// Get the header string identified by the key parameter. + /** + * @note + * If there is no the header, a empty string is retured. + * The key is case insensitive + */ + virtual const std::string &getHeader(const std::string &key) const { + auto field = key; + transform(field.begin(), field.end(), field.begin(), ::tolower); + return getHeaderBy(field); + } + + virtual const std::string &getHeader(std::string &&key) const { + transform(key.begin(), key.end(), key.begin(), ::tolower); + return getHeaderBy(key); + } + + + /// Remove the header identified by the key parameter. + virtual void removeHeader(const std::string &key) { + auto field = key; + transform(field.begin(), field.end(), field.begin(), ::tolower); + removeHeaderBy(field); + } + + /// Remove the header identified by the key parameter. + virtual void removeHeader(std::string &&key) { + transform(key.begin(), key.end(), key.begin(), ::tolower); + removeHeaderBy(key); + } + + void removeHeaderBy(const std::string &lowerKey) { + headers_.erase(lowerKey); + } + + + /// Get all headers of the response + virtual const std::unordered_map &headers() + const { + return headers_; + } + + /// Get all headers of the response + const std::unordered_map &getHeaders() const { + return headers(); + } + + /// Add a header. + virtual void addHeader(const std::string &key, + const std::string &value) { + fullHeaderString_.reset(); + auto field = key; + transform(field.begin(), field.end(), field.begin(), ::tolower); + headers_[std::move(field)] = value; + } + + /// Add a header. + virtual void addHeader(const std::string &key, std::string &&value) { + fullHeaderString_.reset(); + auto field = key; + transform(field.begin(), field.end(), field.begin(), ::tolower); + headers_[std::move(field)] = std::move(value); + } + + void addHeader(const char *start, const char *colon, const char *end); + + /// Add a cookie + virtual void addCookie(const std::string &key, + const std::string &value) { + cookies_[key] = Cookie(key, value); + } + + /// Add a cookie + virtual void addCookie(const Cookie &cookie) { + cookies_[cookie.key()] = cookie; + } + + virtual void addCookie(Cookie &&cookie) { + cookies_[cookie.key()] = std::move(cookie); + } + + /// Get the cookie identified by the key parameter. + /// If there is no the cookie, the empty cookie is retured. + virtual const Cookie &getCookie(const std::string &key) const { + static const Cookie defaultCookie; + auto it = cookies_.find(key); + if (it != cookies_.end()) { + return it->second; + } + return defaultCookie; + } + + /// Get all cookies. + virtual const std::unordered_map &cookies() + const { + return cookies_; + } + + /// Get all cookies. + const std::unordered_map &getCookies() const { + return cookies(); + } + + /// Remove the cookie identified by the key parameter. + virtual void removeCookie(const std::string &key) { + cookies_.erase(key); + } + + /// Set the response body(content). + /** + * @note The body must match the content type + */ + virtual void setBody(const std::string &body) { + bodyPtr_ = std::make_shared(body); + } + /// Set the response body(content). + virtual void setBody(std::string &&body) { + bodyPtr_ = std::make_shared(std::move(body)); + } + + /// Set the response body(content). + template + void setBody(const char (&body)[N]) { + assert(strnlen(body, N) == N - 1); + setBody(body, N - 1); + } + + /// Get the response body. + string_view body() const { + return string_view{ getBodyData(), getBodyLength() }; + } + + /// Get the response body. + string_view getBody() const { + return body(); + } + + /// Return the enum type version of the response. + /** + * kHttp10 means Http version is 1.0 + * kHttp11 means Http verison is 1.1 + */ + virtual Version version() const { + return version_; + } + + /// Return the enum type version of the response. + Version getVersion() const { + return version(); + } + + /// Reset the reponse object to its initial state + virtual void clear(); + + /// Set the expiration time of the response cache in memory. + /// in seconds, 0 means always cache, negative means not cache, default is + /// -1. + virtual void setExpiredTime(ssize_t expiredTime) { + expriedTime_ = expiredTime; + datePos_ = std::string::npos; + if (expriedTime_ < 0 && version_ == Version::kHttp10) { + fullHeaderString_.reset(); + } + } + + + /// Get the expiration time of the response. + virtual ssize_t expiredTime() const { + return expriedTime_; + } + ssize_t getExpiredTime() const { + return expiredTime(); + } + + /// Get the json object from the server response. + /// If the response is not in json format, then a empty shared_ptr is + /// retured. + virtual const std::shared_ptr &jsonObject() const { + // Not multi-thread safe but good, because we basically call this + // function in a single thread + if (!flagForParsingJson_) { + flagForParsingJson_ = true; + parseJson(); + } + return jsonPtr_; + } + const std::shared_ptr &getJsonObject() const { + return jsonObject(); + } + + + void setJsonObject(const Json::Value &pJson) { + flagForParsingJson_ = true; + flagForSerializingJson_ = false; + jsonPtr_ = std::make_shared(pJson); + } + void setJsonObject(Json::Value &&pJson) { + flagForParsingJson_ = true; + flagForSerializingJson_ = false; + jsonPtr_ = std::make_shared(std::move(pJson)); + } + bool shouldBeCompressed() const; + void generateBodyFromJson() const; + const std::string &sendfileName() const { + return sendfileName_; + } + void setSendfile(const std::string &filename) { + sendfileName_ = filename; + } + void makeHeaderString() { + fullHeaderString_ = std::make_shared(128); + makeHeaderString(*fullHeaderString_); + } + + void gunzip() { + if (bodyPtr_) { + auto gunzipBody = + utils::gzipDecompress(bodyPtr_->data(), bodyPtr_->length()); + removeHeader("content-encoding"); + bodyPtr_ = + std::make_shared(move(gunzipBody)); + } + } +#ifdef USE_BROTLI + void brDecompress() { + if (bodyPtr_) { + auto gunzipBody = + utils::brotliDecompress(bodyPtr_->data(), bodyPtr_->length()); + removeHeader("content-encoding"); + bodyPtr_ = + std::make_shared(move(gunzipBody)); + } + } +#endif + + + + /** + * @brief Get the error message of parsing the JSON body received from peer. + * This method usually is called after getting a empty shared_ptr object + * by the getJsonObject() method. + * + * @return const std::string& The error message. An empty string is returned + * when no error occurs. + */ + virtual const std::string &getJsonError() const { + const static std::string none{ "" }; + if (jsonParsingErrorPtr_) + return *jsonParsingErrorPtr_; + return none; + } + + /** + * @brief Set the reponse object to the pass-through mode or not. It's not + * by default when a new response object is created. + * In pass-through mode, no addtional headers (including server, date, + * content-type and content-length, etc.) are added to the response. This + * mode is useful for some applications such as a proxy. + * + * @param flag + */ + virtual void setPassThrough(bool flag) { + passThrough_ = flag; + } + + + void redirect(const std::string &url) { + headers_["location"] = url; + } + + std::shared_ptr renderToBuffer(); + void renderToBuffer(trantor::MsgBuffer &buffer); + std::shared_ptr renderHeaderForHeadMethod(); + + + /* The following methods are a series of factory methods that help users + * create response objects. */ + + /// Create a normal response with a status code of 200ok and a content type + /// of text/html. + static HttpResponsePtr newHttpResponse(); + /// Create a response which returns a 404 page. + static HttpResponsePtr newNotFoundResponse(); + /// Create a response which returns a json object. Its content type is set + /// to set/json. + static HttpResponsePtr newHttpJsonResponse(const Json::Value &data); + static HttpResponsePtr newHttpJsonResponse(Json::Value &&data); + /// Create a response that returns a page rendered by a view named + /// viewName. + /** + * @param viewName The name of the view + * @param data is the data displayed on the page. + * @note For more details, see the wiki pages, the "View" section. + */ + static HttpResponsePtr newHttpViewResponse( + const std::string &viewName, + const HttpViewData &data = HttpViewData()); + + /// Create a response that returns a redirection page, redirecting to + /// another page located in the location parameter. + /** + * @param location The location to redirect + * @param status The HTTP status code, k302Found by default. Users could set + * it to one of the 301, 302, 303, 307, ... + */ + static HttpResponsePtr newRedirectionResponse( + const std::string &location, + HttpStatusCode status = k302Found); + + /// Create a response that returns a file to the client. + /** + * @param fullPath is the full path to the file. + * @param attachmentFileName if the parameter is not empty, the browser + * does not open the file, but saves it as an attachment. + * @param type if the parameter is CT_NONE, the content type is set by + * drogon based on the file extension. + */ + static HttpResponsePtr newFileResponse( + const std::string &fullPath, + const std::string &attachmentFileName = "", + ContentType type = CT_NONE); + + /** + * @brief Create a custom HTTP response object. For using this template, + * users must specialize the toResponse template. + */ + template + static HttpResponsePtr newCustomHttpResponse(T &&obj) { + return toResponse(std::forward(obj)); + } + + HttpResponse() : + creationDate_(trantor::Date::now()) { + } + HttpResponse(HttpStatusCode code, ContentType type) : + statusCode_(code), + statusMessage_(statusCodeToString(code)), + creationDate_(trantor::Date::now()), + contentType_(type), + flagForParsingContentType_(true), + contentTypeString_(webContentTypeToString(type)) { + } + + virtual ~HttpResponse(); + + + // virtual void setContentTypeCodeAndCharacterSet(ContentType type, const + // std::string &charSet = "utf-8") + // { + // contentType_ = type; + // setContentType(webContentTypeAndCharsetToString(type, charSet)); + // } + + + + void swap(HttpResponse &that) noexcept; +protected: + void makeHeaderString(trantor::MsgBuffer &headerString); + +private: + virtual void setBody(const char *body, size_t len) { + bodyPtr_ = std::make_shared(body, len); + } + virtual const char *getBodyData() const { + if (!flagForSerializingJson_ && jsonPtr_) { + generateBodyFromJson(); + } else if (!bodyPtr_) { + return nullptr; + } + return bodyPtr_->data(); + } + + virtual size_t getBodyLength() const { + if (bodyPtr_) + return bodyPtr_->length(); + return 0; + } + + + void parseJson() const; + + virtual void setContentTypeCodeAndCustomString( + ContentType type, + const char *typeString, + size_t typeStringLength) { + contentType_ = type; + flagForParsingContentType_ = true; + setContentType(string_view{ typeString, typeStringLength }); + } + + + std::unordered_map headers_; + std::unordered_map cookies_; + + HttpStatusCode statusCode_{ kUnknown }; + string_view statusMessage_; + + trantor::Date creationDate_; + Version version_{ Version::kHttp11 }; + bool closeConnection_{ false }; + mutable std::shared_ptr bodyPtr_; + ssize_t expriedTime_{ -1 }; + std::string sendfileName_; + mutable std::shared_ptr jsonPtr_; + + std::shared_ptr fullHeaderString_; + mutable std::shared_ptr httpString_; + mutable size_t datePos_{ static_cast(-1) }; + mutable int64_t httpStringDate_{ -1 }; + mutable bool flagForParsingJson_{ false }; + mutable bool flagForSerializingJson_{ true }; + mutable ContentType contentType_{ CT_TEXT_PLAIN }; + mutable bool flagForParsingContentType_{ false }; + mutable std::shared_ptr jsonParsingErrorPtr_; + string_view contentTypeString_{ + "Content-Type: text/html; charset=utf-8\r\n" + }; + bool passThrough_{ false }; + void setContentType(const string_view &contentType) { + contentTypeString_ = contentType; + } + void setStatusMessage(const string_view &message) { + statusMessage_ = message; + } + +}; + +template <> +inline HttpResponsePtr toResponse(const Json::Value &pJson) { + return HttpResponse::newHttpJsonResponse(pJson); +} + +template <> +inline HttpResponsePtr toResponse(Json::Value &&pJson) { + return HttpResponse::newHttpJsonResponse(std::move(pJson)); +} + +template <> +inline std::shared_ptr fromResponse(const HttpResponse &resp) { + return resp.getJsonObject(); +} + +using HttpResponsePtr = std::shared_ptr; + +inline void swap(HttpResponse &one, HttpResponse &two) noexcept { + one.swap(two); +} + +} // namespace drogon diff --git a/core/http_server.cpp b/core/http_server.cpp index 1e23e25..4e0685e 100644 --- a/core/http_server.cpp +++ b/core/http_server.cpp @@ -1,19 +1,25 @@ #include "http_server.h" -#include "request.h" +#include + #include "application.h" +#include "request.h" + +#include #define LOG_VERBOSE 0 +using namespace std::placeholders; + void HTTPServer::http_callback_handler(Request *request) { - Application::handle_request(request); + Application::handle_request(request); } void HTTPServer::httpEnterCallbackDefault(const HTTPParser &httpParser, const HttpSession::Ptr &session) { Request *request = RequestPool::get_request(); - request->http_parser = &httpParser; - request->session = &session; + request->http_parser = &httpParser; + request->session = &session; request->setup_url_stack(); @@ -21,7 +27,7 @@ void HTTPServer::httpEnterCallbackDefault(const HTTPParser &httpParser, const Ht std::cout << "method:" << http_method_str(static_cast(httpParser.method())) << std::endl; #endif - http_callback_handler(request); + http_callback_handler(request); } void HTTPServer::wsEnterCallbackDefault(const HttpSession::Ptr &httpSession, WebSocketFormat::WebSocketFrameType opcode, const std::string &payload) { @@ -87,10 +93,195 @@ void HTTPServer::main_loop_old() { } } +void HTTPServer::configure() { +} +void HTTPServer::initialize() { +} + +//drogon -> void HttpAppFrameworkImpl::run() +void HTTPServer::main_loop() { + + + if (!getLoop()->isInLoopThread()) { + getLoop()->moveToCurrentThread(); + } + + LOG_TRACE << "Start to run..."; + +/* + trantor::AsyncFileLogger asyncFileLogger; + + // Create dirs for cache files + for (int i = 0; i < 256; ++i) { + char dirName[4]; + snprintf(dirName, sizeof(dirName), "%02x", i); + std::transform(dirName, dirName + 2, dirName, toupper); + utils::createPath(getUploadPath() + "/tmp/" + dirName); + } + */ + + /* + if (runAsDaemon_) { + // go daemon! + godaemon(); +#ifdef __linux__ + getLoop()->resetTimerQueue(); +#endif + getLoop()->resetAfterFork(); + } + */ + +/* + // set relaunching + if (relaunchOnError_) { +#ifndef _WIN32 + while (true) { + int child_status = 0; + auto child_pid = fork(); + if (child_pid < 0) { + LOG_ERROR << "fork error"; + abort(); + } else if (child_pid == 0) { + // child + break; + } + waitpid(child_pid, &child_status, 0); + sleep(1); + LOG_INFO << "start new process"; + } + getLoop()->resetAfterFork(); +#endif + }*/ + + //signal(SIGTERM, TERMFunction); + +/* + // set logger + if (!logPath_.empty()) { +#ifdef _WIN32 + if (_access(logPath_.c_str(), 06) != 0) +#else + if (access(logPath_.c_str(), R_OK | W_OK) != 0) +#endif + { + LOG_ERROR << "log file path not exist"; + abort(); + } else { + std::string baseName = logfileBaseName_; + if (baseName == "") { + baseName = "drogon"; + } + asyncFileLogger.setFileName(baseName, ".log", logPath_); + asyncFileLogger.startLogging(); + trantor::Logger::setOutputFunction( + [&](const char *msg, const uint64_t len) { + asyncFileLogger.output(msg, len); + }, + [&]() { asyncFileLogger.flush(); }); + asyncFileLogger.setFileSizeLimit(logfileSize_); + } + } +*/ + +/* + if (relaunchOnError_) { + LOG_INFO << "Start child process"; + } +*/ + + // now start runing!! + + _running = true; + +/* +#ifndef _WIN32 + if (!libFilePaths_.empty()) { + sharedLibManagerPtr_ = std::unique_ptr( + new SharedLibManager(libFilePaths_, libFileOutputPath_)); + } +#endif +*/ + + // Create all listeners. + auto ioLoops = listenerManagerPtr_->createListeners( + std::bind(&HttpAppFrameworkImpl::onAsyncRequest, this, _1, _2), + std::bind(&HttpAppFrameworkImpl::onNewWebsockRequest, this, _1, _2, _3), + std::bind(&HttpAppFrameworkImpl::onConnection, this, _1), + idleConnectionTimeout_, + sslCertPath_, + sslKeyPath_, + threads, + syncAdvices_); + + + assert(ioLoops.size() == threads); + for (size_t i = 0; i < threads; ++i) { + ioLoops[i]->setIndex(i); + } + + + getLoop()->setIndex(threads); + + // A fast database client instance should be created in the main event + // loop, so put the main loop into ioLoops. + + ioLoops.push_back(getLoop()); + + /* + dbClientManagerPtr_->createDbClients(ioLoops); + httpCtrlsRouterPtr_->init(ioLoops); + httpSimpleCtrlsRouterPtr_->init(ioLoops); + staticFileRouterPtr_->init(ioLoops); + websockCtrlsRouterPtr_->init(); + */ + + getLoop()->queueInLoop([this]() { + // Let listener event loops run when everything is ready. + listenerManagerPtr_->startListening(); + + for (auto &adv : beginningAdvices_) { + adv(); + } + + beginningAdvices_.clear(); + }); + + getLoop()->loop(); +} + +trantor::EventLoop *HttpAppFrameworkImpl::get_loop() const +{ + static trantor::EventLoop loop; + return &loop; +} + HTTPServer::HTTPServer() { port = 80; threads = 4; listenBuilder = nullptr; + _running = false; +} + +HttpServer::HTTPServer( + EventLoop *loop, + const InetAddress &listenAddr, + const std::string &name, + const std::vector> + &syncAdvices) +#ifdef __linux__ + : server_(loop, listenAddr, name.c_str()), +#else + : server_(loop, listenAddr, name.c_str(), true, false), +#endif + httpAsyncCallback_(defaultHttpAsyncCallback), + newWebsocketCallback_(defaultWebSockAsyncCallback), + connectionCallback_(defaultConnectionCallback), + syncAdvices_(syncAdvices) +{ + server_.setConnectionCallback( + std::bind(&HttpServer::onConnection, this, _1)); + server_.setRecvMessageCallback( + std::bind(&HttpServer::onMessage, this, _1, _2)); } HTTPServer::~HTTPServer() { diff --git a/core/http_server.h b/core/http_server.h index fdbefa3..cf8f127 100644 --- a/core/http_server.h +++ b/core/http_server.h @@ -12,6 +12,13 @@ #include #include +#include +#include +#include + +#include "core/http_server_callbacks.h" +#include "core/http_request_parser.h" + using namespace brynet; using namespace brynet::net; using namespace brynet::net::http; @@ -23,25 +30,78 @@ public: int port; int threads; std::shared_ptr service; - wrapper::HttpListenerBuilder *listenBuilder; + wrapper::HttpListenerBuilder *listenBuilder; - static void http_callback_handler(Request *response); + static void http_callback_handler(Request *response); static void httpEnterCallbackDefault(const HTTPParser &httpParser, const HttpSession::Ptr &session); static void wsEnterCallbackDefault(const HttpSession::Ptr &httpSession, WebSocketFormat::WebSocketFrameType opcode, const std::string &payload); - virtual void configure_old(); + virtual void configure_old(); virtual void initialize_old(); void main_loop_old(); - //virtual void configure(); - //virtual void initialize(); + virtual void configure(); + virtual void initialize(); - //void main_loop(); + void main_loop(); - HTTPServer(); - virtual ~HTTPServer(); + trantor::EventLoop *getLoop() const { + return server_.getLoop(); + } + + void setHttpAsyncCallback(const HttpAsyncCallback &cb) { + httpAsyncCallback_ = cb; + } + void setNewWebsocketCallback(const WebSocketNewAsyncCallback &cb) { + newWebsocketCallback_ = cb; + } + void setConnectionCallback(const trantor::ConnectionCallback &cb) { + connectionCallback_ = cb; + } + void setIoLoopThreadPool( + const std::shared_ptr &pool) { + server_.setIoLoopThreadPool(pool); + } + void setIoLoopNum(int numThreads) { + server_.setIoLoopNum(numThreads); + } + void kickoffIdleConnections(size_t timeout) { + server_.kickoffIdleConnections(timeout); + } + trantor::EventLoop *getLoop() { + return server_.getLoop(); + } + std::vector getIoLoops() { + return server_.getIoLoops(); + } + + void start(); + void stop(); + + void enableSSL(const std::string &certPath, const std::string &keyPath) { + server_.enableSSL(certPath, keyPath); + } + + HTTPServer(); + HTTPServer(trantor::EventLoop *loop, const trantor::InetAddress &listenAddr, const std::string &name, + const std::vector > &syncAdvices); + + virtual ~HTTPServer(); + +protected: + void onConnection(const trantor::TcpConnectionPtr &conn); + void onMessage(const trantor::TcpConnectionPtr &, trantor::MsgBuffer *); + void onRequests(const trantor::TcpConnectionPtr &, const std::vector &, const std::shared_ptr &); + void sendResponse(const trantor::TcpConnectionPtr &, const HttpResponsePtr &, bool isHeadMethod); + void sendResponses(const trantor::TcpConnectionPtr &conn, const std::vector > &responses, trantor::MsgBuffer &buffer); + + trantor::TcpServer server_; + HttpAsyncCallback httpAsyncCallback_; + WebSocketNewAsyncCallback newWebsocketCallback_; + trantor::ConnectionCallback connectionCallback_; + const std::vector > &syncAdvices_; }; #endif \ No newline at end of file diff --git a/core/http_server_callbacks.h b/core/http_server_callbacks.h new file mode 100644 index 0000000..edceb5f --- /dev/null +++ b/core/http_server_callbacks.h @@ -0,0 +1,19 @@ +#ifndef HTTP_SERVER_CALLBACKS_H +#define HTTP_SERVER_CALLBACKS_H + +#include + +#include + +class HttpRequest; +using HttpRequestPtr = std::shared_ptr; +class HttpResponse; +using HttpResponsePtr = std::shared_ptr; +class WebSocketConnectionImpl; +using WebSocketConnectionPtr = std::shared_ptr; + +using HttpAsyncCallback = std::function &&)>; +using WebSocketNewAsyncCallback = std::function &&, + const WebSocketConnectionPtr &)>; + +#endif \ No newline at end of file diff --git a/core/http_types.h b/core/http_types.h new file mode 100644 index 0000000..9d1bded --- /dev/null +++ b/core/http_types.h @@ -0,0 +1,144 @@ +/** + * HttpTypes.h + * 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 + * + */ + +#pragma once +#include +#include + +namespace drogon +{ +enum HttpStatusCode +{ + // rfc2616-6.1.1 + kUnknown = 0, + k100Continue = 100, + k101SwitchingProtocols = 101, + k200OK = 200, + k201Created = 201, + k202Accepted = 202, + k203NonAuthoritativeInformation = 203, + k204NoContent = 204, + k205ResetContent = 205, + k206PartialContent = 206, + k300MultipleChoices = 300, + k301MovedPermanently = 301, + k302Found = 302, + k303SeeOther = 303, + k304NotModified = 304, + k305UseProxy = 305, + k307TemporaryRedirect = 307, + k308PermanentRedirect = 308, + k400BadRequest = 400, + k401Unauthorized = 401, + k402PaymentRequired = 402, + k403Forbidden = 403, + k404NotFound = 404, + k405MethodNotAllowed = 405, + k406NotAcceptable = 406, + k407ProxyAuthenticationRequired = 407, + k408RequestTimeout = 408, + k409Conflict = 409, + k410Gone = 410, + k411LengthRequired = 411, + k412PreconditionFailed = 412, + k413RequestEntityTooLarge = 413, + k414RequestURITooLarge = 414, + k415UnsupportedMediaType = 415, + k416RequestedRangeNotSatisfiable = 416, + k417ExpectationFailed = 417, + k421MisdirectedRequest = 421, + k425TooEarly = 425, + k426UpgradeRequired = 426, + k428PreconditionRequired = 428, + k429TooManyRequests = 429, + k431RequestHeaderFieldsTooLarge = 431, + k451UnavailableForLegalReasons = 451, + k500InternalServerError = 500, + k501NotImplemented = 501, + k502BadGateway = 502, + k503ServiceUnavailable = 503, + k504GatewayTimeout = 504, + k505HTTPVersionNotSupported = 505, + k510NotExtended = 510, +}; + +enum class Version +{ + kUnknown = 0, + kHttp10, + kHttp11 +}; + +enum ContentType +{ + CT_NONE = 0, + CT_APPLICATION_JSON, + CT_TEXT_PLAIN, + CT_TEXT_HTML, + CT_APPLICATION_X_FORM, + CT_APPLICATION_X_JAVASCRIPT, + CT_TEXT_CSS, + CT_TEXT_XML, + CT_APPLICATION_XML, + CT_TEXT_XSL, + CT_APPLICATION_WASM, + CT_APPLICATION_OCTET_STREAM, + CT_APPLICATION_X_FONT_TRUETYPE, + CT_APPLICATION_X_FONT_OPENTYPE, + CT_APPLICATION_FONT_WOFF, + CT_APPLICATION_FONT_WOFF2, + CT_APPLICATION_VND_MS_FONTOBJ, + CT_APPLICATION_PDF, + CT_IMAGE_SVG_XML, + CT_IMAGE_PNG, + CT_IMAGE_JPG, + CT_IMAGE_GIF, + CT_IMAGE_XICON, + CT_IMAGE_ICNS, + CT_IMAGE_BMP, + CT_MULTIPART_FORM_DATA, + CT_CUSTOM +}; + +enum HttpMethod +{ + Get = 0, + Post, + Head, + Put, + Delete, + Options, + Patch, + Invalid +}; + +enum class ReqResult +{ + Ok, + BadResponse, + NetworkFailure, + BadServerAddress, + Timeout +}; + +enum class WebSocketMessageType +{ + Text, + Binary, + Ping, + Pong, + Close, + Unknown +}; + +} // namespace drogon diff --git a/core/http_utils.cpp b/core/http_utils.cpp new file mode 100644 index 0000000..53a0759 --- /dev/null +++ b/core/http_utils.cpp @@ -0,0 +1,580 @@ +/** + * + * @file HttpUtils.h + * 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_utils.h" + +#include "core/utilities.h" + +#include +#include + +namespace drogon +{ +const string_view &webContentTypeToString(ContentType contenttype) +{ + switch (contenttype) + { + case CT_TEXT_HTML: + { + static string_view sv = + "Content-Type: text/html; charset=utf-8\r\n"; + return sv; + } + case CT_APPLICATION_X_FORM: + { + static string_view sv = + "Content-Type: application/x-www-form-urlencoded\r\n"; + return sv; + } + case CT_APPLICATION_XML: + { + static string_view sv = + "Content-Type: application/xml; charset=utf-8\r\n"; + return sv; + } + case CT_APPLICATION_JSON: + { + static string_view sv = + "Content-Type: application/json; charset=utf-8\r\n"; + return sv; + } + case CT_APPLICATION_X_JAVASCRIPT: + { + static string_view sv = + "Content-Type: application/x-javascript; charset=utf-8\r\n"; + return sv; + } + case CT_TEXT_CSS: + { + static string_view sv = "Content-Type: text/css; charset=utf-8\r\n"; + return sv; + } + case CT_TEXT_XML: + { + static string_view sv = "Content-Type: text/xml; charset=utf-8\r\n"; + return sv; + } + case CT_TEXT_XSL: + { + static string_view sv = "Content-Type: text/xsl; charset=utf-8\r\n"; + return sv; + } + case CT_APPLICATION_OCTET_STREAM: + { + static string_view sv = + "Content-Type: application/octet-stream\r\n"; + return sv; + } + case CT_IMAGE_SVG_XML: + { + static string_view sv = "Content-Type: image/svg+xml\r\n"; + return sv; + } + case CT_APPLICATION_X_FONT_TRUETYPE: + { + static string_view sv = + "Content-Type: application/x-font-truetype\r\n"; + return sv; + } + case CT_APPLICATION_X_FONT_OPENTYPE: + { + static string_view sv = + "Content-Type: application/x-font-opentype\r\n"; + return sv; + } + case CT_APPLICATION_FONT_WOFF: + { + static string_view sv = "Content-Type: application/font-woff\r\n"; + return sv; + } + case CT_APPLICATION_FONT_WOFF2: + { + static string_view sv = "Content-Type: application/font-woff2\r\n"; + return sv; + } + case CT_APPLICATION_VND_MS_FONTOBJ: + { + static string_view sv = + "Content-Type: application/vnd.ms-fontobject\r\n"; + return sv; + } + case CT_APPLICATION_PDF: + { + static string_view sv = "Content-Type: application/pdf\r\n"; + return sv; + } + case CT_IMAGE_PNG: + { + static string_view sv = "Content-Type: image/png\r\n"; + return sv; + } + case CT_IMAGE_JPG: + { + static string_view sv = "Content-Type: image/jpeg\r\n"; + return sv; + } + case CT_IMAGE_GIF: + { + static string_view sv = "Content-Type: image/gif\r\n"; + return sv; + } + case CT_IMAGE_XICON: + { + static string_view sv = "Content-Type: image/x-icon\r\n"; + return sv; + } + case CT_IMAGE_BMP: + { + static string_view sv = "Content-Type: image/bmp\r\n"; + return sv; + } + case CT_IMAGE_ICNS: + { + static string_view sv = "Content-Type: image/icns\r\n"; + return sv; + } + case CT_APPLICATION_WASM: + { + static string_view sv = "Content-Type: application/wasm\r\n"; + return sv; + } + default: + case CT_TEXT_PLAIN: + { + static string_view sv = + "Content-Type: text/plain; charset=utf-8\r\n"; + return sv; + } + } +} + +const string_view &statusCodeToString(int code) +{ + switch (code) + { + case 100: + { + static string_view sv = "Continue"; + return sv; + } + case 101: + { + static string_view sv = "Switching Protocols"; + return sv; + } + case 200: + { + static string_view sv = "OK"; + return sv; + } + case 201: + { + static string_view sv = "Created"; + return sv; + } + case 202: + { + static string_view sv = "Accepted"; + return sv; + } + case 203: + { + static string_view sv = "Non-Authoritative Information"; + return sv; + } + case 204: + { + static string_view sv = "No Content"; + return sv; + } + case 205: + { + static string_view sv = "Reset Content"; + return sv; + } + case 206: + { + static string_view sv = "Partial Content"; + return sv; + } + case 300: + { + static string_view sv = "Multiple Choices"; + return sv; + } + case 301: + { + static string_view sv = "Moved Permanently"; + return sv; + } + case 302: + { + static string_view sv = "Found"; + return sv; + } + case 303: + { + static string_view sv = "See Other"; + return sv; + } + case 304: + { + static string_view sv = "Not Modified"; + return sv; + } + case 305: + { + static string_view sv = "Use Proxy"; + return sv; + } + case 307: + { + static string_view sv = "Temporary Redirect"; + return sv; + } + case 308: + { + static string_view sv = "Permanent Redirect"; + return sv; + } + case 400: + { + static string_view sv = "Bad Request"; + return sv; + } + case 401: + { + static string_view sv = "Unauthorized"; + return sv; + } + case 402: + { + static string_view sv = "Payment Required"; + return sv; + } + case 403: + { + static string_view sv = "Forbidden"; + return sv; + } + case 404: + { + static string_view sv = "Not Found"; + return sv; + } + case 405: + { + static string_view sv = "Method Not Allowed"; + return sv; + } + case 406: + { + static string_view sv = "Not Acceptable"; + return sv; + } + case 407: + { + static string_view sv = "Proxy Authentication Required"; + return sv; + } + case 408: + { + static string_view sv = "Request Time-out"; + return sv; + } + case 409: + { + static string_view sv = "Conflict"; + return sv; + } + case 410: + { + static string_view sv = "Gone"; + return sv; + } + case 411: + { + static string_view sv = "Length Required"; + return sv; + } + case 412: + { + static string_view sv = "Precondition Failed"; + return sv; + } + case 413: + { + static string_view sv = "Request Entity Too Large"; + return sv; + } + case 414: + { + static string_view sv = "Request-URI Too Large"; + return sv; + } + case 415: + { + static string_view sv = "Unsupported Media Type"; + return sv; + } + case 416: + { + static string_view sv = "Requested Range Not Satisfiable"; + return sv; + } + case 417: + { + static string_view sv = "Expectation Failed"; + return sv; + } + case 421: + { + static string_view sv = "Misdirected Request"; + return sv; + } + case 425: + { + static string_view sv = "Too Early"; + return sv; + } + case 426: + { + static string_view sv = "Upgrade Required"; + return sv; + } + case 428: + { + static string_view sv = "Precondition Required"; + return sv; + } + case 429: + { + static string_view sv = "Too Many Requests"; + return sv; + } + case 431: + { + static string_view sv = "Request Header Fields Too Large"; + return sv; + } + case 451: + { + static string_view sv = "Unavailable For Legal Reasons"; + return sv; + } + case 500: + { + static string_view sv = "Internal Server Error"; + return sv; + } + case 501: + { + static string_view sv = "Not Implemented"; + return sv; + } + case 502: + { + static string_view sv = "Bad Gateway"; + return sv; + } + case 503: + { + static string_view sv = "Service Unavailable"; + return sv; + } + case 504: + { + static string_view sv = "Gateway Time-out"; + return sv; + } + case 505: + { + static string_view sv = "HTTP Version Not Supported"; + return sv; + } + case 510: + { + static string_view sv = "Not Extended"; + return sv; + } + default: + if (code >= 100 && code < 200) + { + static string_view sv = "Informational"; + return sv; + } + else if (code >= 200 && code < 300) + { + static string_view sv = "Successful"; + return sv; + } + else if (code >= 300 && code < 400) + { + static string_view sv = "Redirection"; + return sv; + } + else if (code >= 400 && code < 500) + { + static string_view sv = "Bad Request"; + return sv; + } + else if (code >= 500 && code < 600) + { + static string_view sv = "Server Error"; + return sv; + } + else + { + static string_view sv = "Undefined Error"; + return sv; + } + } +} + +ContentType getContentType(const std::string &fileName) +{ + std::string extName; + auto pos = fileName.rfind('.'); + if (pos != std::string::npos) + { + extName = fileName.substr(pos + 1); + transform(extName.begin(), extName.end(), extName.begin(), tolower); + } + switch (extName.length()) + { + case 0: + return CT_APPLICATION_OCTET_STREAM; + case 2: + { + if (extName == "js") + return CT_APPLICATION_X_JAVASCRIPT; + return CT_APPLICATION_OCTET_STREAM; + } + case 3: + { + switch (extName[0]) + { + case 'b': + if (extName == "bmp") + return CT_IMAGE_BMP; + break; + case 'c': + if (extName == "css") + return CT_TEXT_CSS; + break; + case 'e': + if (extName == "eot") + return CT_APPLICATION_VND_MS_FONTOBJ; + break; + case 'g': + if (extName == "gif") + return CT_IMAGE_GIF; + break; + case 'i': + if (extName == "ico") + return CT_IMAGE_XICON; + break; + case 'j': + if (extName == "jpg") + return CT_IMAGE_JPG; + break; + case 'o': + if (extName == "otf") + return CT_APPLICATION_X_FONT_OPENTYPE; + break; + case 'p': + if (extName == "png") + return CT_IMAGE_PNG; + else if (extName == "pdf") + return CT_APPLICATION_PDF; + break; + case 's': + if (extName == "svg") + return CT_IMAGE_SVG_XML; + break; + case 't': + if (extName == "txt") + return CT_TEXT_PLAIN; + else if (extName == "ttf") + return CT_APPLICATION_X_FONT_TRUETYPE; + break; + case 'x': + if (extName == "xml") + return CT_TEXT_XML; + else if (extName == "xsl") + return CT_TEXT_XSL; + break; + default: + break; + } + return CT_APPLICATION_OCTET_STREAM; + } + case 4: + { + if (extName == "html") + return CT_TEXT_HTML; + else if (extName == "jpeg") + return CT_IMAGE_JPG; + else if (extName == "icns") + return CT_IMAGE_ICNS; + else if (extName == "woff") + return CT_APPLICATION_FONT_WOFF; + return CT_APPLICATION_OCTET_STREAM; + } + case 5: + { + if (extName == "woff2") + return CT_APPLICATION_FONT_WOFF2; + return CT_APPLICATION_OCTET_STREAM; + } + default: + return CT_APPLICATION_OCTET_STREAM; + } +} + +ContentType parseContentType(const string_view &contentType) +{ + static const std::unordered_map map_{ + {"text/html", CT_TEXT_HTML}, + {"application/x-www-form-urlencoded", CT_APPLICATION_X_FORM}, + {"application/xml", CT_APPLICATION_XML}, + {"application/json", CT_APPLICATION_JSON}, + {"application/x-javascript", CT_APPLICATION_X_JAVASCRIPT}, + {"text/css", CT_TEXT_CSS}, + {"text/xml", CT_TEXT_XML}, + {"text/xsl", CT_TEXT_XSL}, + {"application/octet-stream", CT_APPLICATION_OCTET_STREAM}, + {"image/svg+xml", CT_IMAGE_SVG_XML}, + {"application/x-font-truetype", CT_APPLICATION_X_FONT_TRUETYPE}, + {"application/x-font-opentype", CT_APPLICATION_X_FONT_OPENTYPE}, + {"application/font-woff", CT_APPLICATION_FONT_WOFF}, + {"application/font-woff2", CT_APPLICATION_FONT_WOFF2}, + {"application/vnd.ms-fontobject", CT_APPLICATION_VND_MS_FONTOBJ}, + {"application/pdf", CT_APPLICATION_PDF}, + {"image/png", CT_IMAGE_PNG}, + {"image/jpeg", CT_IMAGE_JPG}, + {"image/gif", CT_IMAGE_GIF}, + {"image/x-icon", CT_IMAGE_XICON}, + {"image/bmp", CT_IMAGE_BMP}, + {"image/icns", CT_IMAGE_ICNS}, + {"application/wasm", CT_APPLICATION_WASM}, + {"text/plain", CT_TEXT_PLAIN}, + {"multipart/form-data", CT_MULTIPART_FORM_DATA}}; + auto iter = map_.find(contentType); + if (iter == map_.end()) + return CT_NONE; + return iter->second; +} + +} // namespace drogon diff --git a/core/http_utils.h b/core/http_utils.h new file mode 100644 index 0000000..746fc0d --- /dev/null +++ b/core/http_utils.h @@ -0,0 +1,58 @@ +/** + * + * HttpUtils.h + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace drogon +{ +ContentType parseContentType(const string_view &contentType); +const string_view &webContentTypeToString(ContentType contenttype); +const string_view &statusCodeToString(int code); +ContentType getContentType(const std::string &fileName); +template +inline constexpr const char *contentLengthFormatString() +{ + return "Content-Length: %d\r\n"; +} +template <> +inline constexpr const char *contentLengthFormatString() +{ + return "Content-Length: %u\r\n"; +} +template <> +inline constexpr const char *contentLengthFormatString() +{ + return "Content-Length: %ld\r\n"; +} +template <> +inline constexpr const char *contentLengthFormatString() +{ + return "Content-Length: %lu\r\n"; +} +template <> +inline constexpr const char *contentLengthFormatString() +{ + return "Content-Length: %lld\r\n"; +} +template <> +inline constexpr const char *contentLengthFormatString() +{ + return "Content-Length: %llu\r\n"; +} +} // namespace drogon diff --git a/core/http_view_data.h b/core/http_view_data.h new file mode 100644 index 0000000..25e4257 --- /dev/null +++ b/core/http_view_data.h @@ -0,0 +1,191 @@ +/** + * + * HttpViewData.h + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace drogon +{ +/// This class represents the data set displayed in views. +class HttpViewData +{ + public: + /// The function template is used to get an item in the data set by the key + /// parameter. + template + const T &get(const std::string &key) const + { + const static T nullVal = T(); + auto it = viewData_.find(key); + if (it != viewData_.end()) + { + if (typeid(T) == it->second.type()) + { + return *(any_cast(&(it->second))); + } + else + { + LOG_ERROR << "Bad type"; + } + } + return nullVal; + } + + /// Insert an item identified by the key parameter into the data set; + void insert(const std::string &key, any &&obj) + { + viewData_[key] = std::move(obj); + } + void insert(const std::string &key, const any &obj) + { + viewData_[key] = obj; + } + + /// Insert an item identified by the key parameter into the data set; The + /// item is converted to a string. + template + void insertAsString(const std::string &key, T &&val) + { + std::stringstream ss; + ss << val; + viewData_[key] = ss.str(); + } + + /// Insert a formated string identified by the key parameter. + void insertFormattedString(const std::string &key, const char *format, ...) + { + std::string strBuffer; + strBuffer.resize(128); + va_list ap, backup_ap; + va_start(ap, format); + va_copy(backup_ap, ap); + auto result = vsnprintf((char *)strBuffer.data(), + strBuffer.size(), + format, + backup_ap); + va_end(backup_ap); + if ((result >= 0) && + ((std::string::size_type)result < strBuffer.size())) + { + strBuffer.resize(result); + } + else + { + while (true) + { + if (result < 0) + { + // Older snprintf() behavior. Just try doubling the buffer + // size + strBuffer.resize(strBuffer.size() * 2); + } + else + { + strBuffer.resize(result + 1); + } + + va_copy(backup_ap, ap); + auto result = vsnprintf((char *)strBuffer.data(), + strBuffer.size(), + format, + backup_ap); + va_end(backup_ap); + + if ((result >= 0) && + ((std::string::size_type)result < strBuffer.size())) + { + strBuffer.resize(result); + break; + } + } + } + va_end(ap); + viewData_[key] = std::move(strBuffer); + } + + /// Get the 'any' object by the key parameter. + any &operator[](const std::string &key) const + { + return viewData_[key]; + } + + /// Translate some special characters to HTML format + /** + * such as: + * @code + " --> " + & --> & + < --> < + > --> > + @endcode + */ + static std::string htmlTranslate(const char *str, size_t length); + static std::string htmlTranslate(const std::string &str) + { + return htmlTranslate(str.data(), str.length()); + } + static std::string htmlTranslate(const string_view &str) + { + return htmlTranslate(str.data(), str.length()); + } + static bool needTranslation(const std::string &str) + { + for (auto const &c : str) + { + switch (c) + { + case '"': + case '&': + case '<': + case '>': + return true; + default: + continue; + } + } + return false; + } + static bool needTranslation(const string_view &str) + { + for (auto const &c : str) + { + switch (c) + { + case '"': + case '&': + case '<': + case '>': + return true; + default: + continue; + } + } + return false; + } + + protected: + using ViewDataMap = std::unordered_map; + mutable ViewDataMap viewData_; +}; + +} // namespace drogon diff --git a/core/io_thread_storage.h b/core/io_thread_storage.h new file mode 100644 index 0000000..940e782 --- /dev/null +++ b/core/io_thread_storage.h @@ -0,0 +1,162 @@ +/** + * + * IOThreadStorage.h + * Daniel Mensinger + * + * Copyright 2019, Daniel Mensinger. 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 + * + */ + +#pragma once + +//#include +#include +#include +#include +#include +#include + +namespace drogon +{ +/** + * @brief Utility class for thread storage handling + * + * Thread storage allows the efficient handling of reusable data without thread + * synchronisation. For instance, such a thread storage would be useful to store + * database connections. + * + * Example usage: + * + * @code + * struct MyThreadData { + * int threadLocal = 42; + * std::string something = "foo"; + * }; + * + * class MyController : public HttpController { + * public: + * METHOD_LIST_BEGIN + * ADD_METHOD_TO(MyController::endpoint, "/some/path", Get); + * METHOD_LIST_END + * + * void login(const HttpRequestPtr &req, + * std::function &&callback) { + * assert(storage_->threadLocal == 42); + * + * // handle the request + * } + * + * private: + * IOThreadStorage storage_; + * }; + * @endcode + */ +template +class IOThreadStorage : public trantor::NonCopyable +{ + public: + using ValueType = C; + using InitCallback = std::function; + + template + IOThreadStorage(Args &&... args) + { + static_assert(std::is_constructible::value, + "Unable to construct storage with given signature"); + size_t numThreads = app().getThreadNum(); +#ifdef _WIN32 + assert(numThreads > 0 && numThreads != size_t(-1)); +#else + assert(numThreads > 0 && + numThreads != std::numeric_limits::max()); +#endif + // set the size to numThreads+1 to enable access to this in the main + // thread. + storage_.reserve(numThreads + 1); + + for (size_t i = 0; i <= numThreads; ++i) + { + storage_.emplace_back(std::forward(args)...); + } + } + + void init(const InitCallback &initCB) + { + for (size_t i = 0; i < storage_.size(); ++i) + { + initCB(storage_[i], i); + } + } + + /** + * @brief Get the thread storage asociate with the current thread + * + * This function may only be called in a request handler + */ + inline ValueType &getThreadData() + { + size_t idx = app().getCurrentThreadIndex(); + assert(idx < storage_.size()); + return storage_[idx]; + } + + inline const ValueType &getThreadData() const + { + size_t idx = app().getCurrentThreadIndex(); + assert(idx < storage_.size()); + return storage_[idx]; + } + + /** + * @brief Sets the thread data for the current thread + * + * This function may only be called in a request handler + */ + inline void setThreadData(const ValueType &newData) + { + size_t idx = app().getCurrentThreadIndex(); + assert(idx < storage_.size()); + storage_[idx] = newData; + } + + inline void setThreadData(ValueType &&newData) + { + size_t idx = app().getCurrentThreadIndex(); + assert(idx < storage_.size()); + storage_[idx] = std::move(newData); + } + + inline ValueType *operator->() + { + size_t idx = app().getCurrentThreadIndex(); + assert(idx < storage_.size()); + return &storage_[idx]; + } + + inline ValueType &operator*() + { + return getThreadData(); + } + + inline const ValueType *operator->() const + { + size_t idx = app().getCurrentThreadIndex(); + assert(idx < storage_.size()); + return &storage_[idx]; + } + + inline const ValueType &operator*() const + { + return getThreadData(); + } + + private: + std::vector storage_; +}; + +} // namespace drogon diff --git a/core/listener_manager.cpp b/core/listener_manager.cpp new file mode 100644 index 0000000..871b7bd --- /dev/null +++ b/core/listener_manager.cpp @@ -0,0 +1,241 @@ +/** + * + * ListenerManager.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 "listener_manager.h" +#include "http_server.h" +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#endif + +namespace drogon { +#ifndef _WIN32 +class DrogonFileLocker : public trantor::NonCopyable { +public: + DrogonFileLocker() { + fd_ = open("/tmp/drogon.lock", O_TRUNC | O_CREAT, 0755); + flock(fd_, LOCK_EX); + } + + ~DrogonFileLocker() { + close(fd_); + } + +private: + int fd_{ 0 }; +}; + +#endif +} // namespace drogon + +using namespace trantor; +using namespace drogon; + +void ListenerManager::addListener(const std::string &ip, uint16_t port, bool useSSL, const std::string &certFile, const std::string &keyFile) { +#ifndef OpenSSL_FOUND + if (useSSL) { + LOG_ERROR << "Can't use SSL without OpenSSL found in your system"; + } +#endif + + listeners_.emplace_back(ip, port, useSSL, certFile, keyFile); +} + +std::vector ListenerManager::createListeners( + const HttpAsyncCallback &httpCallback, + const WebSocketNewAsyncCallback &webSocketCallback, + const ConnectionCallback &connectionCallback, + size_t connectionTimeout, + const std::string &globalCertFile, + const std::string &globalKeyFile, + size_t threadNum, + const std::vector > &syncAdvices) { + +#ifdef __linux__ + for (size_t i = 0; i < threadNum; ++i) { + LOG_TRACE << "thread num=" << threadNum; + + auto loopThreadPtr = std::make_shared("DrogonIoLoop"); + listeningloopThreads_.push_back(loopThreadPtr); + ioLoops_.push_back(loopThreadPtr->getLoop()); + + for (auto const &listener : listeners_) { + auto const &ip = listener.ip_; + bool isIpv6 = ip.find(':') == std::string::npos ? false : true; + std::shared_ptr serverPtr; + if (i == 0) { + DrogonFileLocker lock; + // Check whether the port is in use. + TcpServer server(HttpAppFrameworkImpl::instance().getLoop(), + InetAddress(ip, listener.port_, isIpv6), + "drogonPortTest", + true, + false); + serverPtr = std::make_shared( + loopThreadPtr->getLoop(), + InetAddress(ip, listener.port_, isIpv6), + "drogon", + syncAdvices); + } else { + serverPtr = std::make_shared( + loopThreadPtr->getLoop(), + InetAddress(ip, listener.port_, isIpv6), + "drogon", + syncAdvices); + } + + if (listener.useSSL_) { +#ifdef OpenSSL_FOUND + auto cert = listener.certFile_; + auto key = listener.keyFile_; + if (cert == "") + cert = globalCertFile; + if (key == "") + key = globalKeyFile; + if (cert == "" || key == "") { + std::cerr + << "You can't use https without cert file or key file" + << std::endl; + exit(1); + } + serverPtr->enableSSL(cert, key); +#endif + } + + serverPtr->setHttpAsyncCallback(httpCallback); + serverPtr->setNewWebsocketCallback(webSocketCallback); + serverPtr->setConnectionCallback(connectionCallback); + serverPtr->kickoffIdleConnections(connectionTimeout); + serverPtr->start(); + servers_.push_back(serverPtr); + } + } + +#else + auto loopThreadPtr = + std::make_shared("DrogonListeningLoop"); + listeningloopThreads_.push_back(loopThreadPtr); + ioLoopThreadPoolPtr_ = std::make_shared(threadNum); + for (auto const &listener : listeners_) { + LOG_TRACE << "thread num=" << threadNum; + auto ip = listener.ip_; + bool isIpv6 = ip.find(':') == std::string::npos ? false : true; + auto serverPtr = std::make_shared( + loopThreadPtr->getLoop(), + InetAddress(ip, listener.port_, isIpv6), + "drogon", + syncAdvices); + if (listener.useSSL_) { +#ifdef OpenSSL_FOUND + auto cert = listener.certFile_; + auto key = listener.keyFile_; + if (cert == "") + cert = globalCertFile; + if (key == "") + key = globalKeyFile; + if (cert == "" || key == "") { + std::cerr << "You can't use https without cert file or key file" + << std::endl; + exit(1); + } + serverPtr->enableSSL(cert, key); +#endif + } + serverPtr->setIoLoopThreadPool(ioLoopThreadPoolPtr_); + serverPtr->setHttpAsyncCallback(httpCallback); + serverPtr->setNewWebsocketCallback(webSocketCallback); + serverPtr->setConnectionCallback(connectionCallback); + serverPtr->kickoffIdleConnections(connectionTimeout); + serverPtr->start(); + servers_.push_back(serverPtr); + } + ioLoops_ = ioLoopThreadPoolPtr_->getLoops(); + +#endif + return ioLoops_; +} + +void ListenerManager::startListening() { + if (listeners_.size() == 0) + return; + for (auto &loopThread : listeningloopThreads_) { + loopThread->run(); + } +} + +ListenerManager::~ListenerManager() { +} + +trantor::EventLoop *ListenerManager::getIOLoop(size_t id) const { + + auto const n = listeningloopThreads_.size(); + if (0 == n) { + LOG_WARN << "Please call getIOLoop() after drogon::app().run()"; + return nullptr; + } + + if (id >= n) { + LOG_TRACE << "Loop id (" << id << ") out of range [0-" << n << ")."; + id %= n; + LOG_TRACE << "Rounded to : " << id; + } + +#ifdef __linux__ + assert(listeningloopThreads_[id]); + return listeningloopThreads_[id]->getLoop(); +#else + return ioLoopThreadPoolPtr_->getLoop(id); +#endif +} + +void ListenerManager::stopListening() { + for (auto &serverPtr : servers_) { + serverPtr->stop(); + } + + for (auto loop : ioLoops_) { + assert(!loop->isInLoopThread()); + if (loop->isRunning()) { + std::promise pro; + auto f = pro.get_future(); + loop->queueInLoop([loop, &pro]() { + loop->quit(); + pro.set_value(1); + }); + (void)f.get(); + } + } + +#ifndef __linux__ + for (auto &listenerLoopPtr : listeningloopThreads_) { + auto loop = listenerLoopPtr->getLoop(); + assert(!loop->isInLoopThread()); + if (loop->isRunning()) { + std::promise pro; + auto f = pro.get_future(); + loop->queueInLoop([loop, &pro]() { + loop->quit(); + pro.set_value(1); + }); + (void)f.get(); + } + } +#endif +} diff --git a/core/listener_manager.h b/core/listener_manager.h new file mode 100644 index 0000000..8943b3a --- /dev/null +++ b/core/listener_manager.h @@ -0,0 +1,82 @@ +/** + * + * ListenerManager.h + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "core/http_server_callbacks.h" + +class HttpServer; + +namespace drogon { +class ListenerManager : public trantor::NonCopyable { +public: + void addListener(const std::string &ip,uint16_t port,bool useSSL = false,const std::string &certFile = "", const std::string &keyFile = ""); + + std::vector createListeners( + const HttpAsyncCallback &httpCallback, + const WebSocketNewAsyncCallback &webSocketCallback, + const trantor::ConnectionCallback &connectionCallback, + size_t connectionTimeout, + const std::string &globalCertFile, + const std::string &globalKeyFile, + size_t threadNum, + const std::vector< + std::function > + &syncAdvices); + + void startListening(); + + ~ListenerManager(); + + trantor::EventLoop *getIOLoop(size_t id) const; + + void stopListening(); + + std::vector ioLoops_; + +private: + struct ListenerInfo { + ListenerInfo(const std::string &ip, + uint16_t port, + bool useSSL, + const std::string &certFile, + const std::string &keyFile) : + ip_(ip), + port_(port), + useSSL_(useSSL), + certFile_(certFile), + keyFile_(keyFile) { + } + + std::string ip_; + uint16_t port_; + bool useSSL_; + std::string certFile_; + std::string keyFile_; + }; + + std::vector listeners_; + std::vector > servers_; + std::vector > listeningloopThreads_; + std::shared_ptr ioLoopThreadPoolPtr_; +}; + +} // namespace drogon diff --git a/core/request.h b/core/request.h index e6d0352..2548170 100644 --- a/core/request.h +++ b/core/request.h @@ -9,15 +9,15 @@ #include "handler_instance.h" -using namespace brynet; -using namespace brynet::net; -using namespace brynet::net::http; +//using namespace brynet; +//using namespace brynet::net; +//using namespace brynet::net::http; class Request { public: - const HTTPParser *http_parser; - const HttpSession::Ptr *session; - HttpResponse *response; + const brynet::net::http::HTTPParser *http_parser; + const brynet::net::http::HttpSession::Ptr *session; + brynet::net::http::HttpResponse *response; uint32_t current_middleware_index; HandlerInstance handler_instance; diff --git a/core/session.h b/core/session.h new file mode 100644 index 0000000..4176b17 --- /dev/null +++ b/core/session.h @@ -0,0 +1,197 @@ +/** + * + * Session.h + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace drogon +{ +/** + * @brief This class represents a session stored in the framework. + * One can get or set any type of data to a session object. + */ +class Session +{ + public: + /** + * @brief Get the data identified by the key parameter. + * @note if the data is not found, a default value is returned. + * For example: + * @code + auto &userName = sessionPtr->get("user name"); + @endcode + */ + template + const T &get(const std::string &key) const + { + const static T nullVal = T(); + std::lock_guard lck(mutex_); + auto it = sessionMap_.find(key); + if (it != sessionMap_.end()) + { + if (typeid(T) == it->second.type()) + { + return *(any_cast(&(it->second))); + } + else + { + LOG_ERROR << "Bad type"; + } + } + return nullVal; + } + + /** + * @brief Get the 'any' object identified by the given key + */ + any &operator[](const std::string &key) + { + std::lock_guard lck(mutex_); + return sessionMap_[key]; + } + + /** + * @brief Insert a key-value pair + * @note here the any object can be created implicitly. for example + * @code + sessionPtr->insert("user name", userNameString); + @endcode + */ + void insert(const std::string &key, const any &obj) + { + std::lock_guard lck(mutex_); + sessionMap_[key] = obj; + } + + /** + * @brief Insert a key-value pair + * @note here the any object can be created implicitly. for example + * @code + sessionPtr->insert("user name", userNameString); + @endcode + */ + void insert(const std::string &key, any &&obj) + { + std::lock_guard lck(mutex_); + sessionMap_[key] = std::move(obj); + } + + /** + * @brief Erase the data identified by the given key. + */ + void erase(const std::string &key) + { + std::lock_guard lck(mutex_); + sessionMap_.erase(key); + } + + /** + * @brief Retrun true if the data identified by the key exists. + */ + bool find(const std::string &key) + { + std::lock_guard lck(mutex_); + if (sessionMap_.find(key) == sessionMap_.end()) + { + return false; + } + return true; + } + + /** + * @brief Clear all data in the session. + */ + void clear() + { + std::lock_guard lck(mutex_); + sessionMap_.clear(); + } + + /** + * @brief Get the session ID of the current session. + */ + std::string sessionId() const + { + std::lock_guard lck(mutex_); + return sessionId_; + } + + /** + * @brief Let the framework create a new session ID for this session and set + * it to the client. + * @note This method does not change the session ID now. + */ + void changeSessionIdToClient() + { + needToChange_ = true; + needToSet_ = true; + } + Session() = delete; + + private: + using SessionMap = std::map; + SessionMap sessionMap_; + mutable std::mutex mutex_; + std::string sessionId_; + bool needToSet_{false}; + bool needToChange_{false}; + friend class SessionManager; + friend class HttpAppFrameworkImpl; + /** + * @brief Constructor, usually called by the framework + */ + Session(const std::string &id, bool needToSet) + : sessionId_(id), needToSet_(needToSet) + { + } + /** + * @brief Change the state of the session, usually called by the framework + */ + void hasSet() + { + needToSet_ = false; + } + /** + * @brief If the session ID needs to be changed. + * + */ + bool needToChangeSessionId() const + { + return needToChange_; + } + /** + * @brief If the session ID needs to be set to the client through cookie, + * return true + */ + bool needSetToClient() const + { + return needToSet_; + } + void setSessionId(const std::string &id) + { + std::lock_guard lck(mutex_); + sessionId_ = id; + needToChange_ = false; + } +}; + +using SessionPtr = std::shared_ptr; + +} // namespace drogon diff --git a/core/ssl_funcs/Md5.cc b/core/ssl_funcs/Md5.cc new file mode 100644 index 0000000..411d7ef --- /dev/null +++ b/core/ssl_funcs/Md5.cc @@ -0,0 +1,351 @@ +/** + * + * Md5.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 "Md5.h" +#include +#include + +const uint32_t Md5Encode::kA = 0x67452301; +const uint32_t Md5Encode::kB = 0xefcdab89; +const uint32_t Md5Encode::kC = 0x98badcfe; +const uint32_t Md5Encode::kD = 0x10325476; + +const uint64_t Md5Encode::tiNumInteger = 4294967296; + +// function: CycleMoveLeft +// @param srcNum: the number to be moved left +// @param bitNumToMove: the number of bit of moving +// @return result after moving +uint32_t Md5Encode::cycleMoveLeft(uint32_t srcNum, int bitNumToMove) +{ + uint32_t srcNum1 = srcNum; + uint32_t srcNum2 = srcNum; + if (0 >= bitNumToMove) + { + return srcNum; + } + return ((srcNum1 << bitNumToMove) | (srcNum2 >> (32 - bitNumToMove))); +} + +// function: FillData +// @param inDataPtr: input data +// @param dataByteLen: length of input data +// @param outDataPtr: output data +// return : length of output data +uint32_t Md5Encode::fillData(const char *inDataPtr, + int dataByteLen, + char **outDataPtr) +{ + int bitNum = dataByteLen * BIT_OF_BYTE; + // int grop_num = bitNum / BIT_OF_GROUP; + int modBitNum = bitNum % BIT_OF_GROUP; + int bitNeedFill = 0; + if (modBitNum > (BIT_OF_GROUP - SRC_DATA_LEN)) + { + bitNeedFill = (BIT_OF_GROUP - modBitNum); + bitNeedFill += (BIT_OF_GROUP - SRC_DATA_LEN); + } + else + { + bitNeedFill = (BIT_OF_GROUP - SRC_DATA_LEN) - modBitNum; + } + int allBit = bitNum + bitNeedFill; + if (0 < bitNeedFill) + { + *outDataPtr = + new char[allBit / BIT_OF_BYTE + SRC_DATA_LEN / BIT_OF_BYTE]; + memset(*outDataPtr, + 0, + allBit / BIT_OF_BYTE + SRC_DATA_LEN / BIT_OF_BYTE); + // copy data + memcpy(*outDataPtr, inDataPtr, dataByteLen); + // fill rest data + unsigned char *tmp = reinterpret_cast(*outDataPtr); + tmp += dataByteLen; + // fill 1 and 0 + *tmp = 0x80; + // fill origin data len + unsigned long long *originNum = + (unsigned long long *)((*outDataPtr) + ((allBit / BIT_OF_BYTE))); + *originNum = dataByteLen * BIT_OF_BYTE; + } + return (allBit / BIT_OF_BYTE + SRC_DATA_LEN / BIT_OF_BYTE); +} + +void Md5Encode::roundF(char *data512Ptr, ParamDynamic ¶m) +{ + uint32_t *M = reinterpret_cast(data512Ptr); + int s[] = {7, 12, 17, 22}; + for (int i = 0; i < 16; ++i) + { + uint64_t ti = tiNumInteger * fabs(sin(i + 1)); + if (i % 4 == 0) + { + FF(param.ua_, param.ub_, param.uc_, param.ud_, M[i], s[i % 4], ti); + } + else if (i % 4 == 1) + { + FF(param.ud_, param.ua_, param.ub_, param.uc_, M[i], s[i % 4], ti); + } + else if (i % 4 == 2) + { + FF(param.uc_, param.ud_, param.ua_, param.ub_, M[i], s[i % 4], ti); + } + else if (i % 4 == 3) + { + FF(param.ub_, param.uc_, param.ud_, param.ua_, M[i], s[i % 4], ti); + } + } +} + +void Md5Encode::roundG(char *data512Ptr, ParamDynamic ¶m) +{ + uint32_t *M = reinterpret_cast(data512Ptr); + int s[] = {5, 9, 14, 20}; + for (int i = 0; i < 16; ++i) + { + auto sss = sin(i + 1 + 16); + uint64_t ti = tiNumInteger * fabs(sss); + int index = (i * 5 + 1) % 16; + if (i % 4 == 0) + { + GG(param.ua_, + param.ub_, + param.uc_, + param.ud_, + M[index], + s[i % 4], + ti); + } + else if (i % 4 == 1) + { + GG(param.ud_, + param.ua_, + param.ub_, + param.uc_, + M[index], + s[i % 4], + ti); + } + else if (i % 4 == 2) + { + GG(param.uc_, + param.ud_, + param.ua_, + param.ub_, + M[index], + s[i % 4], + ti); + } + else if (i % 4 == 3) + { + GG(param.ub_, + param.uc_, + param.ud_, + param.ua_, + M[index], + s[i % 4], + ti); + } + } +} + +void Md5Encode::roundH(char *data512Ptr, ParamDynamic ¶m) +{ + uint32_t *M = reinterpret_cast(data512Ptr); + int s[] = {4, 11, 16, 23}; + for (int i = 0; i < 16; ++i) + { + uint64_t ti = tiNumInteger * fabs(sin(i + 1 + 32)); + int index = (i * 3 + 5) % 16; + if (i % 4 == 0) + { + HH(param.ua_, + param.ub_, + param.uc_, + param.ud_, + M[index], + s[i % 4], + ti); + } + else if (i % 4 == 1) + { + HH(param.ud_, + param.ua_, + param.ub_, + param.uc_, + M[index], + s[i % 4], + ti); + } + else if (i % 4 == 2) + { + HH(param.uc_, + param.ud_, + param.ua_, + param.ub_, + M[index], + s[i % 4], + ti); + } + else if (i % 4 == 3) + { + HH(param.ub_, + param.uc_, + param.ud_, + param.ua_, + M[index], + s[i % 4], + ti); + } + } +} + +void Md5Encode::roundI(char *data512Ptr, ParamDynamic ¶m) +{ + uint32_t *M = reinterpret_cast(data512Ptr); + int s[] = {6, 10, 15, 21}; + for (int i = 0; i < 16; ++i) + { + uint64_t ti = tiNumInteger * fabs(sin(i + 1 + 48)); + int index = (i * 7 + 0) % 16; + if (i % 4 == 0) + { + II(param.ua_, + param.ub_, + param.uc_, + param.ud_, + M[index], + s[i % 4], + ti); + } + else if (i % 4 == 1) + { + II(param.ud_, + param.ua_, + param.ub_, + param.uc_, + M[index], + s[i % 4], + ti); + } + else if (i % 4 == 2) + { + II(param.uc_, + param.ud_, + param.ua_, + param.ub_, + M[index], + s[i % 4], + ti); + } + else if (i % 4 == 3) + { + II(param.ub_, + param.uc_, + param.ud_, + param.ua_, + M[index], + s[i % 4], + ti); + } + } +} + +void Md5Encode::rotationCalculate(char *data512Ptr, ParamDynamic ¶m) +{ + if (nullptr == data512Ptr) + { + return; + } + roundF(data512Ptr, param); + roundG(data512Ptr, param); + roundH(data512Ptr, param); + roundI(data512Ptr, param); + param.ua_ = param.va_last_ + param.ua_; + param.ub_ = param.vb_last_ + param.ub_; + param.uc_ = param.vc_last_ + param.uc_; + param.ud_ = param.vd_last_ + param.ud_; + + param.va_last_ = param.ua_; + param.vb_last_ = param.ub_; + param.vc_last_ = param.uc_; + param.vd_last_ = param.ud_; +} + +// Convert to hex format string +std::string Md5Encode::getHexStr(uint32_t numStr) +{ + std::string hexstr = ""; + char szch[] = {'0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F'}; + unsigned char *tmptr = (unsigned char *)&numStr; + int len = sizeof(numStr); + for (int i = 0; i < len; ++i) + { + unsigned char ch = tmptr[i] & 0xF0; + ch = ch >> 4; + hexstr.append(1, szch[ch]); + ch = tmptr[i] & 0x0F; + hexstr.append(1, szch[ch]); + } + return hexstr; +} + +// function: Encode +// @param srcInfo: the string to be encoded. +// return : the string after encoding +std::string Md5Encode::encode(const char *data, const size_t dataLen) +{ + ParamDynamic param; + param.ua_ = kA; + param.ub_ = kB; + param.uc_ = kC; + param.ud_ = kD; + param.va_last_ = kA; + param.vb_last_ = kB; + param.vc_last_ = kC; + param.vd_last_ = kD; + + std::string result; + char *outDataPtr = nullptr; + int totalByte = fillData(data, dataLen, &outDataPtr); + + for (int i = 0; i < totalByte / (BIT_OF_GROUP / BIT_OF_BYTE); ++i) + { + char *dataBitOfGroup = outDataPtr; + dataBitOfGroup += i * (BIT_OF_GROUP / BIT_OF_BYTE); + rotationCalculate(dataBitOfGroup, param); + } + delete[] outDataPtr, outDataPtr = nullptr; + result.append(getHexStr(param.ua_)); + result.append(getHexStr(param.ub_)); + result.append(getHexStr(param.uc_)); + result.append(getHexStr(param.ud_)); + return result; +} diff --git a/core/ssl_funcs/Md5.h b/core/ssl_funcs/Md5.h new file mode 100644 index 0000000..e65117e --- /dev/null +++ b/core/ssl_funcs/Md5.h @@ -0,0 +1,81 @@ +/* +******************************************************* +* brief: md5 encryption +* author: Monkey.Knight +******************************************************* +*/ + +/** + * + * Md5.h + * 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 + * + */ + +#pragma once + +#include +#include + +#define BIT_OF_BYTE 8 +#define BIT_OF_GROUP 512 +#define SRC_DATA_LEN 64 + +#define DEF_F(X, Y, Z) ((((X) & (Y)) | ((~X) & (Z)))) +#define DEF_G(X, Y, Z) (((X) & (Z)) | ((Y) & (~Z))) +#define DEF_H(X, Y, Z) ((X) ^ (Y) ^ (Z)) +#define DEF_I(X, Y, Z) ((Y) ^ ((X) | (~Z))) + +#define FF(a, b, c, d, Mj, s, ti) \ + (a = b + cycleMoveLeft((a + DEF_F(b, c, d) + Mj + ti), s)); +#define GG(a, b, c, d, Mj, s, ti) \ + (a = b + cycleMoveLeft((a + DEF_G(b, c, d) + Mj + ti), s)); +#define HH(a, b, c, d, Mj, s, ti) \ + (a = b + cycleMoveLeft((a + DEF_H(b, c, d) + Mj + ti), s)); +#define II(a, b, c, d, Mj, s, ti) \ + (a = b + cycleMoveLeft((a + DEF_I(b, c, d) + Mj + ti), s)); + +class Md5Encode +{ + public: + struct ParamDynamic + { + uint32_t ua_; + uint32_t ub_; + uint32_t uc_; + uint32_t ud_; + uint32_t va_last_; + uint32_t vb_last_; + uint32_t vc_last_; + uint32_t vd_last_; + }; + + public: + static std::string encode(const char *data, const size_t dataLen); + + protected: + static uint32_t cycleMoveLeft(uint32_t srcNum, int bitNumToMove); + static void roundF(char *data512Ptr, ParamDynamic ¶m); + static void roundG(char *data512Ptr, ParamDynamic ¶m); + static void roundH(char *data512Ptr, ParamDynamic ¶m); + static void roundI(char *data512Ptr, ParamDynamic ¶m); + static void rotationCalculate(char *data512Ptr, ParamDynamic ¶m); + static std::string getHexStr(uint32_t numStr); + static uint32_t fillData(const char *inDataPtr, + int dataByteLen, + char **outDataPtr); + + private: + static const uint32_t kA; + static const uint32_t kB; + static const uint32_t kC; + static const uint32_t kD; + static const uint64_t tiNumInteger; +}; diff --git a/core/ssl_funcs/Sha1.cc b/core/ssl_funcs/Sha1.cc new file mode 100644 index 0000000..79aea53 --- /dev/null +++ b/core/ssl_funcs/Sha1.cc @@ -0,0 +1,159 @@ +/** + * + * Sha1.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 "Sha1.h" +#include + +static inline unsigned int fromBigEndian(unsigned int v) +{ + return ((v & 0xff) << 24) | ((v & 0xff00) << 8) | ((v & 0xff0000) >> 8) | + ((v & 0xff000000) >> 24); +} + +static void writeBigEndian64(unsigned char *p, unsigned int v) +{ + memset(p, 0, 8); + memcpy(p, &v, 4); + int i = 0; + for (i = 0; i < 4; ++i) + { + unsigned char t = p[i]; + p[i] = p[7 - i]; + p[7 - i] = t; + } +} + +static inline unsigned int leftRoll(unsigned int v, int n) +{ + return (v << n) | (v >> (32 - n)); +} + +unsigned char *SHA1(const unsigned char *dataIn, + size_t dataLen, + unsigned char *dataOut) +{ + unsigned char *pbytes = (unsigned char *)dataIn; + unsigned int nbyte = dataLen; + + static unsigned int words[80]; + unsigned int H[5] = { + 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0}; + unsigned int f, k, temp, bitlen[2], word; + unsigned int i, j, index, p1, p2, maxlen; + unsigned char spec[4] = {0}; + i = nbyte % 4; + + p1 = nbyte - i; + spec[i] = 1 << 7; + while (i--) + { + spec[i] = pbytes[p1 + i]; + } + + maxlen = (nbyte + 1) % 64; + if (maxlen <= 56) + { + maxlen = (nbyte + 1) - maxlen + 64; + } + else + { + maxlen = (nbyte + 1) - maxlen + 128; + } + p2 = maxlen - 8; + writeBigEndian64((unsigned char *)bitlen, nbyte * 8); + + for (j = 0; j < maxlen; j += 64) + { + unsigned int a, b, c, d, e; + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + for (i = 0; i < 80; ++i) + { + if (i < 16) + { + index = j + (i << 2); + if (index < p1) + { + word = *((unsigned int *)(pbytes + index)); + } + else if (index == p1) + { + word = *(unsigned int *)spec; + } + else if (index < p2) + { + word = 0; + } + else + { + word = (index < maxlen - 4) ? bitlen[0] : bitlen[1]; + } + words[i] = fromBigEndian(word); + } + else + { + words[i] = leftRoll(words[i - 3] ^ words[i - 8] ^ + words[i - 14] ^ words[i - 16], + 1); + } + if (i < 20) + { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } + else if (i < 40) + { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (i < 60) + { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + else + { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + temp = leftRoll(a, 5) + f + e + k + words[i]; + e = d; + d = c; + c = leftRoll(b, 30); + b = a; + a = temp; + } + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + } + int ct = 0; + for (i = 0; i < 5; ++i) + { + unsigned char buf[4] = {0}; + memcpy(buf, &(H[i]), 4); + for (int r = 3; r >= 0; r--) + { + dataOut[ct] = buf[r]; + ++ct; + } + } + + return dataOut; +} diff --git a/core/ssl_funcs/Sha1.h b/core/ssl_funcs/Sha1.h new file mode 100644 index 0000000..8fe1ab3 --- /dev/null +++ b/core/ssl_funcs/Sha1.h @@ -0,0 +1,23 @@ +/** + * + * Sha1.h + * 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 + * + */ + +#pragma once + +#include + +#define SHA_DIGEST_LENGTH 20 + +unsigned char *SHA1(const unsigned char *dataIn, + size_t dataLen, + unsigned char *dataOut); diff --git a/core/string_view.h b/core/string_view.h new file mode 100644 index 0000000..47b0aa2 --- /dev/null +++ b/core/string_view.h @@ -0,0 +1,92 @@ +/** + * + * string_view.h + * 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 + * + */ + +#pragma once +#if __cplusplus >= 201703L || (defined _MSC_VER && _MSC_VER > 1900) +#include +#else +#include +#include +#endif + +#include + +namespace drogon +{ +#if __cplusplus >= 201703L || (defined _MSC_VER && _MSC_VER > 1900) +using std::string_view; +#else +using boost::string_view; +#endif +} // namespace drogon +namespace trantor +{ +inline LogStream &operator<<(LogStream &ls, const drogon::string_view &v) +{ + ls.append(v.data(), v.length()); + return ls; +} +} // namespace trantor + +#if __cplusplus < 201703L +namespace std +{ +template <> +struct hash +{ + size_t operator()(const drogon::string_view &__str) const noexcept + { + // Take from the memory header file + //===-------------------------- memory + //------------------------------------===// + // + // The LLVM Compiler Infrastructure + // + // This file is dual licensed under the MIT and the University of + // Illinois Open Source Licenses. See LICENSE.TXT for details. + // + //===----------------------------------------------------------------------===// + const size_t __m = 0x5bd1e995; + const size_t __r = 24; + size_t __h = __str.length(); + auto __len = __h; + const unsigned char *__data = (const unsigned char *)(__str.data()); + for (; __len >= 4; __data += 4, __len -= 4) + { + size_t __k = *((size_t *)__data); + __k *= __m; + __k ^= __k >> __r; + __k *= __m; + __h *= __m; + __h ^= __k; + } + switch (__len) + { + case 3: + __h ^= __data[2] << 16; + case 2: + __h ^= __data[1] << 8; + case 1: + __h ^= __data[0]; + __h *= __m; + } + __h ^= __h >> 13; + __h *= __m; + __h ^= __h >> 15; + return __h; + } +}; +} // namespace std + +#endif diff --git a/core/upload_file.h b/core/upload_file.h new file mode 100644 index 0000000..d2a9981 --- /dev/null +++ b/core/upload_file.h @@ -0,0 +1,75 @@ +/** + * + * UploadFile.h + * 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 + * + */ + +#pragma once +#include + +namespace drogon +{ +/** + * This class represents an upload file which will be transferred to the server + * via the multipart/form-data format + */ +class UploadFile +{ + public: + /// Constructor + /** + * @param filePath The file location on local host, including file name. + * @param fileName The file name provided to the server. If it is empty by + * default, the file name in the @param filePath + * is provided to the server. + * @param itemName The item name on the browser form. + */ + explicit UploadFile(const std::string &filePath, + const std::string &fileName = "", + const std::string &itemName = "file") + : path_(filePath), itemName_(itemName) + { + if (!fileName.empty()) + { + fileName_ = fileName; + } + else + { + auto pos = filePath.rfind('/'); + if (pos != std::string::npos) + { + fileName_ = filePath.substr(pos + 1); + } + else + { + fileName_ = filePath; + } + } + } + const std::string &path() const + { + return path_; + } + const std::string &fileName() const + { + return fileName_; + } + const std::string &itemName() const + { + return itemName_; + } + + private: + std::string path_; + std::string fileName_; + std::string itemName_; +}; +} // namespace drogon diff --git a/core/utilities.cpp b/core/utilities.cpp new file mode 100644 index 0000000..446b6b4 --- /dev/null +++ b/core/utilities.cpp @@ -0,0 +1,1162 @@ +/** + * + * Utilities.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 +#include + +#ifdef OpenSSL_FOUND +#include +#else +#include "core/ssl_funcs/Md5.h" +#endif +#ifdef USE_BROTLI +#include +#include +#endif +#ifdef _WIN32 +#include +#include +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif +#include +#include +#include + +#ifdef _WIN32 +char *strptime(const char *s, const char *f, struct tm *tm) +{ + // std::get_time is defined such that its + // format parameters are the exact same as strptime. + std::istringstream input(s); + input.imbue(std::locale(setlocale(LC_ALL, nullptr))); + input >> std::get_time(tm, f); + if (input.fail()) + { + return nullptr; + } + return (char *)(s + input.tellg()); +} +time_t timegm(struct tm *tm) +{ + struct tm my_tm; + + memcpy(&my_tm, tm, sizeof(struct tm)); + + /* _mkgmtime() changes the value of the struct tm* you pass in, so + * use a copy + */ + return _mkgmtime(&my_tm); +} +#endif + +namespace drogon +{ +namespace utils +{ +static const std::string base64Chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +static const std::string urlBase64Chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; +class Base64CharMap +{ + public: + Base64CharMap() + { + char index = 0; + for (int c = 'A'; c <= 'Z'; ++c) + { + charMap_[c] = index++; + } + for (int c = 'a'; c <= 'z'; ++c) + { + charMap_[c] = index++; + } + for (int c = '0'; c <= '9'; ++c) + { + charMap_[c] = index++; + } + charMap_[static_cast('+')] = charMap_[static_cast('-')] = + index++; + charMap_[static_cast('/')] = charMap_[static_cast('_')] = + index; + charMap_[0] = 0xff; + } + char getIndex(const char c) const noexcept + { + return charMap_[static_cast(c)]; + } + + private: + char charMap_[256]{0}; +}; +const static Base64CharMap base64CharMap; + +static inline bool isBase64(unsigned char c) +{ + if (isalnum(c)) + return true; + switch (c) + { + case '+': + case '/': + case '-': + case '_': + return true; + } + return false; +} + +bool isInteger(const std::string &str) +{ + for (auto const &c : str) + { + if (c > '9' || c < '0') + return false; + } + return true; +} + +std::string genRandomString(int length) +{ + static const char char_space[] = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + static std::once_flag once; + static const size_t len = strlen(char_space); + static const int randMax = RAND_MAX - (RAND_MAX % len); + std::call_once(once, []() { + std::srand(static_cast(time(nullptr))); + }); + + int i; + std::string str; + str.resize(length); + + for (i = 0; i < length; ++i) + { + int x = std::rand(); + while (x >= randMax) + { + x = std::rand(); + } + x = (x % len); + str[i] = char_space[x]; + } + + return str; +} + +std::vector hexToBinaryVector(const char *ptr, size_t length) +{ + assert(length % 2 == 0); + std::vector ret(length / 2, '\0'); + for (size_t i = 0; i < ret.size(); ++i) + { + auto p = i * 2; + char c1 = ptr[p]; + if (c1 >= '0' && c1 <= '9') + { + c1 -= '0'; + } + else if (c1 >= 'a' && c1 <= 'f') + { + c1 -= 'a'; + c1 += 10; + } + else if (c1 >= 'A' && c1 <= 'F') + { + c1 -= 'A'; + c1 += 10; + } + else + { + return std::vector(); + } + char c2 = ptr[p + 1]; + if (c2 >= '0' && c2 <= '9') + { + c2 -= '0'; + } + else if (c2 >= 'a' && c2 <= 'f') + { + c2 -= 'a'; + c2 += 10; + } + else if (c2 >= 'A' && c2 <= 'F') + { + c2 -= 'A'; + c2 += 10; + } + else + { + return std::vector(); + } + ret[i] = c1 * 16 + c2; + } + return ret; +} +std::string hexToBinaryString(const char *ptr, size_t length) +{ + assert(length % 2 == 0); + std::string ret(length / 2, '\0'); + for (size_t i = 0; i < ret.length(); ++i) + { + auto p = i * 2; + char c1 = ptr[p]; + if (c1 >= '0' && c1 <= '9') + { + c1 -= '0'; + } + else if (c1 >= 'a' && c1 <= 'f') + { + c1 -= 'a'; + c1 += 10; + } + else if (c1 >= 'A' && c1 <= 'F') + { + c1 -= 'A'; + c1 += 10; + } + else + { + return ""; + } + char c2 = ptr[p + 1]; + if (c2 >= '0' && c2 <= '9') + { + c2 -= '0'; + } + else if (c2 >= 'a' && c2 <= 'f') + { + c2 -= 'a'; + c2 += 10; + } + else if (c2 >= 'A' && c2 <= 'F') + { + c2 -= 'A'; + c2 += 10; + } + else + { + return ""; + } + ret[i] = c1 * 16 + c2; + } + return ret; +} + +std::string binaryStringToHex(const unsigned char *ptr, size_t length) +{ + std::string idString; + for (size_t i = 0; i < length; ++i) + { + int value = (ptr[i] & 0xf0) >> 4; + if (value < 10) + { + idString.append(1, char(value + 48)); + } + else + { + idString.append(1, char(value + 55)); + } + + value = (ptr[i] & 0x0f); + if (value < 10) + { + idString.append(1, char(value + 48)); + } + else + { + idString.append(1, char(value + 55)); + } + } + return idString; +} + +std::set splitStringToSet(const std::string &str, + const std::string &separator) +{ + std::set ret; + std::string::size_type pos1, pos2; + pos2 = 0; + pos1 = str.find(separator); + while (pos1 != std::string::npos) + { + if (pos1 != 0) + { + std::string item = str.substr(pos2, pos1 - pos2); + ret.insert(item); + } + pos2 = pos1 + separator.length(); + while (pos2 < str.length() && + str.substr(pos2, separator.length()) == separator) + pos2 += separator.length(); + pos1 = str.find(separator, pos2); + } + if (pos2 < str.length()) + ret.insert(str.substr(pos2)); + return ret; +} + +std::string getUuid() +{ +#if USE_OSSP_UUID + uuid_t *uuid; + uuid_create(&uuid); + uuid_make(uuid, UUID_MAKE_V4); + char *str{nullptr}; + size_t len{0}; + uuid_export(uuid, UUID_FMT_BIN, &str, &len); + uuid_destroy(uuid); + std::string ret{binaryStringToHex((const unsigned char *)str, len)}; + free(str); + return ret; +#elif defined __FreeBSD__ + uuid_t *uuid = new uuid_t; + char *binstr = (char *)malloc(16); + uuidgen(uuid, 1); +#if _BYTE_ORDER == _LITTLE_ENDIAN + uuid_enc_le(binstr, uuid); +#else /* _BYTE_ORDER != _LITTLE_ENDIAN */ + uuid_enc_be(binstr, uuid); +#endif /* _BYTE_ORDER == _LITTLE_ENDIAN */ + delete uuid; + std::string ret{binaryStringToHex((const unsigned char *)binstr, 16)}; + free(binstr); + return ret; +#elif defined _WIN32 + uuid_t uu; + UuidCreate(&uu); + char tempStr[100]; + auto len = snprintf(tempStr, + sizeof(tempStr), + "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + uu.Data1, + uu.Data2, + uu.Data3, + uu.Data4[0], + uu.Data4[1], + uu.Data4[2], + uu.Data4[3], + uu.Data4[4], + uu.Data4[5], + uu.Data4[6], + uu.Data4[7]); + return std::string{tempStr, static_cast(len)}; +#else + uuid_t uu; + uuid_generate(uu); + return binaryStringToHex(uu, 16); +#endif +} + +std::string base64Encode(const unsigned char *bytes_to_encode, + unsigned int in_len, + bool url_safe) +{ + std::string ret; + int i = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + const std::string &charSet = url_safe ? urlBase64Chars : base64Chars; + + while (in_len--) + { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) + { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; (i < 4); ++i) + ret += charSet[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for (int j = i; j < 3; ++j) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (int j = 0; (j < i + 1); ++j) + ret += charSet[char_array_4[j]]; + + while ((i++ < 3)) + ret += '='; + } + return ret; +} + +std::vector base64DecodeToVector(const std::string &encoded_string) +{ + auto in_len = encoded_string.size(); + int i = 0; + int in_{0}; + char char_array_4[4], char_array_3[3]; + std::vector ret; + ret.reserve(in_len); + + while (in_len-- && (encoded_string[in_] != '=') && + isBase64(encoded_string[in_])) + { + char_array_4[i++] = encoded_string[in_]; + ++in_; + if (i == 4) + { + for (i = 0; i < 4; ++i) + { + char_array_4[i] = base64CharMap.getIndex(char_array_4[i]); + } + + char_array_3[0] = + (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); ++i) + ret.push_back(char_array_3[i]); + i = 0; + } + } + + if (i) + { + for (int j = i; j < 4; ++j) + char_array_4[j] = 0; + + for (int j = 0; j < 4; ++j) + { + char_array_4[j] = base64CharMap.getIndex(char_array_4[j]); + } + + char_array_3[0] = + (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = + ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (int j = 0; (j < i - 1); ++j) + ret.push_back(char_array_3[j]); + } + + return ret; +} + +std::string base64Decode(const std::string &encoded_string) +{ + auto in_len = encoded_string.size(); + int i = 0; + int in_{0}; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && (encoded_string[in_] != '=') && + isBase64(encoded_string[in_])) + { + char_array_4[i++] = encoded_string[in_]; + ++in_; + if (i == 4) + { + for (i = 0; i < 4; ++i) + { + char_array_4[i] = base64CharMap.getIndex(char_array_4[i]); + } + char_array_3[0] = + (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); ++i) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) + { + for (int j = i; j < 4; ++j) + char_array_4[j] = 0; + + for (int j = 0; j < 4; ++j) + { + char_array_4[j] = base64CharMap.getIndex(char_array_4[j]); + } + + char_array_3[0] = + (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = + ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (int j = 0; (j < i - 1); ++j) + ret += char_array_3[j]; + } + + return ret; +} +static std::string charToHex(char c) +{ + std::string result; + char first, second; + + first = (c & 0xF0) / 16; + first += first > 9 ? 'A' - 10 : '0'; + second = c & 0x0F; + second += second > 9 ? 'A' - 10 : '0'; + + result.append(1, first); + result.append(1, second); + + return result; +} +std::string urlEncodeComponent(const std::string &src) +{ + std::string result; + std::string::const_iterator iter; + + for (iter = src.begin(); iter != src.end(); ++iter) + { + switch (*iter) + { + case ' ': + result.append(1, '+'); + break; + // alnum + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // mark + case '-': + case '_': + case '.': + case '!': + case '~': + case '*': + case '(': + case ')': + result.append(1, *iter); + break; + // escape + default: + result.append(1, '%'); + result.append(charToHex(*iter)); + break; + } + } + + return result; +} +std::string urlEncode(const std::string &src) +{ + std::string result; + std::string::const_iterator iter; + + for (iter = src.begin(); iter != src.end(); ++iter) + { + switch (*iter) + { + case ' ': + result.append(1, '+'); + break; + // alnum + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // mark + case '-': + case '_': + case '.': + case '!': + case '~': + case '*': + case '\'': + case '(': + case ')': + case '&': + case '=': + case '/': + case '\\': + case '?': + result.append(1, *iter); + break; + // escape + default: + result.append(1, '%'); + result.append(charToHex(*iter)); + break; + } + } + + return result; +} +bool needUrlDecoding(const char *begin, const char *end) +{ + return std::find_if(begin, end, [](const char c) { + return c == '+' || c == '%'; + }) != end; +} +std::string urlDecode(const char *begin, const char *end) +{ + std::string result; + size_t len = end - begin; + result.reserve(len * 2); + int hex = 0; + for (size_t i = 0; i < len; ++i) + { + switch (begin[i]) + { + case '+': + result += ' '; + break; + case '%': + if ((i + 2) < len && isxdigit(begin[i + 1]) && + isxdigit(begin[i + 2])) + { + unsigned int x1 = begin[i + 1]; + if (x1 >= '0' && x1 <= '9') + { + x1 -= '0'; + } + else if (x1 >= 'a' && x1 <= 'f') + { + x1 = x1 - 'a' + 10; + } + else if (x1 >= 'A' && x1 <= 'F') + { + x1 = x1 - 'A' + 10; + } + unsigned int x2 = begin[i + 2]; + if (x2 >= '0' && x2 <= '9') + { + x2 -= '0'; + } + else if (x2 >= 'a' && x2 <= 'f') + { + x2 = x2 - 'a' + 10; + } + else if (x2 >= 'A' && x2 <= 'F') + { + x2 = x2 - 'A' + 10; + } + hex = x1 * 16 + x2; + + result += char(hex); + i += 2; + } + else + { + result += '%'; + } + break; + default: + result += begin[i]; + break; + } + } + return result; +} + +/* Compress gzip data */ +std::string gzipCompress(const char *data, const size_t ndata) +{ + z_stream strm = {0}; + if (data && ndata > 0) + { + if (deflateInit2(&strm, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + MAX_WBITS + 16, + 8, + Z_DEFAULT_STRATEGY) != Z_OK) + { + LOG_ERROR << "deflateInit2 error!"; + return std::string{}; + } + std::string outstr; + outstr.resize(compressBound(static_cast(ndata))); + strm.next_in = (Bytef *)data; + strm.avail_in = static_cast(ndata); + int ret; + do + { + if (strm.total_out >= outstr.size()) + { + outstr.resize(strm.total_out * 2); + } + assert(outstr.size() >= strm.total_out); + strm.avail_out = static_cast(outstr.size() - strm.total_out); + strm.next_out = (Bytef *)outstr.data() + strm.total_out; + ret = deflate(&strm, Z_FINISH); /* no bad return value */ + if (ret == Z_STREAM_ERROR) + { + (void)deflateEnd(&strm); + return std::string{}; + } + } while (strm.avail_out == 0); + assert(strm.avail_in == 0); + assert(ret == Z_STREAM_END); /* stream will be complete */ + outstr.resize(strm.total_out); + /* clean up and return */ + (void)deflateEnd(&strm); + return outstr; + } + return std::string{}; +} + +std::string gzipDecompress(const char *data, const size_t ndata) +{ + if (ndata == 0) + return std::string(data, ndata); + + auto full_length = ndata; + + auto decompressed = std::string(full_length * 2, 0); + bool done = false; + + z_stream strm = {0}; + strm.next_in = (Bytef *)data; + strm.avail_in = static_cast(ndata); + strm.total_out = 0; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + if (inflateInit2(&strm, (15 + 32)) != Z_OK) + { + LOG_ERROR << "inflateInit2 error!"; + return std::string{}; + } + while (!done) + { + // Make sure we have enough room and reset the lengths. + if (strm.total_out >= decompressed.length()) + { + decompressed.resize(decompressed.length() * 2); + } + strm.next_out = (Bytef *)decompressed.data() + strm.total_out; + strm.avail_out = + static_cast(decompressed.length() - strm.total_out); + // Inflate another chunk. + int status = inflate(&strm, Z_SYNC_FLUSH); + if (status == Z_STREAM_END) + { + done = true; + } + else if (status != Z_OK) + { + break; + } + } + if (inflateEnd(&strm) != Z_OK) + return std::string{}; + // Set real length. + if (done) + { + decompressed.resize(strm.total_out); + return decompressed; + } + else + { + return std::string{}; + } +} + +char *getHttpFullDate(const trantor::Date &date) +{ + static thread_local int64_t lastSecond = 0; + static thread_local char lastTimeString[128] = {0}; + auto nowSecond = date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC; + if (nowSecond == lastSecond) + { + return lastTimeString; + } + lastSecond = nowSecond; + date.toCustomedFormattedString("%a, %d %b %Y %H:%M:%S GMT", + lastTimeString, + sizeof(lastTimeString)); + return lastTimeString; +} +trantor::Date getHttpDate(const std::string &httpFullDateString) +{ + static const std::array formats = { + // RFC822 (default) + "%a, %d %b %Y %H:%M:%S", + // RFC 850 (deprecated) + "%a, %d-%b-%y %H:%M:%S", + // ansi asctime format + "%a %b %d %H:%M:%S %Y", + // weird RFC 850-hybrid thing that reddit uses + "%a, %d-%b-%Y %H:%M:%S", + }; + struct tm tmptm; + for (const char *format : formats) + { + if (strptime(httpFullDateString.c_str(), format, &tmptm) != NULL) + { + auto epoch = timegm(&tmptm); + return trantor::Date(epoch * MICRO_SECONDS_PRE_SEC); + } + } + LOG_WARN << "invalid datetime format: '" << httpFullDateString << "'"; + return trantor::Date((std::numeric_limits::max)()); +} +std::string formattedString(const char *format, ...) +{ + std::string strBuffer(128, 0); + va_list ap, backup_ap; + va_start(ap, format); + va_copy(backup_ap, ap); + auto result = vsnprintf((char *)strBuffer.data(), + strBuffer.size(), + format, + backup_ap); + va_end(backup_ap); + if ((result >= 0) && ((std::string::size_type)result < strBuffer.size())) + { + strBuffer.resize(result); + } + else + { + while (true) + { + if (result < 0) + { + // Older snprintf() behavior. Just try doubling the buffer size + strBuffer.resize(strBuffer.size() * 2); + } + else + { + strBuffer.resize(result + 1); + } + + va_copy(backup_ap, ap); + auto result = vsnprintf((char *)strBuffer.data(), + strBuffer.size(), + format, + backup_ap); + va_end(backup_ap); + + if ((result >= 0) && + ((std::string::size_type)result < strBuffer.size())) + { + strBuffer.resize(result); + break; + } + } + } + va_end(ap); + return strBuffer; +} + +int createPath(const std::string &path) +{ + auto tmpPath = path; + std::stack pathStack; +#ifdef _WIN32 + while (_access(tmpPath.c_str(), 06) != 0) +#else + while (access(tmpPath.c_str(), F_OK) != 0) +#endif + { + if (tmpPath == "./" || tmpPath == "/") + return -1; + while (tmpPath[tmpPath.length() - 1] == '/') + tmpPath.resize(tmpPath.length() - 1); + auto pos = tmpPath.rfind('/'); + if (pos != std::string::npos) + { + pathStack.push(tmpPath.substr(pos)); + tmpPath = tmpPath.substr(0, pos + 1); + } + else + { + pathStack.push(tmpPath); + tmpPath.clear(); + break; + } + } + while (pathStack.size() > 0) + { + if (tmpPath.empty()) + { + tmpPath = pathStack.top(); + } + else + { + if (tmpPath[tmpPath.length() - 1] == '/') + { + tmpPath.append(pathStack.top()); + } + else + { + tmpPath.append("/").append(pathStack.top()); + } + } + pathStack.pop(); + +#ifdef _WIN32 + if (_mkdir(tmpPath.c_str()) == -1) +#else + if (mkdir(tmpPath.c_str(), 0755) == -1) +#endif + { + LOG_ERROR << "Can't create path:" << path; + return -1; + } + } + return 0; +} +#ifdef USE_BROTLI +std::string brotliCompress(const char *data, const size_t ndata) +{ + std::string ret; + if (ndata == 0) + return ret; + ret.resize(BrotliEncoderMaxCompressedSize(ndata)); + size_t encodedSize{ret.size()}; + auto r = BrotliEncoderCompress(5, + BROTLI_DEFAULT_WINDOW, + BROTLI_DEFAULT_MODE, + ndata, + (const uint8_t *)(data), + &encodedSize, + (uint8_t *)(ret.data())); + if (r == BROTLI_FALSE) + ret.resize(0); + else + ret.resize(encodedSize); + return ret; +} +std::string brotliDecompress(const char *data, const size_t ndata) +{ + if (ndata == 0) + return std::string(data, ndata); + + size_t availableIn = ndata; + auto nextIn = (const uint8_t *)(data); + auto decompressed = std::string(availableIn * 3, 0); + size_t availableOut = decompressed.size(); + auto nextOut = (uint8_t *)(decompressed.data()); + size_t totalOut{0}; + bool done = false; + auto s = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); + while (!done) + { + auto result = BrotliDecoderDecompressStream( + s, &availableIn, &nextIn, &availableOut, &nextOut, &totalOut); + if (result == BROTLI_DECODER_RESULT_SUCCESS) + { + decompressed.resize(totalOut); + done = true; + } + else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) + { + assert(totalOut == decompressed.size()); + decompressed.resize(totalOut * 2); + nextOut = (uint8_t *)(decompressed.data() + totalOut); + availableOut = totalOut; + } + else + { + decompressed.resize(0); + done = true; + } + } + BrotliDecoderDestroyInstance(s); + return decompressed; +} +#else +std::string brotliCompress(const char *data, const size_t ndata) +{ + LOG_ERROR << "If you do not have the brotli package installed, you cannot " + "use brotliCompress()"; + abort(); +} +std::string brotliDecompress(const char *data, const size_t ndata) +{ + LOG_ERROR << "If you do not have the brotli package installed, you cannot " + "use brotliDecompress()"; + abort(); +} +#endif + +std::string getMd5(const char *data, const size_t dataLen) +{ +#ifdef OpenSSL_FOUND + MD5_CTX c; + unsigned char md5[16] = {0}; + MD5_Init(&c); + MD5_Update(&c, data, dataLen); + MD5_Final(md5, &c); + return utils::binaryStringToHex(md5, 16); +#else + return Md5Encode::encode(data, dataLen); +#endif +} + +} // namespace utils +} // namespace drogon diff --git a/core/utilities.h b/core/utilities.h new file mode 100644 index 0000000..c228e27 --- /dev/null +++ b/core/utilities.h @@ -0,0 +1,153 @@ +/** + * + * Utilities.h + * 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 + * + */ + +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +char *strptime(const char *s, const char *f, struct tm *tm); +time_t timegm(struct tm *tm); +#endif +namespace drogon +{ +namespace utils +{ +/// Determine if the string is an integer +bool isInteger(const std::string &str); + +/// Generate random a string +/** + * @param length The string length + * The returned string consists of uppercase and lowercase letters and numbers + */ +std::string genRandomString(int length); + +/// Convert a binary string to hex format +std::string binaryStringToHex(const unsigned char *ptr, size_t length); + +/// Get a binary string from hexadecimal format +std::string hexToBinaryString(const char *ptr, size_t length); + +/// Get a binary vector from hexadecimal format +std::vector hexToBinaryVector(const char *ptr, size_t length); + +/// Split the string into multiple separated strings. +/** + * @param acceptEmptyString if true, empty strings are accepted in the + * result, for example, splitting the ",1,2,,3," by "," produces + * ["","1","2","","3",""] + */ +inline std::vector splitString(const std::string &str, + const std::string &separator, + bool acceptEmptyString = false) +{ + return trantor::splitString(str, separator, acceptEmptyString); +} + +std::set splitStringToSet(const std::string &str, + const std::string &separator); + +/// Get UUID string. +std::string getUuid(); + +/// Encode the string to base64 format. +std::string base64Encode(const unsigned char *bytes_to_encode, + unsigned int in_len, + bool url_safe = false); + +/// Decode the base64 format string. +std::string base64Decode(const std::string &encoded_string); +std::vector base64DecodeToVector(const std::string &encoded_string); + +/// Check if the string need decoding +bool needUrlDecoding(const char *begin, const char *end); + +/// Decode from or encode to the URL format string +std::string urlDecode(const char *begin, const char *end); +inline std::string urlDecode(const std::string &szToDecode) +{ + auto begin = szToDecode.data(); + return urlDecode(begin, begin + szToDecode.length()); +} +inline std::string urlDecode(const string_view &szToDecode) +{ + auto begin = szToDecode.data(); + return urlDecode(begin, begin + szToDecode.length()); +} + +std::string urlEncode(const std::string &); +std::string urlEncodeComponent(const std::string &); + +/// Get the MD5 digest of a string. +std::string getMd5(const char *data, const size_t dataLen); +inline std::string getMd5(const std::string &originalString) +{ + return getMd5(originalString.data(), originalString.length()); +} + +/// Commpress or decompress data using gzip lib. +/** + * @param data the input data + * @param ndata the input data length + */ +std::string gzipCompress(const char *data, const size_t ndata); +std::string gzipDecompress(const char *data, const size_t ndata); + +/// Commpress or decompress data using brotli lib. +/** + * @param data the input data + * @param ndata the input data length + */ +std::string brotliCompress(const char *data, const size_t ndata); +std::string brotliDecompress(const char *data, const size_t ndata); + +/// Get the http full date string +/** + * rfc2616-3.3.1 + * Full Date format(RFC 822) + * like this: + * @code + Sun, 06 Nov 1994 08:49:37 GMT + Wed, 12 Sep 2018 09:22:40 GMT + @endcode + */ +char *getHttpFullDate(const trantor::Date &date = trantor::Date::now()); + +/// Get the trantor::Date object according to the http full date string +/** + * Returns trantor::Date(std::numeric_limits::max()) upon failure. + */ +trantor::Date getHttpDate(const std::string &httpFullDateString); + +/// Get a formatted string +std::string formattedString(const char *format, ...); + +/// Recursively create a file system path +/** + * Return 0 or -1 on success or failure. + */ +int createPath(const std::string &path); + +} // namespace utils +} // namespace drogon