mirror of
https://github.com/Relintai/rcpp_framework.git
synced 2025-05-10 21:52:08 +02:00
Started migrating to trantor. Took lots of core utility classes from drogon aswell. At the moment the project doesn't compiles!
This commit is contained in:
parent
5cc22461fd
commit
647694b576
@ -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"])
|
||||
|
161
core/HttpFileImpl.cc
Normal file
161
core/HttpFileImpl.cc
Normal file
@ -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 <drogon/MultiPart.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
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<HttpFileImpl> &&implPtr)
|
||||
: implPtr_(std::move(implPtr))
|
||||
{
|
||||
}
|
100
core/HttpFileImpl.h
Normal file
100
core/HttpFileImpl.h
Normal file
@ -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 <drogon/utils/string_view.h>
|
||||
#include <drogon/HttpRequest.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
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
|
292
core/HttpResponseParser.cc
Normal file
292
core/HttpResponseParser.cc
Normal file
@ -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 <iostream>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
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<HttpMessageStringBody>();
|
||||
}
|
||||
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:"<<request_->content_;
|
||||
LOG_TRACE << "content(END)";
|
||||
hasMore = false;
|
||||
}
|
||||
}
|
||||
else if (status_ == HttpResponseParseStatus::kExpectClose)
|
||||
{
|
||||
if (!responsePtr_->bodyPtr_)
|
||||
{
|
||||
responsePtr_->bodyPtr_ =
|
||||
std::make_shared<HttpMessageStringBody>();
|
||||
}
|
||||
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="<<currentChunkLength_;
|
||||
if (buf->readableBytes() >= (currentChunkLength_ + 2))
|
||||
{
|
||||
if (*(buf->peek() + currentChunkLength_) == '\r' &&
|
||||
*(buf->peek() + currentChunkLength_ + 1) == '\n')
|
||||
{
|
||||
if (!responsePtr_->bodyPtr_)
|
||||
{
|
||||
responsePtr_->bodyPtr_ =
|
||||
std::make_shared<HttpMessageStringBody>();
|
||||
}
|
||||
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;
|
||||
}
|
75
core/HttpResponseParser.h
Normal file
75
core/HttpResponseParser.h
Normal file
@ -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 <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
|
||||
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
|
81
core/SessionManager.cc
Normal file
81
core/SessionManager.cc
Normal file
@ -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 <drogon/utils/Utilities.h>
|
||||
|
||||
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<CacheMap<std::string, SessionPtr>>(
|
||||
new CacheMap<std::string, SessionPtr>(
|
||||
loop_, 1.0, wheelNum, bucketNum));
|
||||
}
|
||||
else if (timeout_ == 0)
|
||||
{
|
||||
sessionMapPtr_ = std::unique_ptr<CacheMap<std::string, SessionPtr>>(
|
||||
new CacheMap<std::string, SessionPtr>(loop_, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
SessionPtr SessionManager::getSession(const std::string &sessionID,
|
||||
bool needToSet)
|
||||
{
|
||||
assert(!sessionID.empty());
|
||||
SessionPtr sessionPtr;
|
||||
std::lock_guard<std::mutex> lock(mapMutex_);
|
||||
if (sessionMapPtr_->findAndFetch(sessionID, sessionPtr) == false)
|
||||
{
|
||||
sessionPtr =
|
||||
std::shared_ptr<Session>(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);
|
||||
});
|
||||
}
|
44
core/SessionManager.h
Normal file
44
core/SessionManager.h
Normal file
@ -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 <drogon/Session.h>
|
||||
#include <drogon/CacheMap.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
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<CacheMap<std::string, SessionPtr>> sessionMapPtr_;
|
||||
std::mutex mapMutex_;
|
||||
trantor::EventLoop *loop_;
|
||||
size_t timeout_;
|
||||
};
|
||||
} // namespace drogon
|
31
core/any.h
Normal file
31
core/any.h
Normal file
@ -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 <any>
|
||||
#else
|
||||
#include <boost/any.hpp>
|
||||
#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
|
@ -12,6 +12,12 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <trantor/utils/Logger.h>
|
||||
|
||||
#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<SharedLibManager>(
|
||||
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<void(const HttpResponsePtr &)> &&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::function<void(const HttpResponsePtr &)> >(
|
||||
std::move(callback));
|
||||
doAdvicesChain(
|
||||
preRoutingAdvices_,
|
||||
0,
|
||||
req,
|
||||
std::make_shared<std::function<void(const HttpResponsePtr &)> >(
|
||||
[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<void(const HttpResponsePtr &)> &&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::function<void(const HttpResponsePtr &)> >(
|
||||
std::move(callback));
|
||||
doAdvicesChain(
|
||||
preRoutingAdvices_,
|
||||
0,
|
||||
req,
|
||||
std::make_shared<std::function<void(const HttpResponsePtr &)> >(
|
||||
[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<std::mutex> 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<std::mutex> 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() {
|
||||
|
@ -7,9 +7,16 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/net/callbacks.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
|
||||
#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<void(const HttpResponsePtr &)> &&callback);
|
||||
void onNewWebsockRequest(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
|
||||
const WebSocketConnectionPtr &wsConnPtr);
|
||||
void onConnection(const trantor::TcpConnectionPtr &conn);
|
||||
|
||||
Application();
|
||||
virtual ~Application();
|
||||
|
||||
static Application *get_instance();
|
||||
|
||||
std::unique_ptr<ListenerManager> listenerManagerPtr_;
|
||||
|
||||
public:
|
||||
static HandlerInstance index_func;
|
||||
static std::map<std::string, HandlerInstance> main_route_map;
|
||||
@ -44,8 +62,15 @@ public:
|
||||
static std::map<int, std::function<void(int, Request *)> > error_handler_map;
|
||||
static std::function<void(int, Request *)> default_error_handler_func;
|
||||
|
||||
size_t idleConnectionTimeout_;
|
||||
size_t threadNum_;
|
||||
std::string sslCertPath_;
|
||||
std::string sslKeyPath_;
|
||||
std::vector<std::function<HttpResponsePtr(const HttpRequestPtr &)> > syncAdvices_;
|
||||
|
||||
private:
|
||||
static Application *_instance;
|
||||
bool _running;
|
||||
};
|
||||
|
||||
#endif
|
130
core/attribute.h
Normal file
130
core/attribute.h
Normal file
@ -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 <core/any.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
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<std::string>("user name");
|
||||
@endcode
|
||||
*/
|
||||
template <typename T>
|
||||
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<T>(&(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<std::string, any>;
|
||||
AttributesMap attributesMap_;
|
||||
};
|
||||
|
||||
using AttributesPtr = std::shared_ptr<Attributes>;
|
||||
|
||||
} // namespace drogon
|
95
core/cache_file.cpp
Normal file
95
core/cache_file.cpp
Normal file
@ -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 <trantor/utils/Logger.h>
|
||||
#ifdef _WIN32
|
||||
#include <mman.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#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<char *>(
|
||||
mmap(nullptr, dataLength_, PROT_READ, MAP_SHARED, fd, 0));
|
||||
if (data_ == MAP_FAILED)
|
||||
{
|
||||
data_ = nullptr;
|
||||
LOG_SYSERR << "mmap:";
|
||||
}
|
||||
}
|
||||
return data_;
|
||||
}
|
50
core/cache_file.h
Normal file
50
core/cache_file.h
Normal file
@ -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 <core/string_view.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
|
||||
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
|
50
core/cookie.cpp
Normal file
50
core/cookie.cpp
Normal file
@ -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 <core/cookie.h>
|
||||
#include <core/utilities.h>
|
||||
|
||||
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<int64_t>::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;
|
||||
}
|
252
core/cookie.h
Normal file
252
core/cookie.h
Normal file
@ -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 <trantor/utils/Date.h>
|
||||
#include <string>
|
||||
#include <limits>
|
||||
|
||||
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<int64_t>::max)()};
|
||||
bool httpOnly_{true};
|
||||
bool secure_{false};
|
||||
std::string domain_;
|
||||
std::string path_;
|
||||
std::string key_;
|
||||
std::string value_;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
32
core/http_file_upload_request.cpp
Normal file
32
core/http_file_upload_request.cpp
Normal file
@ -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 <core/upload_file.h>
|
||||
#include <core/utilities.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
HttpFileUploadRequest::HttpFileUploadRequest(
|
||||
const std::vector<UploadFile> &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;
|
||||
}
|
40
core/http_file_upload_request.h
Normal file
40
core/http_file_upload_request.h
Normal file
@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpFileUploadRequest : public HttpRequest
|
||||
{
|
||||
public:
|
||||
const std::string &boundary() const
|
||||
{
|
||||
return boundary_;
|
||||
}
|
||||
const std::vector<UploadFile> &files() const
|
||||
{
|
||||
return files_;
|
||||
}
|
||||
explicit HttpFileUploadRequest(const std::vector<UploadFile> &files);
|
||||
|
||||
private:
|
||||
std::string boundary_;
|
||||
std::vector<UploadFile> files_;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
159
core/http_message_body.h
Normal file
159
core/http_message_body.h
Normal file
@ -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 <string>
|
||||
|
||||
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<char *>(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<char *>(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<std::string>(body_.data(), body_.length());
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyString_ = std::make_unique<std::string>();
|
||||
}
|
||||
}
|
||||
return *bodyString_;
|
||||
}
|
||||
virtual std::string &getString() override
|
||||
{
|
||||
if (!bodyString_)
|
||||
{
|
||||
if (!body_.empty())
|
||||
{
|
||||
bodyString_ =
|
||||
std::make_unique<std::string>(body_.data(), body_.length());
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyString_ = std::make_unique<std::string>();
|
||||
}
|
||||
}
|
||||
return *bodyString_;
|
||||
}
|
||||
|
||||
private:
|
||||
string_view body_;
|
||||
mutable std::unique_ptr<std::string> bodyString_;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
699
core/http_request.cpp
Normal file
699
core/http_request.cpp
Normal file
@ -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 <core/utilities.h>
|
||||
|
||||
#include "core/application.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
void HttpRequest::parseJson() const
|
||||
{
|
||||
auto input = contentView();
|
||||
if (input.empty())
|
||||
return;
|
||||
if (contentType_ == CT_APPLICATION_JSON ||
|
||||
getHeaderBy("content-type").find("application/json") !=
|
||||
std::string::npos)
|
||||
{
|
||||
static std::once_flag once;
|
||||
static Json::CharReaderBuilder builder;
|
||||
std::call_once(once, []() { builder["collectComments"] = false; });
|
||||
jsonPtr_ = std::make_shared<Json::Value>();
|
||||
JSONCPP_STRING errs;
|
||||
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
|
||||
if (!reader->parse(input.data(),
|
||||
input.data() + input.size(),
|
||||
jsonPtr_.get(),
|
||||
&errs))
|
||||
{
|
||||
LOG_ERROR << errs;
|
||||
jsonPtr_.reset();
|
||||
jsonParsingErrorPtr_ =
|
||||
std::make_unique<std::string>(std::move(errs));
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonParsingErrorPtr_.reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonPtr_.reset();
|
||||
jsonParsingErrorPtr_ =
|
||||
std::make_unique<std::string>("content type error");
|
||||
}
|
||||
}
|
||||
void HttpRequest::parseParameters() const
|
||||
{
|
||||
auto input = queryView();
|
||||
if (!input.empty())
|
||||
{
|
||||
string_view::size_type pos = 0;
|
||||
while ((input[pos] == '?' || isspace(input[pos])) &&
|
||||
pos < input.length())
|
||||
{
|
||||
++pos;
|
||||
}
|
||||
auto value = input.substr(pos);
|
||||
while ((pos = value.find('&')) != string_view::npos)
|
||||
{
|
||||
auto coo = value.substr(0, pos);
|
||||
auto epos = coo.find('=');
|
||||
if (epos != string_view::npos)
|
||||
{
|
||||
auto key = coo.substr(0, epos);
|
||||
string_view::size_type cpos = 0;
|
||||
while (cpos < key.length() && isspace(key[cpos]))
|
||||
++cpos;
|
||||
key = key.substr(cpos);
|
||||
auto pvalue = coo.substr(epos + 1);
|
||||
std::string pdecode = utils::urlDecode(pvalue);
|
||||
std::string keydecode = utils::urlDecode(key);
|
||||
parameters_[keydecode] = pdecode;
|
||||
}
|
||||
value = value.substr(pos + 1);
|
||||
}
|
||||
if (value.length() > 0)
|
||||
{
|
||||
auto &coo = value;
|
||||
auto epos = coo.find('=');
|
||||
if (epos != string_view::npos)
|
||||
{
|
||||
auto key = coo.substr(0, epos);
|
||||
string_view::size_type cpos = 0;
|
||||
while (cpos < key.length() && isspace(key[cpos]))
|
||||
++cpos;
|
||||
key = key.substr(cpos);
|
||||
auto pvalue = coo.substr(epos + 1);
|
||||
std::string pdecode = utils::urlDecode(pvalue);
|
||||
std::string keydecode = utils::urlDecode(key);
|
||||
parameters_[keydecode] = pdecode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input = contentView();
|
||||
if (input.empty())
|
||||
return;
|
||||
std::string type = getHeaderBy("content-type");
|
||||
std::transform(type.begin(), type.end(), type.begin(), tolower);
|
||||
if (type.empty() ||
|
||||
type.find("application/x-www-form-urlencoded") != std::string::npos)
|
||||
{
|
||||
string_view::size_type pos = 0;
|
||||
while ((input[pos] == '?' || isspace(input[pos])) &&
|
||||
pos < input.length())
|
||||
{
|
||||
++pos;
|
||||
}
|
||||
auto value = input.substr(pos);
|
||||
while ((pos = value.find('&')) != string_view::npos)
|
||||
{
|
||||
auto coo = value.substr(0, pos);
|
||||
auto epos = coo.find('=');
|
||||
if (epos != string_view::npos)
|
||||
{
|
||||
auto key = coo.substr(0, epos);
|
||||
string_view::size_type cpos = 0;
|
||||
while (cpos < key.length() && isspace(key[cpos]))
|
||||
++cpos;
|
||||
key = key.substr(cpos);
|
||||
auto pvalue = coo.substr(epos + 1);
|
||||
std::string pdecode = utils::urlDecode(pvalue);
|
||||
std::string keydecode = utils::urlDecode(key);
|
||||
parameters_[keydecode] = pdecode;
|
||||
}
|
||||
value = value.substr(pos + 1);
|
||||
}
|
||||
if (value.length() > 0)
|
||||
{
|
||||
auto &coo = value;
|
||||
auto epos = coo.find('=');
|
||||
if (epos != string_view::npos)
|
||||
{
|
||||
auto key = coo.substr(0, epos);
|
||||
string_view::size_type cpos = 0;
|
||||
while (cpos < key.length() && isspace(key[cpos]))
|
||||
++cpos;
|
||||
key = key.substr(cpos);
|
||||
auto pvalue = coo.substr(epos + 1);
|
||||
std::string pdecode = utils::urlDecode(pvalue);
|
||||
std::string keydecode = utils::urlDecode(key);
|
||||
parameters_[keydecode] = pdecode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::appendToBuffer(trantor::MsgBuffer *output) const
|
||||
{
|
||||
switch (method_)
|
||||
{
|
||||
case Get:
|
||||
output->append("GET ");
|
||||
break;
|
||||
case Post:
|
||||
output->append("POST ");
|
||||
break;
|
||||
case Head:
|
||||
output->append("HEAD ");
|
||||
break;
|
||||
case Put:
|
||||
output->append("PUT ");
|
||||
break;
|
||||
case Delete:
|
||||
output->append("DELETE ");
|
||||
break;
|
||||
case Options:
|
||||
output->append("OPTIONS ");
|
||||
break;
|
||||
case Patch:
|
||||
output->append("PATCH ");
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!path_.empty())
|
||||
{
|
||||
output->append(utils::urlEncode(path_));
|
||||
}
|
||||
else
|
||||
{
|
||||
output->append("/");
|
||||
}
|
||||
|
||||
std::string content;
|
||||
|
||||
if (!passThrough_ &&
|
||||
(!parameters_.empty() && contentType_ != CT_MULTIPART_FORM_DATA))
|
||||
{
|
||||
for (auto const &p : parameters_)
|
||||
{
|
||||
content.append(utils::urlEncodeComponent(p.first));
|
||||
content.append("=");
|
||||
content.append(utils::urlEncodeComponent(p.second));
|
||||
content.append("&");
|
||||
}
|
||||
content.resize(content.length() - 1);
|
||||
if (method_ == Get || method_ == Delete || method_ == Head)
|
||||
{
|
||||
auto ret = std::find(output->peek(),
|
||||
(const char *)output->beginWrite(),
|
||||
'?');
|
||||
if (ret != output->beginWrite())
|
||||
{
|
||||
if (ret != output->beginWrite() - 1)
|
||||
{
|
||||
output->append("&");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
output->append("?");
|
||||
}
|
||||
output->append(content);
|
||||
content.clear();
|
||||
}
|
||||
else if (contentType_ == CT_APPLICATION_JSON)
|
||||
{
|
||||
/// Can't set parameters in content in this case
|
||||
LOG_ERROR
|
||||
<< "You can't set parameters in the query string when the "
|
||||
"request content type is JSON and http method "
|
||||
"is POST or PUT";
|
||||
LOG_ERROR << "Please put these parameters into the path or "
|
||||
"into the json "
|
||||
"string";
|
||||
content.clear();
|
||||
}
|
||||
}
|
||||
|
||||
output->append(" ");
|
||||
if (version_ == Version::kHttp11)
|
||||
{
|
||||
output->append("HTTP/1.1");
|
||||
}
|
||||
else if (version_ == Version::kHttp10)
|
||||
{
|
||||
output->append("HTTP/1.0");
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
output->append("\r\n");
|
||||
|
||||
if (!passThrough_ && contentType_ == CT_MULTIPART_FORM_DATA)
|
||||
{
|
||||
auto mReq = dynamic_cast<const HttpFileUploadRequest *>(this);
|
||||
if (mReq)
|
||||
{
|
||||
for (auto ¶m : mReq->getParameters())
|
||||
{
|
||||
content.append("--");
|
||||
content.append(mReq->boundary());
|
||||
content.append("\r\n");
|
||||
content.append("Content-Disposition: form-data; name=\"");
|
||||
content.append(param.first);
|
||||
content.append("\"\r\n\r\n");
|
||||
content.append(param.second);
|
||||
content.append("\r\n");
|
||||
}
|
||||
for (auto &file : mReq->files())
|
||||
{
|
||||
content.append("--");
|
||||
content.append(mReq->boundary());
|
||||
content.append("\r\n");
|
||||
content.append("Content-Disposition: form-data; name=\"");
|
||||
content.append(file.itemName());
|
||||
content.append("\"; filename=\"");
|
||||
content.append(file.fileName());
|
||||
content.append("\"\r\n\r\n");
|
||||
std::ifstream infile(file.path(), std::ifstream::binary);
|
||||
if (!infile)
|
||||
{
|
||||
LOG_ERROR << file.path() << " not found";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::streambuf *pbuf = infile.rdbuf();
|
||||
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
|
||||
pbuf->pubseekoff(0, infile.beg); // rewind
|
||||
std::string str;
|
||||
str.resize(filesize);
|
||||
pbuf->sgetn(&str[0], filesize);
|
||||
content.append(std::move(str));
|
||||
}
|
||||
content.append("\r\n");
|
||||
}
|
||||
content.append("--");
|
||||
content.append(mReq->boundary());
|
||||
content.append("--");
|
||||
}
|
||||
}
|
||||
assert(!(!content.empty() && !content_.empty()));
|
||||
if (!passThrough_ && (!content.empty() || !content_.empty()))
|
||||
{
|
||||
char buf[64];
|
||||
auto len = snprintf(buf,
|
||||
sizeof(buf),
|
||||
contentLengthFormatString<decltype(content.length())>(),
|
||||
content.length() + content_.length());
|
||||
output->append(buf, len);
|
||||
if (contentTypeString_.empty())
|
||||
{
|
||||
auto &type = webContentTypeToString(contentType_);
|
||||
output->append(type.data(), type.length());
|
||||
}
|
||||
}
|
||||
if (!passThrough_ && !contentTypeString_.empty())
|
||||
{
|
||||
output->append(contentTypeString_);
|
||||
}
|
||||
for (auto it = headers_.begin(); it != headers_.end(); ++it)
|
||||
{
|
||||
output->append(it->first);
|
||||
output->append(": ");
|
||||
output->append(it->second);
|
||||
output->append("\r\n");
|
||||
}
|
||||
if (cookies_.size() > 0)
|
||||
{
|
||||
output->append("Cookie: ");
|
||||
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
|
||||
{
|
||||
output->append(it->first);
|
||||
output->append("= ");
|
||||
output->append(it->second);
|
||||
output->append(";");
|
||||
}
|
||||
output->unwrite(1); // delete last ';'
|
||||
output->append("\r\n");
|
||||
}
|
||||
|
||||
output->append("\r\n");
|
||||
if (!content.empty())
|
||||
output->append(content);
|
||||
if (!content_.empty())
|
||||
output->append(content_);
|
||||
}
|
||||
|
||||
void HttpRequest::addHeader(const char *start,
|
||||
const char *colon,
|
||||
const char *end)
|
||||
{
|
||||
std::string field(start, colon);
|
||||
// Field name is case-insensitive.so we transform it to lower;(rfc2616-4.2)
|
||||
std::transform(field.begin(), field.end(), field.begin(), ::tolower);
|
||||
++colon;
|
||||
while (colon < end && isspace(*colon))
|
||||
{
|
||||
++colon;
|
||||
}
|
||||
std::string value(colon, end);
|
||||
while (!value.empty() && isspace(value[value.size() - 1]))
|
||||
{
|
||||
value.resize(value.size() - 1);
|
||||
}
|
||||
if (field.length() == 6 && field == "cookie")
|
||||
{
|
||||
LOG_TRACE << "cookies!!!:" << value;
|
||||
std::string::size_type pos;
|
||||
while ((pos = value.find(';')) != std::string::npos)
|
||||
{
|
||||
std::string coo = value.substr(0, pos);
|
||||
auto epos = coo.find('=');
|
||||
if (epos != std::string::npos)
|
||||
{
|
||||
std::string cookie_name = coo.substr(0, epos);
|
||||
std::string::size_type cpos = 0;
|
||||
while (cpos < cookie_name.length() &&
|
||||
isspace(cookie_name[cpos]))
|
||||
++cpos;
|
||||
cookie_name = cookie_name.substr(cpos);
|
||||
std::string cookie_value = coo.substr(epos + 1);
|
||||
cookies_[std::move(cookie_name)] = std::move(cookie_value);
|
||||
}
|
||||
value = value.substr(pos + 1);
|
||||
}
|
||||
if (value.length() > 0)
|
||||
{
|
||||
std::string &coo = value;
|
||||
auto epos = coo.find('=');
|
||||
if (epos != std::string::npos)
|
||||
{
|
||||
std::string cookie_name = coo.substr(0, epos);
|
||||
std::string::size_type cpos = 0;
|
||||
while (cpos < cookie_name.length() &&
|
||||
isspace(cookie_name[cpos]))
|
||||
++cpos;
|
||||
cookie_name = cookie_name.substr(cpos);
|
||||
std::string cookie_value = coo.substr(epos + 1);
|
||||
cookies_[std::move(cookie_name)] = std::move(cookie_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (field.length())
|
||||
{
|
||||
case 6:
|
||||
if (field == "expect")
|
||||
{
|
||||
expectPtr_ = std::make_unique<std::string>(value);
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
{
|
||||
if (field == "connection")
|
||||
{
|
||||
if (version_ == Version::kHttp11)
|
||||
{
|
||||
if (value.length() == 5 && value == "close")
|
||||
keepAlive_ = false;
|
||||
}
|
||||
else if (value.length() == 10 &&
|
||||
(value == "Keep-Alive" || value == "keep-alive"))
|
||||
{
|
||||
keepAlive_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
headers_.emplace(std::move(field), std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
HttpRequestPtr HttpRequest::newHttpRequest()
|
||||
{
|
||||
auto req = std::make_shared<HttpRequest>(nullptr);
|
||||
req->setMethod(drogon::Get);
|
||||
req->setVersion(drogon::Version::kHttp11);
|
||||
return req;
|
||||
}
|
||||
|
||||
HttpRequestPtr HttpRequest::newHttpFormPostRequest()
|
||||
{
|
||||
auto req = std::make_shared<HttpRequest>(nullptr);
|
||||
req->setMethod(drogon::Post);
|
||||
req->setVersion(drogon::Version::kHttp11);
|
||||
req->contentType_ = CT_APPLICATION_X_FORM;
|
||||
req->flagForParsingContentType_ = true;
|
||||
return req;
|
||||
}
|
||||
|
||||
HttpRequestPtr HttpRequest::newHttpJsonRequest(const Json::Value &data)
|
||||
{
|
||||
static std::once_flag once;
|
||||
static Json::StreamWriterBuilder builder;
|
||||
std::call_once(once, []() {
|
||||
builder["commentStyle"] = "None";
|
||||
builder["indentation"] = "";
|
||||
|
||||
/*
|
||||
if (!Application::get_instance()->isUnicodeEscapingUsedInJson())
|
||||
{
|
||||
builder["emitUTF8"] = true;
|
||||
}*/
|
||||
|
||||
});
|
||||
auto req = std::make_shared<HttpRequest>(nullptr);
|
||||
req->setMethod(drogon::Get);
|
||||
req->setVersion(drogon::Version::kHttp11);
|
||||
req->contentType_ = CT_APPLICATION_JSON;
|
||||
req->setContent(writeString(builder, data));
|
||||
req->flagForParsingContentType_ = true;
|
||||
return req;
|
||||
}
|
||||
|
||||
HttpRequestPtr HttpRequest::newFileUploadRequest(
|
||||
const std::vector<UploadFile> &files)
|
||||
{
|
||||
return std::make_shared<HttpFileUploadRequest>(files);
|
||||
}
|
||||
|
||||
void HttpRequest::swap(HttpRequest &that) noexcept
|
||||
{
|
||||
using std::swap;
|
||||
swap(method_, that.method_);
|
||||
swap(version_, that.version_);
|
||||
swap(flagForParsingJson_, that.flagForParsingJson_);
|
||||
swap(flagForParsingParameters_, that.flagForParsingParameters_);
|
||||
swap(matchedPathPattern_, that.matchedPathPattern_);
|
||||
swap(path_, that.path_);
|
||||
swap(query_, that.query_);
|
||||
swap(headers_, that.headers_);
|
||||
swap(cookies_, that.cookies_);
|
||||
swap(parameters_, that.parameters_);
|
||||
swap(jsonPtr_, that.jsonPtr_);
|
||||
swap(sessionPtr_, that.sessionPtr_);
|
||||
swap(attributesPtr_, that.attributesPtr_);
|
||||
swap(cacheFilePtr_, that.cacheFilePtr_);
|
||||
swap(peer_, that.peer_);
|
||||
swap(local_, that.local_);
|
||||
swap(creationDate_, that.creationDate_);
|
||||
swap(content_, that.content_);
|
||||
swap(expectPtr_, that.expectPtr_);
|
||||
swap(contentType_, that.contentType_);
|
||||
swap(contentTypeString_, that.contentTypeString_);
|
||||
swap(keepAlive_, that.keepAlive_);
|
||||
swap(loop_, that.loop_);
|
||||
swap(flagForParsingContentType_, that.flagForParsingContentType_);
|
||||
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
|
||||
}
|
||||
|
||||
const char *HttpRequest::methodString() const
|
||||
{
|
||||
const char *result = "UNKNOWN";
|
||||
switch (method_)
|
||||
{
|
||||
case Get:
|
||||
result = "GET";
|
||||
break;
|
||||
case Post:
|
||||
result = "POST";
|
||||
break;
|
||||
case Head:
|
||||
result = "HEAD";
|
||||
break;
|
||||
case Put:
|
||||
result = "PUT";
|
||||
break;
|
||||
case Delete:
|
||||
result = "DELETE";
|
||||
break;
|
||||
case Options:
|
||||
result = "OPTIONS";
|
||||
break;
|
||||
case Patch:
|
||||
result = "PATCH";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HttpRequest::setMethod(const char *start, const char *end)
|
||||
{
|
||||
assert(method_ == Invalid);
|
||||
string_view m(start, end - start);
|
||||
switch (m.length())
|
||||
{
|
||||
case 3:
|
||||
if (m == "GET")
|
||||
{
|
||||
method_ = Get;
|
||||
}
|
||||
else if (m == "PUT")
|
||||
{
|
||||
method_ = Put;
|
||||
}
|
||||
else
|
||||
{
|
||||
method_ = Invalid;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (m == "POST")
|
||||
{
|
||||
method_ = Post;
|
||||
}
|
||||
else if (m == "HEAD")
|
||||
{
|
||||
method_ = Head;
|
||||
}
|
||||
else
|
||||
{
|
||||
method_ = Invalid;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if (m == "PATCH")
|
||||
{
|
||||
method_ = Patch;
|
||||
}
|
||||
else
|
||||
{
|
||||
method_ = Invalid;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if (m == "DELETE")
|
||||
{
|
||||
method_ = Delete;
|
||||
}
|
||||
else
|
||||
{
|
||||
method_ = Invalid;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
if (m == "OPTIONS")
|
||||
{
|
||||
method_ = Options;
|
||||
}
|
||||
else
|
||||
{
|
||||
method_ = Invalid;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
method_ = Invalid;
|
||||
break;
|
||||
}
|
||||
|
||||
// if (method_ != Invalid)
|
||||
// {
|
||||
// content_ = "";
|
||||
// query_ = "";
|
||||
// cookies_.clear();
|
||||
// parameters_.clear();
|
||||
// headers_.clear();
|
||||
// }
|
||||
return method_ != Invalid;
|
||||
}
|
||||
|
||||
HttpRequest::~HttpRequest()
|
||||
{
|
||||
}
|
||||
|
||||
void HttpRequest::reserveBodySize(size_t length)
|
||||
{
|
||||
if (length <= 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<CacheFile>(tmpfile);
|
||||
*/
|
||||
}
|
723
core/http_request.h
Normal file
723
core/http_request.h
Normal file
@ -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 <core/attribute.h>
|
||||
#include <core/http_types.h>
|
||||
#include <core/session.h>
|
||||
#include <core/string_view.h>
|
||||
#include <core/upload_file.h>
|
||||
#include <json/json.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/utils/Date.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "cache_file.h"
|
||||
#include "core/http_utils.h"
|
||||
#include <core/http_request.h>
|
||||
#include <core/utilities.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace drogon {
|
||||
class HttpRequest;
|
||||
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
|
||||
|
||||
/**
|
||||
* @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 <typename T>
|
||||
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 <typename T>
|
||||
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 &>(const Json::Value &pJson);
|
||||
template <>
|
||||
HttpRequestPtr toRequest(Json::Value &&pJson);
|
||||
template <>
|
||||
inline HttpRequestPtr toRequest<Json::Value &>(Json::Value &pJson) {
|
||||
return toRequest((const Json::Value &)pJson);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::shared_ptr<Json::Value> 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<Json::Value> specialization version is available above, so
|
||||
* we can use the following code to get a json object:
|
||||
* @code
|
||||
std::shared_ptr<Json::Value> 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 <typename T>
|
||||
operator T() const {
|
||||
return fromRequest<T>(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This template enables explicit type conversion, see the above
|
||||
* template.
|
||||
*/
|
||||
template <typename T>
|
||||
T as() const {
|
||||
return fromRequest<T>(*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<std::string, std::string> &headers() const {
|
||||
return headers_;
|
||||
}
|
||||
|
||||
/// Get all headers of the request
|
||||
const std::unordered_map<std::string, std::string> &getHeaders() const {
|
||||
return headers();
|
||||
}
|
||||
|
||||
/// Get all cookies of the request
|
||||
virtual const std::unordered_map<std::string, std::string> &cookies() const {
|
||||
return cookies_;
|
||||
}
|
||||
|
||||
/// Get all cookies of the request
|
||||
const std::unordered_map<std::string, std::string> &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<Attributes>();
|
||||
}
|
||||
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<std::string, std::string> ¶meters()
|
||||
const {
|
||||
parseParametersOnce();
|
||||
return parameters_;
|
||||
}
|
||||
|
||||
/// Get parameters of the request.
|
||||
const std::unordered_map<std::string, std::string> &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<Json::Value> &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<Json::Value> &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<UploadFile> &files);
|
||||
|
||||
/**
|
||||
* @brief Create a custom HTTP request object. For using this template,
|
||||
* users must specialize the toRequest template.
|
||||
*/
|
||||
template <typename T>
|
||||
static HttpRequestPtr newCustomHttpRequest(T &&obj) {
|
||||
return toRequest(std::forward<T>(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<std::string, std::string> headers_;
|
||||
std::unordered_map<std::string, std::string> cookies_;
|
||||
mutable std::unordered_map<std::string, std::string> parameters_;
|
||||
mutable std::shared_ptr<Json::Value> jsonPtr_;
|
||||
SessionPtr sessionPtr_;
|
||||
mutable AttributesPtr attributesPtr_;
|
||||
trantor::InetAddress peer_;
|
||||
trantor::InetAddress local_;
|
||||
trantor::Date creationDate_;
|
||||
std::unique_ptr<CacheFile> cacheFilePtr_;
|
||||
mutable std::unique_ptr<std::string> jsonParsingErrorPtr_;
|
||||
std::unique_ptr<std::string> 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 &>(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<Json::Value> fromRequest(const HttpRequest &req) {
|
||||
return req.getJsonObject();
|
||||
}
|
||||
|
||||
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
|
||||
|
||||
inline void swap(HttpRequest &one, HttpRequest &two) noexcept {
|
||||
one.swap(two);
|
||||
}
|
||||
|
||||
} // namespace drogon
|
510
core/http_request_parser.cpp
Normal file
510
core/http_request_parser.cpp
Normal file
@ -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 <core/http_types.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
|
||||
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<HttpRequestParser> weakPtr = shared_from_this();
|
||||
return std::shared_ptr<HttpRequestImpl>(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<<std::string(buf->peek(),buf->readableBytes())<<std::endl;
|
||||
while (hasMore)
|
||||
{
|
||||
if (status_ == HttpRequestParseStatus::kExpectMethod)
|
||||
{
|
||||
auto *space =
|
||||
std::find(buf->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<HttpResponseImpl *>(resp.get())
|
||||
->renderToBuffer();
|
||||
reset();
|
||||
connPtr->send(std::move(*httpString));
|
||||
}
|
||||
else
|
||||
{
|
||||
resp->setStatusCode(k100Continue);
|
||||
auto httpString =
|
||||
static_cast<HttpResponseImpl *>(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="<<responsePtr_->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<HttpResponsePtr, bool> 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;
|
||||
}
|
||||
}
|
||||
}
|
156
core/http_request_parser.h
Normal file
156
core/http_request_parser.h
Normal file
@ -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 <core/http_types.h>
|
||||
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpRequestParser : public trantor::NonCopyable,
|
||||
public std::enable_shared_from_this<HttpRequestParser>
|
||||
{
|
||||
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<HttpResponsePtr, bool> 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<std::pair<HttpResponsePtr, bool>> &getResponseBuffer()
|
||||
{
|
||||
assert(loop_->isInLoopThread());
|
||||
if (!responseBuffer_)
|
||||
{
|
||||
responseBuffer_ =
|
||||
std::unique_ptr<std::vector<std::pair<HttpResponsePtr, bool>>>(
|
||||
new std::vector<std::pair<HttpResponsePtr, bool>>);
|
||||
}
|
||||
return *responseBuffer_;
|
||||
}
|
||||
std::vector<HttpRequestPtr> &getRequestBuffer()
|
||||
{
|
||||
assert(loop_->isInLoopThread());
|
||||
if (!requestBuffer_)
|
||||
{
|
||||
requestBuffer_ = std::unique_ptr<std::vector<HttpRequestPtr>>(
|
||||
new std::vector<HttpRequestPtr>);
|
||||
}
|
||||
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<std::pair<HttpRequestPtr, std::pair<HttpResponsePtr, bool>>>
|
||||
requestPipelining_;
|
||||
size_t requestsCounter_{0};
|
||||
std::weak_ptr<trantor::TcpConnection> conn_;
|
||||
bool stopWorking_{false};
|
||||
trantor::MsgBuffer sendBuffer_;
|
||||
std::unique_ptr<std::vector<std::pair<HttpResponsePtr, bool>>>
|
||||
responseBuffer_;
|
||||
std::unique_ptr<std::vector<HttpRequestPtr>> requestBuffer_;
|
||||
std::vector<HttpRequestPtr> requestsPool_;
|
||||
size_t currentChunkLength_;
|
||||
size_t currentContentLength_{0};
|
||||
};
|
||||
|
||||
} // namespace drogon
|
677
core/http_response.cpp
Normal file
677
core/http_response.cpp
Normal file
@ -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 <core/http_view_data.h>
|
||||
|
||||
#include <core/io_thread_storage.h>
|
||||
#include "core/application.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#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<HttpResponse>(k200OK, CT_TEXT_HTML);
|
||||
doResponseCreateAdvices(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newHttpJsonResponse(const Json::Value &data)
|
||||
{
|
||||
auto res = std::make_shared<HttpResponse>(k200OK, CT_APPLICATION_JSON);
|
||||
res->setJsonObject(data);
|
||||
doResponseCreateAdvices(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newHttpJsonResponse(Json::Value &&data)
|
||||
{
|
||||
auto res = std::make_shared<HttpResponse>(k200OK, CT_APPLICATION_JSON);
|
||||
res->setJsonObject(std::move(data));
|
||||
doResponseCreateAdvices(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
void HttpResponse::generateBodyFromJson() const
|
||||
{
|
||||
if (!jsonPtr_ || flagForSerializingJson_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
flagForSerializingJson_ = true;
|
||||
static std::once_flag once;
|
||||
static Json::StreamWriterBuilder builder;
|
||||
std::call_once(once, []() {
|
||||
builder["commentStyle"] = "None";
|
||||
builder["indentation"] = "";
|
||||
|
||||
/*
|
||||
if (!app().isUnicodeEscapingUsedInJson())
|
||||
{
|
||||
builder["emitUTF8"] = true;
|
||||
}*/
|
||||
|
||||
|
||||
});
|
||||
bodyPtr_ = std::make_shared<HttpMessageStringBody>(
|
||||
writeString(builder, *jsonPtr_));
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newNotFoundResponse()
|
||||
{
|
||||
auto loop = trantor::EventLoop::getEventLoopOfCurrentThread();
|
||||
auto &resp = HttpAppFrameworkImpl::instance().getCustom404Page();
|
||||
if (resp)
|
||||
{
|
||||
if (loop && loop->index() < Application::get_instance()->threadNum_)
|
||||
{
|
||||
return resp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return HttpResponsePtr{new HttpResponse(
|
||||
*static_cast<HttpResponse *>(resp.get()))};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loop && loop->index() < Application::get_instance()->threadNum_)
|
||||
{
|
||||
// If the current thread is an IO thread
|
||||
static std::once_flag threadOnce;
|
||||
static IOThreadStorage<HttpResponsePtr> thread404Pages;
|
||||
std::call_once(threadOnce, [] {
|
||||
thread404Pages.init(
|
||||
[](drogon::HttpResponsePtr &resp, size_t index) {
|
||||
if (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<HttpResponse>();
|
||||
res->setStatusCode(status);
|
||||
res->redirect(location);
|
||||
doResponseCreateAdvices(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newHttpViewResponse(const std::string &viewName,
|
||||
const HttpViewData &data)
|
||||
{
|
||||
return genHttpResponse(viewName, data);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpResponse::newFileResponse(
|
||||
const std::string &fullPath,
|
||||
const std::string &attachmentFileName,
|
||||
ContentType type)
|
||||
{
|
||||
std::ifstream infile(fullPath, std::ifstream::binary);
|
||||
LOG_TRACE << "send http file:" << fullPath;
|
||||
if (!infile)
|
||||
{
|
||||
auto resp = HttpResponse::newNotFoundResponse();
|
||||
return resp;
|
||||
}
|
||||
auto resp = std::make_shared<HttpResponse>();
|
||||
std::streambuf *pbuf = infile.rdbuf();
|
||||
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
|
||||
pbuf->pubseekoff(0, infile.beg); // rewind
|
||||
if (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<decltype(bodyLength)>(),
|
||||
bodyLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct stat filestat;
|
||||
if (stat(sendfileName_.data(), &filestat) < 0)
|
||||
{
|
||||
LOG_SYSERR << sendfileName_ << " stat error";
|
||||
return;
|
||||
}
|
||||
len = snprintf(buffer.beginWrite(),
|
||||
buffer.writableBytes(),
|
||||
contentLengthFormatString<decltype(filestat.st_size)>(),
|
||||
filestat.st_size);
|
||||
}
|
||||
buffer.hasWritten(len);
|
||||
if (headers_.find("Connection") == headers_.end())
|
||||
{
|
||||
if (closeConnection_)
|
||||
{
|
||||
buffer.append("Connection: close\r\n");
|
||||
}
|
||||
else if (version_ == Version::kHttp10)
|
||||
{
|
||||
buffer.append("Connection: Keep-Alive\r\n");
|
||||
}
|
||||
}
|
||||
buffer.append(contentTypeString_.data(), contentTypeString_.length());
|
||||
if (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<trantor::MsgBuffer> HttpResponse::renderToBuffer()
|
||||
{
|
||||
if (expriedTime_ >= 0)
|
||||
{
|
||||
if (!passThrough_ &&
|
||||
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
|
||||
{
|
||||
if (datePos_ != static_cast<size_t>(-1))
|
||||
{
|
||||
auto now = trantor::Date::now();
|
||||
bool isDateChanged =
|
||||
((now.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC) !=
|
||||
httpStringDate_);
|
||||
assert(httpString_);
|
||||
if (isDateChanged)
|
||||
{
|
||||
httpStringDate_ =
|
||||
now.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC;
|
||||
auto newDate = utils::getHttpFullDate(now);
|
||||
|
||||
httpString_ =
|
||||
std::make_shared<trantor::MsgBuffer>(*httpString_);
|
||||
memcpy((void *)&(*httpString_)[datePos_],
|
||||
newDate,
|
||||
httpFullDateStringLength);
|
||||
return httpString_;
|
||||
}
|
||||
|
||||
return httpString_;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (httpString_)
|
||||
return httpString_;
|
||||
}
|
||||
}
|
||||
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
|
||||
if (!fullHeaderString_)
|
||||
{
|
||||
makeHeaderString(*httpString);
|
||||
}
|
||||
else
|
||||
{
|
||||
httpString->append(*fullHeaderString_);
|
||||
}
|
||||
|
||||
// output cookies
|
||||
if (cookies_.size() > 0)
|
||||
{
|
||||
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
|
||||
{
|
||||
httpString->append(it->second.cookieString());
|
||||
}
|
||||
}
|
||||
|
||||
// output Date header
|
||||
if (!passThrough_ &&
|
||||
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<trantor::MsgBuffer> HttpResponse::
|
||||
renderHeaderForHeadMethod()
|
||||
{
|
||||
auto httpString = std::make_shared<trantor::MsgBuffer>(256);
|
||||
if (!fullHeaderString_)
|
||||
{
|
||||
makeHeaderString(*httpString);
|
||||
}
|
||||
else
|
||||
{
|
||||
httpString->append(*fullHeaderString_);
|
||||
}
|
||||
|
||||
// output cookies
|
||||
if (cookies_.size() > 0)
|
||||
{
|
||||
for (auto it = cookies_.begin(); it != cookies_.end(); ++it)
|
||||
{
|
||||
httpString->append(it->second.cookieString());
|
||||
}
|
||||
}
|
||||
|
||||
// output Date header
|
||||
if (!passThrough_ &&
|
||||
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!!!:"<<value;
|
||||
auto values = utils::splitString(value, ";");
|
||||
Cookie cookie;
|
||||
cookie.setHttpOnly(false);
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
{
|
||||
std::string &coo = values[i];
|
||||
std::string cookie_name;
|
||||
std::string cookie_value;
|
||||
auto epos = coo.find('=');
|
||||
if (epos != std::string::npos)
|
||||
{
|
||||
cookie_name = coo.substr(0, epos);
|
||||
std::string::size_type cpos = 0;
|
||||
while (cpos < cookie_name.length() &&
|
||||
isspace(cookie_name[cpos]))
|
||||
++cpos;
|
||||
cookie_name = cookie_name.substr(cpos);
|
||||
++epos;
|
||||
while (epos < coo.length() && isspace(coo[epos]))
|
||||
++epos;
|
||||
cookie_value = coo.substr(epos);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string::size_type cpos = 0;
|
||||
while (cpos < coo.length() && isspace(coo[cpos]))
|
||||
++cpos;
|
||||
cookie_name = coo.substr(cpos);
|
||||
}
|
||||
if (i == 0)
|
||||
{
|
||||
cookie.setKey(cookie_name);
|
||||
cookie.setValue(cookie_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::transform(cookie_name.begin(),
|
||||
cookie_name.end(),
|
||||
cookie_name.begin(),
|
||||
tolower);
|
||||
if (cookie_name == "path")
|
||||
{
|
||||
cookie.setPath(cookie_value);
|
||||
}
|
||||
else if (cookie_name == "domain")
|
||||
{
|
||||
cookie.setDomain(cookie_value);
|
||||
}
|
||||
else if (cookie_name == "expires")
|
||||
{
|
||||
cookie.setExpiresDate(utils::getHttpDate(cookie_value));
|
||||
}
|
||||
else if (cookie_name == "secure")
|
||||
{
|
||||
cookie.setSecure(true);
|
||||
}
|
||||
else if (cookie_name == "httponly")
|
||||
{
|
||||
cookie.setHttpOnly(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cookie.key().empty())
|
||||
{
|
||||
cookies_[cookie.key()] = cookie;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
headers_[std::move(field)] = std::move(value);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpResponse::swap(HttpResponse &that) noexcept
|
||||
{
|
||||
using std::swap;
|
||||
headers_.swap(that.headers_);
|
||||
cookies_.swap(that.cookies_);
|
||||
swap(statusCode_, that.statusCode_);
|
||||
swap(version_, that.version_);
|
||||
swap(statusMessage_, that.statusMessage_);
|
||||
swap(closeConnection_, that.closeConnection_);
|
||||
bodyPtr_.swap(that.bodyPtr_);
|
||||
swap(contentType_, that.contentType_);
|
||||
swap(flagForParsingContentType_, that.flagForParsingContentType_);
|
||||
swap(flagForParsingJson_, that.flagForParsingJson_);
|
||||
swap(sendfileName_, that.sendfileName_);
|
||||
jsonPtr_.swap(that.jsonPtr_);
|
||||
fullHeaderString_.swap(that.fullHeaderString_);
|
||||
httpString_.swap(that.httpString_);
|
||||
swap(datePos_, that.datePos_);
|
||||
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
|
||||
}
|
||||
|
||||
void HttpResponse::clear()
|
||||
{
|
||||
statusCode_ = kUnknown;
|
||||
version_ = Version::kHttp11;
|
||||
statusMessage_ = string_view{};
|
||||
fullHeaderString_.reset();
|
||||
jsonParsingErrorPtr_.reset();
|
||||
sendfileName_.clear();
|
||||
headers_.clear();
|
||||
cookies_.clear();
|
||||
bodyPtr_.reset();
|
||||
jsonPtr_.reset();
|
||||
expriedTime_ = -1;
|
||||
datePos_ = std::string::npos;
|
||||
flagForParsingContentType_ = false;
|
||||
flagForParsingJson_ = false;
|
||||
}
|
||||
|
||||
void HttpResponse::parseJson() const
|
||||
{
|
||||
static std::once_flag once;
|
||||
static Json::CharReaderBuilder builder;
|
||||
std::call_once(once, []() { builder["collectComments"] = false; });
|
||||
JSONCPP_STRING errs;
|
||||
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
|
||||
if (bodyPtr_)
|
||||
{
|
||||
jsonPtr_ = std::make_shared<Json::Value>();
|
||||
if (!reader->parse(bodyPtr_->data(),
|
||||
bodyPtr_->data() + bodyPtr_->length(),
|
||||
jsonPtr_.get(),
|
||||
&errs))
|
||||
{
|
||||
LOG_ERROR << errs;
|
||||
LOG_ERROR << "body: " << bodyPtr_->getString();
|
||||
jsonPtr_.reset();
|
||||
jsonParsingErrorPtr_ =
|
||||
std::make_shared<std::string>(std::move(errs));
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonParsingErrorPtr_.reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonPtr_.reset();
|
||||
jsonParsingErrorPtr_ =
|
||||
std::make_shared<std::string>("empty response body");
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::~HttpResponse()
|
||||
{
|
||||
}
|
||||
|
||||
bool HttpResponse::shouldBeCompressed() const
|
||||
{
|
||||
if (!sendfileName_.empty() ||
|
||||
contentType() >= CT_APPLICATION_OCTET_STREAM ||
|
||||
getBody().length() < 1024 || !(getHeaderBy("content-encoding").empty()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
670
core/http_response.h
Normal file
670
core/http_response.h
Normal file
@ -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 <core/cookie.h>
|
||||
#include <core/http_types.h>
|
||||
#include <core/string_view.h>
|
||||
|
||||
#include <core/http_view_data.h>
|
||||
#include <json/json.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
|
||||
#include "core/http_utils.h"
|
||||
|
||||
#include "http_message_body.h"
|
||||
|
||||
#include <core/http_response.h>
|
||||
#include <core/utilities.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/utils/Date.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace drogon {
|
||||
/// Abstract class for webapp developer to get or set the Http response;
|
||||
class HttpResponse;
|
||||
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
||||
|
||||
/**
|
||||
* @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 <typename T>
|
||||
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 <typename T>
|
||||
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 &>(const Json::Value &pJson);
|
||||
template <>
|
||||
HttpResponsePtr toResponse(Json::Value &&pJson);
|
||||
template <>
|
||||
inline HttpResponsePtr toResponse<Json::Value &>(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<Json::Value> specialization version is available above, so
|
||||
* we can use the following code to get a json object:
|
||||
* @code
|
||||
* std::shared_ptr<Json::Value> 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 <typename T>
|
||||
operator T() const {
|
||||
return fromResponse<T>(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This template enables explicit type conversion, see the above
|
||||
* template.
|
||||
*/
|
||||
template <typename T>
|
||||
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 <int N>
|
||||
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<std::string, std::string> &headers()
|
||||
const {
|
||||
return headers_;
|
||||
}
|
||||
|
||||
/// Get all headers of the response
|
||||
const std::unordered_map<std::string, std::string> &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<std::string, Cookie> &cookies()
|
||||
const {
|
||||
return cookies_;
|
||||
}
|
||||
|
||||
/// Get all cookies.
|
||||
const std::unordered_map<std::string, Cookie> &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<HttpMessageStringBody>(body);
|
||||
}
|
||||
/// Set the response body(content).
|
||||
virtual void setBody(std::string &&body) {
|
||||
bodyPtr_ = std::make_shared<HttpMessageStringBody>(std::move(body));
|
||||
}
|
||||
|
||||
/// Set the response body(content).
|
||||
template <int N>
|
||||
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<Json::Value> &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<Json::Value> &getJsonObject() const {
|
||||
return jsonObject();
|
||||
}
|
||||
|
||||
|
||||
void setJsonObject(const Json::Value &pJson) {
|
||||
flagForParsingJson_ = true;
|
||||
flagForSerializingJson_ = false;
|
||||
jsonPtr_ = std::make_shared<Json::Value>(pJson);
|
||||
}
|
||||
void setJsonObject(Json::Value &&pJson) {
|
||||
flagForParsingJson_ = true;
|
||||
flagForSerializingJson_ = false;
|
||||
jsonPtr_ = std::make_shared<Json::Value>(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<trantor::MsgBuffer>(128);
|
||||
makeHeaderString(*fullHeaderString_);
|
||||
}
|
||||
|
||||
void gunzip() {
|
||||
if (bodyPtr_) {
|
||||
auto gunzipBody =
|
||||
utils::gzipDecompress(bodyPtr_->data(), bodyPtr_->length());
|
||||
removeHeader("content-encoding");
|
||||
bodyPtr_ =
|
||||
std::make_shared<HttpMessageStringBody>(move(gunzipBody));
|
||||
}
|
||||
}
|
||||
#ifdef USE_BROTLI
|
||||
void brDecompress() {
|
||||
if (bodyPtr_) {
|
||||
auto gunzipBody =
|
||||
utils::brotliDecompress(bodyPtr_->data(), bodyPtr_->length());
|
||||
removeHeader("content-encoding");
|
||||
bodyPtr_ =
|
||||
std::make_shared<HttpMessageStringBody>(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<trantor::MsgBuffer> renderToBuffer();
|
||||
void renderToBuffer(trantor::MsgBuffer &buffer);
|
||||
std::shared_ptr<trantor::MsgBuffer> 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 <typename T>
|
||||
static HttpResponsePtr newCustomHttpResponse(T &&obj) {
|
||||
return toResponse(std::forward<T>(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<HttpMessageStringViewBody>(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<std::string, std::string> headers_;
|
||||
std::unordered_map<std::string, Cookie> cookies_;
|
||||
|
||||
HttpStatusCode statusCode_{ kUnknown };
|
||||
string_view statusMessage_;
|
||||
|
||||
trantor::Date creationDate_;
|
||||
Version version_{ Version::kHttp11 };
|
||||
bool closeConnection_{ false };
|
||||
mutable std::shared_ptr<HttpMessageBody> bodyPtr_;
|
||||
ssize_t expriedTime_{ -1 };
|
||||
std::string sendfileName_;
|
||||
mutable std::shared_ptr<Json::Value> jsonPtr_;
|
||||
|
||||
std::shared_ptr<trantor::MsgBuffer> fullHeaderString_;
|
||||
mutable std::shared_ptr<trantor::MsgBuffer> httpString_;
|
||||
mutable size_t datePos_{ static_cast<size_t>(-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<std::string> 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 &>(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<Json::Value> fromResponse(const HttpResponse &resp) {
|
||||
return resp.getJsonObject();
|
||||
}
|
||||
|
||||
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
||||
|
||||
inline void swap(HttpResponse &one, HttpResponse &two) noexcept {
|
||||
one.swap(two);
|
||||
}
|
||||
|
||||
} // namespace drogon
|
@ -1,19 +1,25 @@
|
||||
#include "http_server.h"
|
||||
|
||||
#include "request.h"
|
||||
#include <functional>
|
||||
|
||||
#include "application.h"
|
||||
#include "request.h"
|
||||
|
||||
#include <trantor/utils/Logger.h>
|
||||
|
||||
#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<http_method>(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<SharedLibManager>(
|
||||
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<std::function<HttpResponsePtr(const HttpRequestPtr &)>>
|
||||
&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() {
|
||||
|
@ -12,6 +12,13 @@
|
||||
#include <brynet/net/wrapper/HttpServiceBuilder.hpp>
|
||||
#include <brynet/net/wrapper/ServiceBuilder.hpp>
|
||||
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/net/callbacks.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
|
||||
#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<TcpService> 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<trantor::EventLoopThreadPool> &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<trantor::EventLoop *> 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<std::function<HttpResponsePtr(const HttpRequestPtr &)> > &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<HttpRequestPtr> &, const std::shared_ptr<drogon::HttpRequestParser> &);
|
||||
void sendResponse(const trantor::TcpConnectionPtr &, const HttpResponsePtr &, bool isHeadMethod);
|
||||
void sendResponses(const trantor::TcpConnectionPtr &conn, const std::vector<std::pair<HttpResponsePtr, bool> > &responses, trantor::MsgBuffer &buffer);
|
||||
|
||||
trantor::TcpServer server_;
|
||||
HttpAsyncCallback httpAsyncCallback_;
|
||||
WebSocketNewAsyncCallback newWebsocketCallback_;
|
||||
trantor::ConnectionCallback connectionCallback_;
|
||||
const std::vector<std::function<HttpResponsePtr(const HttpRequestPtr &)> > &syncAdvices_;
|
||||
};
|
||||
|
||||
#endif
|
19
core/http_server_callbacks.h
Normal file
19
core/http_server_callbacks.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef HTTP_SERVER_CALLBACKS_H
|
||||
#define HTTP_SERVER_CALLBACKS_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class HttpRequest;
|
||||
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
|
||||
class HttpResponse;
|
||||
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
||||
class WebSocketConnectionImpl;
|
||||
using WebSocketConnectionPtr = std::shared_ptr<WebSocketConnectionImpl>;
|
||||
|
||||
using HttpAsyncCallback = std::function<void(const HttpRequestPtr &,std::function<void(const HttpResponsePtr &)> &&)>;
|
||||
using WebSocketNewAsyncCallback = std::function<void(const HttpRequestPtr &, std::function<void(const HttpResponsePtr &)> &&,
|
||||
const WebSocketConnectionPtr &)>;
|
||||
|
||||
#endif
|
144
core/http_types.h
Normal file
144
core/http_types.h
Normal file
@ -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 <atomic>
|
||||
#include <thread>
|
||||
|
||||
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
|
580
core/http_utils.cpp
Normal file
580
core/http_utils.cpp
Normal file
@ -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 <trantor/utils/Logger.h>
|
||||
#include <unordered_map>
|
||||
|
||||
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<string_view, ContentType> 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
|
58
core/http_utils.h
Normal file
58
core/http_utils.h
Normal file
@ -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 <core/string_view.h>
|
||||
#include <core/http_types.h>
|
||||
#include <string>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
|
||||
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 <typename T>
|
||||
inline constexpr const char *contentLengthFormatString()
|
||||
{
|
||||
return "Content-Length: %d\r\n";
|
||||
}
|
||||
template <>
|
||||
inline constexpr const char *contentLengthFormatString<unsigned int>()
|
||||
{
|
||||
return "Content-Length: %u\r\n";
|
||||
}
|
||||
template <>
|
||||
inline constexpr const char *contentLengthFormatString<long>()
|
||||
{
|
||||
return "Content-Length: %ld\r\n";
|
||||
}
|
||||
template <>
|
||||
inline constexpr const char *contentLengthFormatString<unsigned long>()
|
||||
{
|
||||
return "Content-Length: %lu\r\n";
|
||||
}
|
||||
template <>
|
||||
inline constexpr const char *contentLengthFormatString<long long>()
|
||||
{
|
||||
return "Content-Length: %lld\r\n";
|
||||
}
|
||||
template <>
|
||||
inline constexpr const char *contentLengthFormatString<unsigned long long>()
|
||||
{
|
||||
return "Content-Length: %llu\r\n";
|
||||
}
|
||||
} // namespace drogon
|
191
core/http_view_data.h
Normal file
191
core/http_view_data.h
Normal file
@ -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 <core/string_view.h>
|
||||
#include <core/any.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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 <typename T>
|
||||
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<T>(&(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 <typename T>
|
||||
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<std::string, any>;
|
||||
mutable ViewDataMap viewData_;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
162
core/io_thread_storage.h
Normal file
162
core/io_thread_storage.h
Normal file
@ -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 <drogon/HttpAppFramework.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
#include <functional>
|
||||
|
||||
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<MyController> {
|
||||
* public:
|
||||
* METHOD_LIST_BEGIN
|
||||
* ADD_METHOD_TO(MyController::endpoint, "/some/path", Get);
|
||||
* METHOD_LIST_END
|
||||
*
|
||||
* void login(const HttpRequestPtr &req,
|
||||
* std::function<void (const HttpResponsePtr &)> &&callback) {
|
||||
* assert(storage_->threadLocal == 42);
|
||||
*
|
||||
* // handle the request
|
||||
* }
|
||||
*
|
||||
* private:
|
||||
* IOThreadStorage<MyThreadData> storage_;
|
||||
* };
|
||||
* @endcode
|
||||
*/
|
||||
template <typename C>
|
||||
class IOThreadStorage : public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
using ValueType = C;
|
||||
using InitCallback = std::function<void(ValueType &, size_t)>;
|
||||
|
||||
template <typename... Args>
|
||||
IOThreadStorage(Args &&... args)
|
||||
{
|
||||
static_assert(std::is_constructible<C, Args &&...>::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<size_t>::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>(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<ValueType> storage_;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
241
core/listener_manager.cpp
Normal file
241
core/listener_manager.cpp
Normal file
@ -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 <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/file.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#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<trantor::EventLoop *> 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<std::function<HttpResponsePtr(const HttpRequestPtr &)> > &syncAdvices) {
|
||||
|
||||
#ifdef __linux__
|
||||
for (size_t i = 0; i < threadNum; ++i) {
|
||||
LOG_TRACE << "thread num=" << threadNum;
|
||||
|
||||
auto loopThreadPtr = std::make_shared<EventLoopThread>("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<HttpServer> 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<HttpServer>(
|
||||
loopThreadPtr->getLoop(),
|
||||
InetAddress(ip, listener.port_, isIpv6),
|
||||
"drogon",
|
||||
syncAdvices);
|
||||
} else {
|
||||
serverPtr = std::make_shared<HttpServer>(
|
||||
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<EventLoopThread>("DrogonListeningLoop");
|
||||
listeningloopThreads_.push_back(loopThreadPtr);
|
||||
ioLoopThreadPoolPtr_ = std::make_shared<EventLoopThreadPool>(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<HttpServer>(
|
||||
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<int> 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<int> pro;
|
||||
auto f = pro.get_future();
|
||||
loop->queueInLoop([loop, &pro]() {
|
||||
loop->quit();
|
||||
pro.set_value(1);
|
||||
});
|
||||
(void)f.get();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
82
core/listener_manager.h
Normal file
82
core/listener_manager.h
Normal file
@ -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 <trantor/net/EventLoopThreadPool.h>
|
||||
#include <trantor/net/callbacks.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<trantor::EventLoop *> 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<HttpResponsePtr(const HttpRequestPtr &)> >
|
||||
&syncAdvices);
|
||||
|
||||
void startListening();
|
||||
|
||||
~ListenerManager();
|
||||
|
||||
trantor::EventLoop *getIOLoop(size_t id) const;
|
||||
|
||||
void stopListening();
|
||||
|
||||
std::vector<trantor::EventLoop *> 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<ListenerInfo> listeners_;
|
||||
std::vector<std::shared_ptr<HttpServer> > servers_;
|
||||
std::vector<std::shared_ptr<trantor::EventLoopThread> > listeningloopThreads_;
|
||||
std::shared_ptr<trantor::EventLoopThreadPool> ioLoopThreadPoolPtr_;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
@ -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;
|
||||
|
197
core/session.h
Normal file
197
core/session.h
Normal file
@ -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 <core/any.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
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<std::string>("user name");
|
||||
@endcode
|
||||
*/
|
||||
template <typename T>
|
||||
const T &get(const std::string &key) const
|
||||
{
|
||||
const static T nullVal = T();
|
||||
std::lock_guard<std::mutex> lck(mutex_);
|
||||
auto it = sessionMap_.find(key);
|
||||
if (it != sessionMap_.end())
|
||||
{
|
||||
if (typeid(T) == it->second.type())
|
||||
{
|
||||
return *(any_cast<T>(&(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lck(mutex_);
|
||||
if (sessionMap_.find(key) == sessionMap_.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear all data in the session.
|
||||
*/
|
||||
void clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mutex_);
|
||||
sessionMap_.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the session ID of the current session.
|
||||
*/
|
||||
std::string sessionId() const
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::string, any>;
|
||||
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<std::mutex> lck(mutex_);
|
||||
sessionId_ = id;
|
||||
needToChange_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
using SessionPtr = std::shared_ptr<Session>;
|
||||
|
||||
} // namespace drogon
|
351
core/ssl_funcs/Md5.cc
Normal file
351
core/ssl_funcs/Md5.cc
Normal file
@ -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 <math.h>
|
||||
#include <string.h>
|
||||
|
||||
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<unsigned char *>(*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<uint32_t *>(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<uint32_t *>(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<uint32_t *>(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<uint32_t *>(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;
|
||||
}
|
81
core/ssl_funcs/Md5.h
Normal file
81
core/ssl_funcs/Md5.h
Normal file
@ -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 <string>
|
||||
#include <cstdint>
|
||||
|
||||
#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;
|
||||
};
|
159
core/ssl_funcs/Sha1.cc
Normal file
159
core/ssl_funcs/Sha1.cc
Normal file
@ -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 <string.h>
|
||||
|
||||
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;
|
||||
}
|
23
core/ssl_funcs/Sha1.h
Normal file
23
core/ssl_funcs/Sha1.h
Normal file
@ -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 <iostream>
|
||||
|
||||
#define SHA_DIGEST_LENGTH 20
|
||||
|
||||
unsigned char *SHA1(const unsigned char *dataIn,
|
||||
size_t dataLen,
|
||||
unsigned char *dataOut);
|
92
core/string_view.h
Normal file
92
core/string_view.h
Normal file
@ -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 <string_view>
|
||||
#else
|
||||
#include <boost/utility/string_view.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#endif
|
||||
|
||||
#include <trantor/utils/LogStream.h>
|
||||
|
||||
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<drogon::string_view>
|
||||
{
|
||||
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
|
75
core/upload_file.h
Normal file
75
core/upload_file.h
Normal file
@ -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 <string>
|
||||
|
||||
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
|
1162
core/utilities.cpp
Normal file
1162
core/utilities.cpp
Normal file
File diff suppressed because it is too large
Load Diff
153
core/utilities.h
Normal file
153
core/utilities.h
Normal file
@ -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 <trantor/utils/Date.h>
|
||||
#include <trantor/utils/Funcs.h>
|
||||
|
||||
#include <core/string_view.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <limits>
|
||||
#ifdef _WIN32
|
||||
#include <time.h>
|
||||
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<char> 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<std::string> splitString(const std::string &str,
|
||||
const std::string &separator,
|
||||
bool acceptEmptyString = false)
|
||||
{
|
||||
return trantor::splitString(str, separator, acceptEmptyString);
|
||||
}
|
||||
|
||||
std::set<std::string> 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<char> 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<int64_t>::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
|
Loading…
Reference in New Issue
Block a user