mirror of
https://github.com/drogonframework/drogon.git
synced 2025-09-22 00:00:33 -04:00
Add location configuration for static resources (#331)
This commit is contained in:
parent
62fc82cba1
commit
fb2343ac74
@ -95,6 +95,25 @@
|
||||
"cur",
|
||||
"xml"
|
||||
],
|
||||
//locations: An array of locations of static files for GET requests.
|
||||
"locations": [{
|
||||
//uri_prefix: The URI prefix of the location prefixed with "/", the default value is "" that disables the location.
|
||||
//"uri_prefix": "/.well-known/acme-challenge/",
|
||||
//default_content_type: The default content type of the static files without
|
||||
//an extension. empty string by default.
|
||||
"default_content_type": "text/plain",
|
||||
//alias: The location in file system, if it is prefixed with "/", it
|
||||
//presents an absolute path, otherwise it presents a relative path to
|
||||
//the document_root path.
|
||||
//The default value is "" which means use the document root path as the location base path.
|
||||
"alias": "",
|
||||
//is_case_sensitive: indicates whether the URI prefix is case sensitive.
|
||||
"is_case_sensitive": false,
|
||||
//allow_all: true by default. If it is set to false, only static files with a valid extension can be accessed.
|
||||
"allow_all": true,
|
||||
//is_recursive: true by default. If it is set to false, files in sub directories can't be accessed.
|
||||
"is_recursive": true
|
||||
}],
|
||||
//max_connections: maximum connections number,100000 by default
|
||||
"max_connections": 100000,
|
||||
//max_connections_per_ip: maximum connections number per clinet,0 by default which means no limit
|
||||
@ -126,7 +145,7 @@
|
||||
"run_as_daemon": false,
|
||||
//relaunch_on_error: False by default, if true, the program will be restart by the parent after exiting;
|
||||
"relaunch_on_error": false,
|
||||
//use_sendfile: True by default, if ture, the program
|
||||
//use_sendfile: True by default, if true, the program
|
||||
//uses sendfile() system-call to send static files to clients;
|
||||
"use_sendfile": true,
|
||||
//use_gzip: True by default, use gzip to compress the response body's content;
|
||||
|
@ -95,6 +95,25 @@
|
||||
"cur",
|
||||
"xml"
|
||||
],
|
||||
//locations: An array of locations of static files for GET requests.
|
||||
"locations": [{
|
||||
//uri_prefix: The URI prefix of the location prefixed with "/", the default value is "" that disables the location.
|
||||
//"uri_prefix": "/.well-known/acme-challenge/",
|
||||
//default_content_type: The default content type of the static files without
|
||||
//an extension. empty string by default.
|
||||
"default_content_type": "text/plain",
|
||||
//alias: The location in file system, if it is prefixed with "/", it
|
||||
//presents an absolute path, otherwise it presents a relative path to
|
||||
//the document_root path.
|
||||
//The default value is "" which means use the document root path as the location base path.
|
||||
"alias": "",
|
||||
//is_case_sensitive: indicates whether the URI prefix is case sensitive.
|
||||
"is_case_sensitive": false,
|
||||
//allow_all: true by default. If it is set to false, only static files with a valid extension can be accessed.
|
||||
"allow_all": true,
|
||||
//is_recursive: true by default. If it is set to false, files in sub directories can't be accessed.
|
||||
"is_recursive": true
|
||||
}],
|
||||
//max_connections: maximum connections number,100000 by default
|
||||
"max_connections": 100000,
|
||||
//max_connections_per_ip: maximum connections number per clinet,0 by default which means no limit
|
||||
@ -126,7 +145,7 @@
|
||||
"run_as_daemon": false,
|
||||
//relaunch_on_error: False by default, if true, the program will be restart by the parent after exiting;
|
||||
"relaunch_on_error": false,
|
||||
//use_sendfile: True by default, if ture, the program
|
||||
//use_sendfile: True by default, if true, the program
|
||||
//uses sendfile() system-call to send static files to clients;
|
||||
"use_sendfile": true,
|
||||
//use_gzip: True by default, use gzip to compress the response body's content;
|
||||
|
@ -89,7 +89,7 @@
|
||||
"run_as_daemon": false,
|
||||
//relaunch_on_error: False by default, if true, the program will be restart by the parent after exiting;
|
||||
"relaunch_on_error": false,
|
||||
//use_sendfile: True by default, if ture, the program
|
||||
//use_sendfile: True by default, if true, the program
|
||||
//uses sendfile() system-call to send static files to clients;
|
||||
"use_sendfile": true,
|
||||
//use_gzip: True by default, use gzip to compress the response body's content;
|
||||
|
@ -648,6 +648,28 @@ class HttpAppFramework : public trantor::NonCopyable
|
||||
virtual HttpAppFramework &setStaticFileHeaders(
|
||||
const std::vector<std::pair<std::string, std::string>> &headers) = 0;
|
||||
|
||||
/**
|
||||
* @brief Add a location of static files for GET requests.
|
||||
*
|
||||
* @param uriPrefix The URI prefix of the location prefixed with "/"
|
||||
* @param defaultContentType The default content type of the static files
|
||||
* without an extension.
|
||||
* @param alias The location in file system, if it is prefixed with "/", it
|
||||
* presents an absolute path, otherwise it presents a relative path to the
|
||||
* document_root path.
|
||||
* @param isCaseSensitive
|
||||
* @param allowAll If it is set to false, only static files with a valid extension can be accessed.
|
||||
* @param isRecursive If it is set to false, files in sub directories can't be accessed.
|
||||
* @return HttpAppFramework&
|
||||
*/
|
||||
virtual HttpAppFramework &addALocation(
|
||||
const std::string &uriPrefix,
|
||||
const std::string &defaultContentType = "",
|
||||
const std::string &alias = "",
|
||||
bool isCaseSensitive = false,
|
||||
bool allowAll = true,
|
||||
bool isRecursive = true) = 0;
|
||||
|
||||
/// Set the path to store uploaded files.
|
||||
/**
|
||||
* @param uploadPath is the dictionary where the uploaded files are
|
||||
|
@ -96,7 +96,8 @@ enum ContentType
|
||||
CT_IMAGE_XICON,
|
||||
CT_IMAGE_ICNS,
|
||||
CT_IMAGE_BMP,
|
||||
CT_MULTIPART_FORM_DATA
|
||||
CT_MULTIPART_FORM_DATA,
|
||||
CT_CUSTOM
|
||||
};
|
||||
|
||||
enum HttpMethod
|
||||
|
@ -263,6 +263,35 @@ static void loadApp(const Json::Value &app)
|
||||
}
|
||||
drogon::app().setFileTypes(types);
|
||||
}
|
||||
// locations
|
||||
if (app.isMember("locations"))
|
||||
{
|
||||
auto &locations = app["locations"];
|
||||
if (!locations.isArray())
|
||||
{
|
||||
std::cerr << "The locations option must be an array\n";
|
||||
exit(1);
|
||||
}
|
||||
for (auto &location : locations)
|
||||
{
|
||||
auto uri = location.get("uri_prefix", "").asString();
|
||||
if (uri.empty())
|
||||
continue;
|
||||
auto defaultContentType =
|
||||
location.get("default_content_type", "").asString();
|
||||
auto alias = location.get("alias", "").asString();
|
||||
auto isCaseSensitive =
|
||||
location.get("is_case_sensitive", false).asBool();
|
||||
auto allAll = location.get("allow_all", true).asBool();
|
||||
auto isRecursive = location.get("is_recursive", true).asBool();
|
||||
drogon::app().addALocation(uri,
|
||||
defaultContentType,
|
||||
alias,
|
||||
isCaseSensitive,
|
||||
allAll,
|
||||
isRecursive);
|
||||
}
|
||||
}
|
||||
// max connections
|
||||
auto maxConns = app.get("max_connections", 0).asUInt64();
|
||||
if (maxConns > 0)
|
||||
|
@ -62,7 +62,7 @@ using namespace drogon;
|
||||
using namespace std::placeholders;
|
||||
|
||||
HttpAppFrameworkImpl::HttpAppFrameworkImpl()
|
||||
: staticFileRouterPtr_(new StaticFileRouter(staticFileHeaders_)),
|
||||
: staticFileRouterPtr_(new StaticFileRouter{}),
|
||||
httpCtrlsRouterPtr_(new HttpControllersRouter(*staticFileRouterPtr_,
|
||||
postRoutingAdvices_,
|
||||
postRoutingObservers_,
|
||||
@ -829,4 +829,28 @@ const HttpResponsePtr &HttpAppFrameworkImpl::getCustom404Page()
|
||||
{
|
||||
return custom404_;
|
||||
}
|
||||
}
|
||||
|
||||
HttpAppFramework &HttpAppFrameworkImpl::setStaticFileHeaders(
|
||||
const std::vector<std::pair<std::string, std::string>> &headers)
|
||||
{
|
||||
staticFileRouterPtr_->setStaticFileHeaders(headers);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &HttpAppFrameworkImpl::addALocation(
|
||||
const std::string &uriPrefix,
|
||||
const std::string &defaultContentType,
|
||||
const std::string &alias,
|
||||
bool isCaseSensitive,
|
||||
bool allowAll,
|
||||
bool isRecursive)
|
||||
{
|
||||
staticFileRouterPtr_->addALocation(uriPrefix,
|
||||
defaultContentType,
|
||||
alias,
|
||||
isCaseSensitive,
|
||||
allowAll,
|
||||
isRecursive);
|
||||
return *this;
|
||||
}
|
@ -189,11 +189,16 @@ class HttpAppFrameworkImpl : public HttpAppFramework
|
||||
|
||||
virtual HttpAppFramework &setStaticFileHeaders(
|
||||
const std::vector<std::pair<std::string, std::string>> &headers)
|
||||
override
|
||||
{
|
||||
staticFileHeaders_ = headers;
|
||||
return *this;
|
||||
}
|
||||
override;
|
||||
|
||||
virtual HttpAppFramework &addALocation(
|
||||
const std::string &uriPrefix,
|
||||
const std::string &defaultContentType = "",
|
||||
const std::string &alias = "",
|
||||
bool isCaseSensitive = false,
|
||||
bool allowAll = true,
|
||||
bool isRecursive = true) override;
|
||||
|
||||
virtual const std::string &getUploadPath() const override
|
||||
{
|
||||
return uploadPath_;
|
||||
@ -453,7 +458,6 @@ class HttpAppFrameworkImpl : public HttpAppFramework
|
||||
const std::unique_ptr<orm::DbClientManager> dbClientManagerPtr_;
|
||||
|
||||
std::string rootPath_{"./"};
|
||||
std::vector<std::pair<std::string, std::string>> staticFileHeaders_;
|
||||
std::string uploadPath_;
|
||||
std::atomic_bool running_{false};
|
||||
|
||||
|
@ -16,10 +16,9 @@
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpResponseImpl.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <algorithm>
|
||||
#include <fcntl.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
@ -41,6 +40,12 @@ void StaticFileRouter::init(const std::vector<trantor::EventLoop *> &ioloops)
|
||||
staticFilesCache_ = decltype(staticFilesCache_)(
|
||||
new IOThreadStorage<
|
||||
std::unordered_map<std::string, HttpResponsePtr>>{});
|
||||
ioLocationsPtr_ =
|
||||
decltype(ioLocationsPtr_)(new IOThreadStorage<std::vector<Location>>);
|
||||
for (auto *loop : ioloops)
|
||||
{
|
||||
loop->queueInLoop([this] { **ioLocationsPtr_ = locations_; });
|
||||
}
|
||||
}
|
||||
|
||||
void StaticFileRouter::route(
|
||||
@ -48,146 +53,112 @@ void StaticFileRouter::route(
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
const std::string &path = req->path();
|
||||
auto pos = path.rfind('.');
|
||||
if (pos != std::string::npos)
|
||||
if (path.find("/../") != std::string::npos)
|
||||
{
|
||||
std::string filetype = path.substr(pos + 1);
|
||||
transform(filetype.begin(), filetype.end(), filetype.begin(), tolower);
|
||||
if (fileTypeSet_.find(filetype) != fileTypeSet_.end())
|
||||
// Downloading files from the parent folder is forbidden.
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(k403Forbidden);
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
auto lPath = path;
|
||||
std::transform(lPath.begin(), lPath.end(), lPath.begin(), tolower);
|
||||
|
||||
for (auto &location : **ioLocationsPtr_)
|
||||
{
|
||||
auto &URI = location.uriPrefix_;
|
||||
auto &defaultContentType = location.defaultContentType_;
|
||||
if (location.realLocation_.empty())
|
||||
{
|
||||
// LOG_INFO << "file query!" << path;
|
||||
std::string filePath =
|
||||
HttpAppFrameworkImpl::instance().getDocumentRoot() + path;
|
||||
if (filePath.find("/../") != std::string::npos)
|
||||
if (!location.alias_.empty())
|
||||
{
|
||||
if (location.alias_[0] == '/')
|
||||
{
|
||||
location.realLocation_ = location.alias_;
|
||||
}
|
||||
else
|
||||
{
|
||||
location.realLocation_ =
|
||||
HttpAppFrameworkImpl::instance().getDocumentRoot() +
|
||||
location.alias_;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
location.realLocation_ =
|
||||
HttpAppFrameworkImpl::instance().getDocumentRoot() +
|
||||
location.uriPrefix_;
|
||||
}
|
||||
if (location.realLocation_[location.realLocation_.length() - 1] !=
|
||||
'/')
|
||||
{
|
||||
location.realLocation_.append(1, '/');
|
||||
}
|
||||
if (!location.isCaseSensitive_)
|
||||
{
|
||||
std::transform(URI.begin(), URI.end(), URI.begin(), tolower);
|
||||
}
|
||||
}
|
||||
auto &tmpPath = location.isCaseSensitive_ ? path : lPath;
|
||||
if (tmpPath.length() >= URI.length() &&
|
||||
std::equal(tmpPath.begin(),
|
||||
tmpPath.begin() + URI.length(),
|
||||
URI.begin()))
|
||||
{
|
||||
string_view restOfThePath{path.data() + URI.length(),
|
||||
path.length() - URI.length()};
|
||||
auto pos = restOfThePath.find('/');
|
||||
if (pos != 0 && pos != string_view::npos && !location.isRecursive_)
|
||||
{
|
||||
// Downloading files from the parent folder is forbidden.
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(k403Forbidden);
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
// find cached response
|
||||
HttpResponsePtr cachedResp;
|
||||
auto &cacheMap = staticFilesCache_->getThreadData();
|
||||
auto iter = cacheMap.find(filePath);
|
||||
if (iter != cacheMap.end())
|
||||
if (!location.allowAll_)
|
||||
{
|
||||
cachedResp = iter->second;
|
||||
}
|
||||
|
||||
// check last modified time,rfc2616-14.25
|
||||
// If-Modified-Since: Mon, 15 Oct 2018 06:26:33 GMT
|
||||
|
||||
std::string timeStr;
|
||||
if (enableLastModify_)
|
||||
{
|
||||
if (cachedResp)
|
||||
pos = restOfThePath.rfind('.');
|
||||
if (pos == string_view::npos)
|
||||
{
|
||||
if (static_cast<HttpResponseImpl *>(cachedResp.get())
|
||||
->getHeaderBy("last-modified") ==
|
||||
req->getHeaderBy("if-modified-since"))
|
||||
{
|
||||
std::shared_ptr<HttpResponseImpl> resp =
|
||||
std::make_shared<HttpResponseImpl>();
|
||||
resp->setStatusCode(k304NotModified);
|
||||
HttpAppFrameworkImpl::instance().callCallback(req,
|
||||
resp,
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(k403Forbidden);
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
else
|
||||
std::string extension{restOfThePath.data() + pos + 1,
|
||||
restOfThePath.length() - pos - 1};
|
||||
std::transform(extension.begin(),
|
||||
extension.end(),
|
||||
extension.begin(),
|
||||
tolower);
|
||||
if (fileTypeSet_.find(extension) == fileTypeSet_.end())
|
||||
{
|
||||
struct stat fileStat;
|
||||
LOG_TRACE << "enabled LastModify";
|
||||
if (stat(filePath.c_str(), &fileStat) >= 0)
|
||||
{
|
||||
LOG_TRACE << "last modify time:" << fileStat.st_mtime;
|
||||
struct tm tm1;
|
||||
gmtime_r(&fileStat.st_mtime, &tm1);
|
||||
timeStr.resize(64);
|
||||
auto len = strftime((char *)timeStr.data(),
|
||||
timeStr.size(),
|
||||
"%a, %d %b %Y %T GMT",
|
||||
&tm1);
|
||||
timeStr.resize(len);
|
||||
const std::string &modiStr =
|
||||
req->getHeaderBy("if-modified-since");
|
||||
if (modiStr == timeStr && !modiStr.empty())
|
||||
{
|
||||
LOG_TRACE << "not Modified!";
|
||||
std::shared_ptr<HttpResponseImpl> resp =
|
||||
std::make_shared<HttpResponseImpl>();
|
||||
resp->setStatusCode(k304NotModified);
|
||||
HttpAppFrameworkImpl::instance().callCallback(
|
||||
req, resp, callback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(k403Forbidden);
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cachedResp)
|
||||
{
|
||||
LOG_TRACE << "Using file cache";
|
||||
HttpAppFrameworkImpl::instance().callCallback(req,
|
||||
cachedResp,
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
HttpResponsePtr resp;
|
||||
if (gzipStaticFlag_ &&
|
||||
req->getHeaderBy("accept-encoding").find("gzip") !=
|
||||
std::string::npos)
|
||||
{
|
||||
// Find compressed file first.
|
||||
auto gzipFileName = filePath + ".gz";
|
||||
std::ifstream infile(gzipFileName, std::ifstream::binary);
|
||||
if (infile)
|
||||
{
|
||||
resp = HttpResponse::newFileResponse(
|
||||
gzipFileName, "", drogon::getContentType(filePath));
|
||||
resp->addHeader("Content-Encoding", "gzip");
|
||||
}
|
||||
}
|
||||
if (!resp)
|
||||
resp = HttpResponse::newFileResponse(filePath);
|
||||
if (resp->statusCode() != k404NotFound)
|
||||
{
|
||||
if (!timeStr.empty())
|
||||
{
|
||||
resp->addHeader("Last-Modified", timeStr);
|
||||
resp->addHeader("Expires", "Thu, 01 Jan 1970 00:00:00 GMT");
|
||||
}
|
||||
if (!headers_.empty())
|
||||
{
|
||||
for (auto &header : headers_)
|
||||
{
|
||||
resp->addHeader(header.first, header.second);
|
||||
}
|
||||
}
|
||||
// cache the response for 5 seconds by default
|
||||
if (staticFilesCacheTime_ >= 0)
|
||||
{
|
||||
LOG_TRACE << "Save in cache for " << staticFilesCacheTime_
|
||||
<< " seconds";
|
||||
resp->setExpiredTime(staticFilesCacheTime_);
|
||||
staticFilesCache_->getThreadData()[filePath] = resp;
|
||||
staticFilesCacheMap_->getThreadData()->insert(
|
||||
filePath, 0, staticFilesCacheTime_, [this, filePath]() {
|
||||
LOG_TRACE << "Erase cache";
|
||||
assert(staticFilesCache_->getThreadData().find(
|
||||
filePath) !=
|
||||
staticFilesCache_->getThreadData().end());
|
||||
staticFilesCache_->getThreadData().erase(filePath);
|
||||
});
|
||||
}
|
||||
HttpAppFrameworkImpl::instance().callCallback(req,
|
||||
resp,
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
callback(resp);
|
||||
std::string filePath =
|
||||
location.realLocation_ +
|
||||
std::string{restOfThePath.data(), restOfThePath.length()};
|
||||
sendStaticFileResponse(filePath,
|
||||
req,
|
||||
callback,
|
||||
string_view{location.defaultContentType_});
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto pos = lPath.rfind('.');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
std::string filetype = lPath.substr(pos + 1);
|
||||
if (fileTypeSet_.find(filetype) != fileTypeSet_.end())
|
||||
{
|
||||
// LOG_INFO << "file query!" << path;
|
||||
std::string filePath =
|
||||
HttpAppFrameworkImpl::instance().getDocumentRoot() + path;
|
||||
sendStaticFileResponse(filePath, req, callback, "");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -195,6 +166,140 @@ void StaticFileRouter::route(
|
||||
callback(HttpResponse::newNotFoundResponse());
|
||||
}
|
||||
|
||||
void StaticFileRouter::sendStaticFileResponse(
|
||||
const std::string &filePath,
|
||||
const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
const string_view &defaultContentType)
|
||||
{ // find cached response
|
||||
HttpResponsePtr cachedResp;
|
||||
auto &cacheMap = staticFilesCache_->getThreadData();
|
||||
auto iter = cacheMap.find(filePath);
|
||||
if (iter != cacheMap.end())
|
||||
{
|
||||
cachedResp = iter->second;
|
||||
}
|
||||
|
||||
// check last modified time,rfc2616-14.25
|
||||
// If-Modified-Since: Mon, 15 Oct 2018 06:26:33 GMT
|
||||
|
||||
std::string timeStr;
|
||||
if (enableLastModify_)
|
||||
{
|
||||
if (cachedResp)
|
||||
{
|
||||
if (static_cast<HttpResponseImpl *>(cachedResp.get())
|
||||
->getHeaderBy("last-modified") ==
|
||||
req->getHeaderBy("if-modified-since"))
|
||||
{
|
||||
std::shared_ptr<HttpResponseImpl> resp =
|
||||
std::make_shared<HttpResponseImpl>();
|
||||
resp->setStatusCode(k304NotModified);
|
||||
HttpAppFrameworkImpl::instance().callCallback(req,
|
||||
resp,
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
struct stat fileStat;
|
||||
LOG_TRACE << "enabled LastModify";
|
||||
if (stat(filePath.c_str(), &fileStat) >= 0)
|
||||
{
|
||||
LOG_TRACE << "last modify time:" << fileStat.st_mtime;
|
||||
struct tm tm1;
|
||||
gmtime_r(&fileStat.st_mtime, &tm1);
|
||||
timeStr.resize(64);
|
||||
auto len = strftime((char *)timeStr.data(),
|
||||
timeStr.size(),
|
||||
"%a, %d %b %Y %T GMT",
|
||||
&tm1);
|
||||
timeStr.resize(len);
|
||||
const std::string &modiStr =
|
||||
req->getHeaderBy("if-modified-since");
|
||||
if (modiStr == timeStr && !modiStr.empty())
|
||||
{
|
||||
LOG_TRACE << "not Modified!";
|
||||
std::shared_ptr<HttpResponseImpl> resp =
|
||||
std::make_shared<HttpResponseImpl>();
|
||||
resp->setStatusCode(k304NotModified);
|
||||
HttpAppFrameworkImpl::instance().callCallback(req,
|
||||
resp,
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cachedResp)
|
||||
{
|
||||
LOG_TRACE << "Using file cache";
|
||||
HttpAppFrameworkImpl::instance().callCallback(req,
|
||||
cachedResp,
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
HttpResponsePtr resp;
|
||||
if (gzipStaticFlag_ &&
|
||||
req->getHeaderBy("accept-encoding").find("gzip") != std::string::npos)
|
||||
{
|
||||
// Find compressed file first.
|
||||
auto gzipFileName = filePath + ".gz";
|
||||
std::ifstream infile(gzipFileName, std::ifstream::binary);
|
||||
if (infile)
|
||||
{
|
||||
resp =
|
||||
HttpResponse::newFileResponse(gzipFileName,
|
||||
"",
|
||||
drogon::getContentType(filePath));
|
||||
resp->addHeader("Content-Encoding", "gzip");
|
||||
}
|
||||
}
|
||||
if (!resp)
|
||||
resp = HttpResponse::newFileResponse(filePath);
|
||||
if (resp->statusCode() != k404NotFound)
|
||||
{
|
||||
if (resp->getContentType() == CT_APPLICATION_OCTET_STREAM &&
|
||||
!defaultContentType.empty())
|
||||
{
|
||||
resp->setContentTypeCodeAndCustomString(CT_CUSTOM,
|
||||
defaultContentType);
|
||||
}
|
||||
if (!timeStr.empty())
|
||||
{
|
||||
resp->addHeader("Last-Modified", timeStr);
|
||||
resp->addHeader("Expires", "Thu, 01 Jan 1970 00:00:00 GMT");
|
||||
}
|
||||
if (!headers_.empty())
|
||||
{
|
||||
for (auto &header : headers_)
|
||||
{
|
||||
resp->addHeader(header.first, header.second);
|
||||
}
|
||||
}
|
||||
// cache the response for 5 seconds by default
|
||||
if (staticFilesCacheTime_ >= 0)
|
||||
{
|
||||
LOG_TRACE << "Save in cache for " << staticFilesCacheTime_
|
||||
<< " seconds";
|
||||
resp->setExpiredTime(staticFilesCacheTime_);
|
||||
staticFilesCache_->getThreadData()[filePath] = resp;
|
||||
staticFilesCacheMap_->getThreadData()->insert(
|
||||
filePath, 0, staticFilesCacheTime_, [this, filePath]() {
|
||||
LOG_TRACE << "Erase cache";
|
||||
assert(staticFilesCache_->getThreadData().find(filePath) !=
|
||||
staticFilesCache_->getThreadData().end());
|
||||
staticFilesCache_->getThreadData().erase(filePath);
|
||||
});
|
||||
}
|
||||
HttpAppFrameworkImpl::instance().callCallback(req, resp, callback);
|
||||
return;
|
||||
}
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
void StaticFileRouter::setFileTypes(const std::vector<std::string> &types)
|
||||
{
|
||||
fileTypeSet_.clear();
|
||||
|
@ -43,10 +43,32 @@ class StaticFileRouter
|
||||
gzipStaticFlag_ = useGzipStatic;
|
||||
}
|
||||
void init(const std::vector<trantor::EventLoop *> &ioloops);
|
||||
StaticFileRouter(
|
||||
const std::vector<std::pair<std::string, std::string>> &headers)
|
||||
: headers_(headers)
|
||||
|
||||
void sendStaticFileResponse(
|
||||
const std::string &filePath,
|
||||
const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
const string_view &defaultContentType);
|
||||
|
||||
void addALocation(const std::string &uriPrefix,
|
||||
const std::string &defaultContentType,
|
||||
const std::string &alias,
|
||||
bool isCaseSensitive,
|
||||
bool allowAll,
|
||||
bool isRecursive)
|
||||
{
|
||||
locations_.emplace_back(uriPrefix,
|
||||
defaultContentType,
|
||||
alias,
|
||||
isCaseSensitive,
|
||||
allowAll,
|
||||
isRecursive);
|
||||
}
|
||||
|
||||
void setStaticFileHeaders(
|
||||
const std::vector<std::pair<std::string, std::string>> &headers)
|
||||
{
|
||||
headers_ = headers;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -79,6 +101,36 @@ class StaticFileRouter
|
||||
std::unique_ptr<
|
||||
IOThreadStorage<std::unordered_map<std::string, HttpResponsePtr>>>
|
||||
staticFilesCache_;
|
||||
const std::vector<std::pair<std::string, std::string>> &headers_;
|
||||
std::vector<std::pair<std::string, std::string>> headers_;
|
||||
struct Location
|
||||
{
|
||||
std::string uriPrefix_;
|
||||
std::string defaultContentType_;
|
||||
std::string alias_;
|
||||
std::string realLocation_;
|
||||
bool isCaseSensitive_;
|
||||
bool allowAll_;
|
||||
bool isRecursive_;
|
||||
Location(const std::string &uriPrefix,
|
||||
const std::string &defaultContentType,
|
||||
const std::string &alias,
|
||||
bool isCaseSensitive,
|
||||
bool allowAll,
|
||||
bool isRecursive)
|
||||
: uriPrefix_(uriPrefix),
|
||||
alias_(alias),
|
||||
isCaseSensitive_(isCaseSensitive),
|
||||
allowAll_(allowAll),
|
||||
isRecursive_(isRecursive)
|
||||
{
|
||||
if (!defaultContentType.empty())
|
||||
{
|
||||
defaultContentType_ =
|
||||
std::string{"Content-Type: "} + defaultContentType + "\r\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
std::unique_ptr<IOThreadStorage<std::vector<Location>>> ioLocationsPtr_;
|
||||
std::vector<Location> locations_;
|
||||
};
|
||||
} // namespace drogon
|
||||
|
Loading…
x
Reference in New Issue
Block a user