mirror of
https://gitee.com/an-tao/drogon.git
synced 2024-12-02 19:57:43 +08:00
commit
3042327fc8
@ -147,7 +147,11 @@
|
||||
//pipelining_requests: Set the maximum number of unhandled requests that can be cached in pipelining buffer.
|
||||
//After the maximum number of requests are made, the connection is closed.
|
||||
//The default value of 0 means no limit.
|
||||
"pipelining_requests": 0
|
||||
"pipelining_requests": 0,
|
||||
//gzip_static: If it is set to true, when the client requests a static file, drogon first finds the compressed
|
||||
//file with the extension ".gz" in the same path and send the compressed file to the client.
|
||||
//The default value of gzip_static is true.
|
||||
"gzip_static": true
|
||||
},
|
||||
//plugins: Define all plugins running in the application
|
||||
"plugins": [{
|
||||
|
@ -147,7 +147,11 @@
|
||||
//pipelining_requests: Set the maximum number of unhandled requests that can be cached in pipelining buffer.
|
||||
//After the maximum number of requests are made, the connection is closed.
|
||||
//The default value of 0 means no limit.
|
||||
"pipelining_requests": 0
|
||||
"pipelining_requests": 0,
|
||||
//gzip_static: If it is set to true, when the client requests a static file, drogon first finds the compressed
|
||||
//file with the extension ".gz" in the same path and send the compressed file to the client.
|
||||
//The default value of gzip_static is true.
|
||||
"gzip_static": true
|
||||
},
|
||||
//plugins: Define all plugins running in the application
|
||||
"plugins": [{
|
||||
|
@ -29,7 +29,12 @@ add_executable(webapp_test simple_example_test/main.cc)
|
||||
add_executable(pipelining_test simple_example_test/HttpPipeliningTest.cc)
|
||||
add_executable(websocket_test simple_example_test/WebSocketTest.cc)
|
||||
|
||||
add_custom_command(TARGET webapp POST_BUILD
|
||||
COMMAND gzip
|
||||
ARGS -c ${CMAKE_CURRENT_SOURCE_DIR}/simple_example/index.html > ${CMAKE_CURRENT_BINARY_DIR}/index.html.gz
|
||||
VERBATIM)
|
||||
add_custom_command(TARGET webapp POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${PROJECT_SOURCE_DIR}/config.example.json ${PROJECT_SOURCE_DIR}/drogon.jpg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/simple_example/index.html
|
||||
${PROJECT_SOURCE_DIR}/trantor/trantor/tests/server.pem $<TARGET_FILE_DIR:webapp>)
|
187
examples/simple_example/index.html
Executable file
187
examples/simple_example/index.html
Executable file
@ -0,0 +1,187 @@
|
||||
<p><img src="https://github.com/an-tao/drogon/wiki/images/drogon-white.jpg" alt="" /></p>
|
||||
|
||||
<p><a href="https://travis-ci.com/an-tao/drogon"><img src="https://travis-ci.com/an-tao/drogon.svg?branch=master" alt="Build Status" /></a>
|
||||
<a href="https://app.codacy.com/app/an-tao/drogon?utm_source=github.com&utm_medium=referral&utm_content=an-tao/drogon&utm_campaign=Badge_Grade_Dashboard"><img src="https://api.codacy.com/project/badge/Grade/45f8a65ca1844788b9109c0044a618f8" alt="Codacy Badge" /></a>
|
||||
<a href="https://lgtm.com/projects/g/an-tao/drogon/alerts/"><img src="https://img.shields.io/lgtm/alerts/g/an-tao/drogon.svg?logo=lgtm&logoWidth=18" alt="Total alerts" /></a>
|
||||
<a href="https://lgtm.com/projects/g/an-tao/drogon/context:cpp"><img src="https://img.shields.io/lgtm/grade/cpp/g/an-tao/drogon.svg?logo=lgtm&logoWidth=18" alt="Language grade: C/C++" /></a>
|
||||
<a href="https://gitter.im/drogon-web/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/drogon-web/community.svg" alt="Join the chat at https://gitter.im/drogon-web/community" /></a>
|
||||
<a href="https://cloud.docker.com/u/drogonframework/repository/docker/drogonframework/drogon"><img src="https://img.shields.io/badge/Docker-image-blue.svg" alt="Docker image" /></a></p>
|
||||
|
||||
<h3 id="overview">Overview</h3>
|
||||
<p><strong>Drogon</strong> is a C++14/17-based HTTP application framework. Drogon can be used to easily build various types of web application server programs using C++. <strong>Drogon</strong> is the name of a dragon in the American TV series “Game of Thrones” that I really like.</p>
|
||||
|
||||
<p>Drogon’s main application platform is Linux. It also supports Mac OS and FreeBSD. Currently, it does not support windows. Its main features are as follows:</p>
|
||||
|
||||
<ul>
|
||||
<li>Use a non-blocking I/O network lib based on epoll (kqueue under MacOS/FreeBSD) to provide high-concurrency, high-performance network IO, please visit the <a href="https://github.com/an-tao/drogon/wiki/benchmarks">benchmarks</a> page for more details;</li>
|
||||
<li>Provide a completely asynchronous programming mode;</li>
|
||||
<li>Support Http1.0/1.1 (server side and client side);</li>
|
||||
<li>Based on template, a simple reflection mechanism is implemented to completely decouple the main program framework, controllers and views.</li>
|
||||
<li>Support cookies and built-in sessions;</li>
|
||||
<li>Support back-end rendering, the controller generates the data to the view to generate the Html page, the view is described by a “JSP-like” CSP file, the C++ code is embedded into the Html page by the CSP tag, and the drogon command-line tool automatically generates the C++ code file for compilation;</li>
|
||||
<li>Support view page dynamic loading (dynamic compilation and loading at runtime);</li>
|
||||
<li>Provide a convenient and flexible routing solution from the path to the controller handler;</li>
|
||||
<li>Support filter chains to facilitate the execution of unified logic (such as login verification, Http Method constraint verification, etc.) before controllers;</li>
|
||||
<li>Support https (based on OpenSSL);</li>
|
||||
<li>Support WebSocket (server side and client side);</li>
|
||||
<li>Support JSON format request and response, very friendly to the Restful API application development;</li>
|
||||
<li>Support file download and upload;</li>
|
||||
<li>Support gzip compression transmission;</li>
|
||||
<li>Support pipelining;</li>
|
||||
<li>Provide a lightweight command line tool, drogon_ctl, to simplify the creation of various classes in Drogon and the generation of view code;</li>
|
||||
<li>Support non-blocking I/O based asynchronously reading and writing database (PostgreSQL and MySQL(MariaDB) database);</li>
|
||||
<li>Support asynchronously reading and writing sqlite3 database based on thread pool;</li>
|
||||
<li>Support ARM Architecture;</li>
|
||||
<li>Provide a convenient lightweight ORM implementation that supports for regular object-to-database bidirectional mapping;</li>
|
||||
<li>Support plugins which can be installed by the configuration file at load time;</li>
|
||||
<li>Support AOP with build-in joinpoints.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="a-very-simple-example">A very simple example</h2>
|
||||
|
||||
<p>Unlike most C++ frameworks, the main program of the drogon application can be kept clean and simple. Drogon uses a few tricks to decouple controllers from the main program. The routing settings of controllers can be done through macros or configuration file.</p>
|
||||
|
||||
<p>Below is the main program of a typical drogon application:</p>
|
||||
|
||||
<p><code>c++
|
||||
#include <drogon/drogon.h>
|
||||
using namespace drogon;
|
||||
int main()
|
||||
{
|
||||
app().setLogPath("./");
|
||||
app().setLogLevel(trantor::Logger::WARN);
|
||||
app().addListener("0.0.0.0", 80);
|
||||
app().setThreadNum(16);
|
||||
app().enableRunAsDaemon();
|
||||
app().run();
|
||||
}
|
||||
</code></p>
|
||||
|
||||
<p>It can be further simplified by using configuration file as follows:</p>
|
||||
|
||||
<p><code>c++
|
||||
#include <drogon/drogon.h>
|
||||
using namespace drogon;
|
||||
int main()
|
||||
{
|
||||
app().loadConfigFile("./config.json");
|
||||
app().run();
|
||||
}
|
||||
</code></p>
|
||||
|
||||
<p>Drogon provides some interfaces for adding controller logic directly in the main() function, for example, user can register a handler like this in Drogon:</p>
|
||||
|
||||
<p><code>c++
|
||||
app.registerHandler("/test?username={1}",
|
||||
[](const HttpRequestPtr& req,
|
||||
const std::function<void (const HttpResponsePtr &)> & callback,
|
||||
const std::string &name)
|
||||
{
|
||||
Json::Value json;
|
||||
json["result"]="ok";
|
||||
json["message"]=std::string("hello,")+name;
|
||||
auto resp=HttpResponse::newHttpJsonResponse(json);
|
||||
callback(resp);
|
||||
},
|
||||
{Get,"LoginFilter"});
|
||||
</code></p>
|
||||
|
||||
<p>While such interfaces look intuitive, they are not suitable for complex business logic scenarios. Assuming there are tens or even hundreds of handlers that need to be registered in the framework, isn’t it a better practice to implement them separately in their respective classes? So unless your logic is very simple, we don’t recommend using above interfaces. Instead, we can create an HttpSimpleController as follows:</p>
|
||||
|
||||
<p>```c++
|
||||
/// The TestCtrl.h file
|
||||
#pragma once
|
||||
#include <drogon/HttpSimpleController.h>
|
||||
using namespace drogon;
|
||||
class TestCtrl:public drogon::HttpSimpleController<testctrl>
|
||||
{
|
||||
public:
|
||||
virtual void asyncHandleHttpRequest(const HttpRequestPtr& req,const std::function<void (const HttpResponsePtr &)> & callback)override;
|
||||
PATH_LIST_BEGIN
|
||||
PATH_ADD("/test",Get);
|
||||
PATH_LIST_END
|
||||
};</testctrl></p>
|
||||
|
||||
<p>/// The TestCtrl.cc file
|
||||
#include “TestCtrl.h”
|
||||
void TestCtrl::asyncHandleHttpRequest(const HttpRequestPtr& req,
|
||||
const std::function<void (const HttpResponsePtr &)> & callback)
|
||||
{
|
||||
//write your application logic here
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setBody(“<p>Hello, world!</p>”);
|
||||
resp->setExpiredTime(0);
|
||||
callback(resp);
|
||||
}
|
||||
```</p>
|
||||
|
||||
<p><strong>Most of the above programs can be automatically generated by the command line tool <code>drogon_ctl</code> provided by drogon</strong> (The cammand is <code>drogon_ctl create controller TestCtrl</code>). All the user needs to do is add their own business logic. In the example, the controller returns a <code>Hello, world!</code> string when the client accesses the <code>http://ip/test</code> URL.</p>
|
||||
|
||||
<p>For JSON format response, we create the controller as follows:</p>
|
||||
|
||||
<p>```c++
|
||||
/// The header file
|
||||
#pragma once
|
||||
#include <drogon/HttpSimpleController.h>
|
||||
using namespace drogon;
|
||||
class JsonCtrl : public drogon::HttpSimpleController<jsonctrl>
|
||||
{
|
||||
public:
|
||||
virtual void asyncHandleHttpRequest(const HttpRequestPtr &req, const std::function<void(const HttpResponsePtr &)> &callback) override;
|
||||
PATH_LIST_BEGIN
|
||||
//list path definitions here;
|
||||
PATH_ADD("/json", Get);
|
||||
PATH_LIST_END
|
||||
};</jsonctrl></p>
|
||||
|
||||
<p>/// The source file
|
||||
#include “JsonCtrl.h”
|
||||
void JsonCtrl::asyncHandleHttpRequest(const HttpRequestPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback)
|
||||
{
|
||||
Json::Value ret;
|
||||
ret[“message”] = “Hello, World!”;
|
||||
auto resp = HttpResponse::newHttpJsonResponse(ret);
|
||||
callback(resp);
|
||||
}
|
||||
```</p>
|
||||
|
||||
<p>Let’s go a step further and create a demo RESTful API with the HttpController class, as shown below (Omit the source file):</p>
|
||||
|
||||
<p><code>c++
|
||||
/// The header file
|
||||
#pragma once
|
||||
#include <drogon/HttpController.h>
|
||||
using namespace drogon;
|
||||
namespace api
|
||||
{
|
||||
namespace v1
|
||||
{
|
||||
class User : public drogon::HttpController<User>
|
||||
{
|
||||
public:
|
||||
METHOD_LIST_BEGIN
|
||||
//use METHOD_ADD to add your custom processing function here;
|
||||
METHOD_ADD(User::getInfo, "/{1}", Get); //path is /api/v1/User/{arg1}
|
||||
METHOD_ADD(User::getDetailInfo, "/{1}/detailinfo", Get); //path is /api/v1/User/{arg1}/detailinfo
|
||||
METHOD_ADD(User::newUser, "/{1}", Post); //path is /api/v1/User/{arg1}
|
||||
METHOD_LIST_END
|
||||
//your declaration of processing function maybe like this:
|
||||
void getInfo(const HttpRequestPtr &req, const std::function<void(const HttpResponsePtr &)> &callback, int userId) const;
|
||||
void getDetailInfo(const HttpRequestPtr &req, const std::function<void(const HttpResponsePtr &)> &callback, int userId) const;
|
||||
void newUser(const HttpRequestPtr &req, const std::function<void(const HttpResponsePtr &)> &callback, std::string &&userName);
|
||||
public:
|
||||
User()
|
||||
{
|
||||
LOG_DEBUG << "User constructor!";
|
||||
}
|
||||
};
|
||||
} // namespace v1
|
||||
} // namespace api
|
||||
</code></p>
|
||||
|
||||
<p>As you can see, users can use the <code>HttpController</code> to map paths and parameters at the same time. This is a very convenient way to create a RESTful API application.</p>
|
||||
|
||||
<p>In addition, you can also find that all handler interfaces are in asynchronous mode, where the response is returned by a callback object. This design is for performance reasons because in asynchronous mode the drogon application can handle a large number of concurrent requests with a small number of threads.</p>
|
||||
|
||||
<p>After compiling all of the above source files, we get a very simple web application. This is a good start. <strong>for more information, please visit the <a href="https://github.com/an-tao/drogon/wiki">wiki</a> site</strong></p>
|
@ -27,6 +27,8 @@
|
||||
#define GREEN "\033[32m" /* Green */
|
||||
|
||||
#define JPG_LEN 44618
|
||||
#define INDEX_LEN 10605
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
void outputGood(const HttpRequestPtr &req, bool isHttps)
|
||||
@ -579,7 +581,54 @@ void doTest(const HttpClientPtr &client, std::promise<int> &pro, bool isHttps =
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
/// Test gzip_static
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/index.html");
|
||||
client->sendRequest(req, [=](ReqResult result, const HttpResponsePtr &resp) {
|
||||
if (result == ReqResult::Ok)
|
||||
{
|
||||
if (resp->getBody().length() == INDEX_LEN)
|
||||
{
|
||||
outputGood(req, isHttps);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << resp->getBody().length();
|
||||
LOG_ERROR << "Error!";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Error!";
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/index.html");
|
||||
req->addHeader("accept-encoding", "gzip");
|
||||
client->sendRequest(req, [=](ReqResult result, const HttpResponsePtr &resp) {
|
||||
if (result == ReqResult::Ok)
|
||||
{
|
||||
if (resp->getBody().length() == INDEX_LEN)
|
||||
{
|
||||
outputGood(req, isHttps);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << resp->getBody().length();
|
||||
LOG_ERROR << "Error!";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Error!";
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
/// Test file download
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
|
@ -641,6 +641,17 @@ public:
|
||||
*/
|
||||
virtual void setPipeliningRequestsNumber(const size_t number) = 0;
|
||||
|
||||
///Set the gzip_static option.
|
||||
/**
|
||||
* If it is set to true, when the client requests a static file, drogon first finds the compressed
|
||||
* file with the extension ".gz" in the same path and send the compressed file to the client.
|
||||
* The default value is true.
|
||||
*
|
||||
* NOTE:
|
||||
* This operation can be performed by an option in the configuration file.
|
||||
*/
|
||||
virtual void setGzipStatic(bool useGzipStatic) = 0;
|
||||
|
||||
#if USE_ORM
|
||||
///Get a database client by @param name
|
||||
/**
|
||||
|
@ -235,6 +235,8 @@ static void loadApp(const Json::Value &app)
|
||||
drogon::app().setKeepaliveRequestsNumber(keepaliveReqs);
|
||||
auto pipeliningReqs = app.get("pipelining_requests", 0).asUInt64();
|
||||
drogon::app().setPipeliningRequestsNumber(pipeliningReqs);
|
||||
auto useGzipStatic = app.get("gzip_static", true).asBool();
|
||||
drogon::app().setGzipStatic(useGzipStatic);
|
||||
}
|
||||
static void loadDbClients(const Json::Value &dbClients)
|
||||
{
|
||||
|
@ -57,7 +57,7 @@ namespace drogon
|
||||
|
||||
class DrogonFileLocker : public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
public:
|
||||
DrogonFileLocker()
|
||||
{
|
||||
_fd = open("/tmp/drogon.lock", O_TRUNC | O_CREAT, 0755);
|
||||
@ -68,7 +68,7 @@ class DrogonFileLocker : public trantor::NonCopyable
|
||||
close(_fd);
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
int _fd = 0;
|
||||
};
|
||||
|
||||
@ -743,38 +743,54 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, std::fu
|
||||
callback(cachedResp);
|
||||
return;
|
||||
}
|
||||
auto resp = HttpResponse::newFileResponse(filePath);
|
||||
if (!timeStr.empty())
|
||||
HttpResponsePtr resp;
|
||||
if (_gzipStaticFlag && req->getHeaderBy("accept-encoding").find("gzip") != std::string::npos)
|
||||
{
|
||||
resp->addHeader("Last-Modified", timeStr);
|
||||
resp->addHeader("Expires", "Thu, 01 Jan 1970 00:00:00 GMT");
|
||||
}
|
||||
//cache the response for 5 seconds by default
|
||||
if (_staticFilesCacheTime >= 0)
|
||||
{
|
||||
resp->setExpiredTime(_staticFilesCacheTime);
|
||||
_responseCachingMap->insert(filePath, resp, resp->expiredTime(), [=]() {
|
||||
std::lock_guard<std::mutex> guard(_staticFilesCacheMutex);
|
||||
_staticFilesCache.erase(filePath);
|
||||
});
|
||||
//Find compressed file first.
|
||||
auto gzipFileName = filePath + ".gz";
|
||||
std::ifstream infile(gzipFileName, std::ifstream::binary);
|
||||
if (infile)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_staticFilesCacheMutex);
|
||||
_staticFilesCache[filePath] = resp;
|
||||
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");
|
||||
}
|
||||
//cache the response for 5 seconds by default
|
||||
if (_staticFilesCacheTime >= 0)
|
||||
{
|
||||
resp->setExpiredTime(_staticFilesCacheTime);
|
||||
_responseCachingMap->insert(filePath, resp, resp->expiredTime(), [=]() {
|
||||
std::lock_guard<std::mutex> guard(_staticFilesCacheMutex);
|
||||
_staticFilesCache.erase(filePath);
|
||||
});
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_staticFilesCacheMutex);
|
||||
_staticFilesCache[filePath] = resp;
|
||||
}
|
||||
}
|
||||
|
||||
if (needSetJsessionid)
|
||||
{
|
||||
auto newCachedResp = resp;
|
||||
if (resp->expiredTime() >= 0)
|
||||
if (needSetJsessionid)
|
||||
{
|
||||
//make a copy
|
||||
newCachedResp = std::make_shared<HttpResponseImpl>(*std::dynamic_pointer_cast<HttpResponseImpl>(resp));
|
||||
newCachedResp->setExpiredTime(-1);
|
||||
auto newCachedResp = resp;
|
||||
if (resp->expiredTime() >= 0)
|
||||
{
|
||||
//make a copy
|
||||
newCachedResp = std::make_shared<HttpResponseImpl>(*std::dynamic_pointer_cast<HttpResponseImpl>(resp));
|
||||
newCachedResp->setExpiredTime(-1);
|
||||
}
|
||||
newCachedResp->addCookie("JSESSIONID", sessionId);
|
||||
callback(newCachedResp);
|
||||
return;
|
||||
}
|
||||
newCachedResp->addCookie("JSESSIONID", sessionId);
|
||||
callback(newCachedResp);
|
||||
return;
|
||||
}
|
||||
callback(resp);
|
||||
return;
|
||||
|
@ -170,6 +170,7 @@ public:
|
||||
virtual void setIdleConnectionTimeout(size_t timeout) override { _idleConnectionTimeout = timeout; }
|
||||
virtual void setKeepaliveRequestsNumber(const size_t number) override { _keepaliveRequestsNumber = number; }
|
||||
virtual void setPipeliningRequestsNumber(const size_t number) override { _pipeliningRequestsNumber = number; }
|
||||
virtual void setGzipStatic(bool useGzipStatic) override { _gzipStaticFlag = useGzipStatic; }
|
||||
virtual std::vector<std::tuple<std::string, HttpMethod, std::string>> getHandlersInfo() const override;
|
||||
|
||||
size_t keepaliveRequestsNumber() const { return _keepaliveRequestsNumber; }
|
||||
@ -293,6 +294,7 @@ private:
|
||||
size_t _pipeliningRequestsNumber = 0;
|
||||
bool _useSendfile = true;
|
||||
bool _useGzip = true;
|
||||
bool _gzipStaticFlag = true;
|
||||
int _staticFilesCacheTime = 5;
|
||||
std::unordered_map<std::string, std::weak_ptr<HttpResponse>> _staticFilesCache;
|
||||
std::mutex _staticFilesCacheMutex;
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#include "HttpResponseImpl.h"
|
||||
#include "HttpUtils.h"
|
||||
#include <drogon/HttpViewBase.h>
|
||||
#include <drogon/HttpViewData.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
@ -80,15 +81,15 @@ HttpResponsePtr HttpResponse::newHttpViewResponse(const std::string &viewName, c
|
||||
|
||||
HttpResponsePtr HttpResponse::newFileResponse(const std::string &fullPath, const std::string &attachmentFileName, ContentType type)
|
||||
{
|
||||
auto resp = std::make_shared<HttpResponseImpl>();
|
||||
|
||||
std::ifstream infile(fullPath, std::ifstream::binary);
|
||||
LOG_TRACE << "send http file:" << fullPath;
|
||||
if (!infile)
|
||||
{
|
||||
resp->setStatusCode(k404NotFound);
|
||||
resp->setCloseConnection(true);
|
||||
auto resp = HttpResponse::newNotFoundResponse();
|
||||
return resp;
|
||||
}
|
||||
auto resp = std::make_shared<HttpResponseImpl>();
|
||||
std::streambuf *pbuf = infile.rdbuf();
|
||||
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
|
||||
pbuf->pubseekoff(0, infile.beg); // rewind
|
||||
@ -97,7 +98,7 @@ HttpResponsePtr HttpResponse::newFileResponse(const std::string &fullPath, const
|
||||
//TODO : Is 200k an appropriate value? Or set it to be configurable
|
||||
{
|
||||
//The advantages of sendfile() can only be reflected in sending large files.
|
||||
std::dynamic_pointer_cast<HttpResponseImpl>(resp)->setSendfile(fullPath);
|
||||
resp->setSendfile(fullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -110,71 +111,13 @@ HttpResponsePtr HttpResponse::newFileResponse(const std::string &fullPath, const
|
||||
|
||||
if (type == CT_NONE)
|
||||
{
|
||||
std::string filename;
|
||||
if (!attachmentFileName.empty())
|
||||
{
|
||||
filename = attachmentFileName;
|
||||
resp->setContentTypeCode(drogon::getContentType(attachmentFileName));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto pos = fullPath.rfind("/");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
filename = fullPath.substr(pos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
filename = fullPath;
|
||||
}
|
||||
}
|
||||
std::string filetype;
|
||||
auto pos = filename.rfind(".");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
filetype = filename.substr(pos + 1);
|
||||
transform(filetype.begin(), filetype.end(), filetype.begin(), tolower);
|
||||
}
|
||||
if (filetype == "html")
|
||||
resp->setContentTypeCode(CT_TEXT_HTML);
|
||||
else if (filetype == "js")
|
||||
resp->setContentTypeCode(CT_APPLICATION_X_JAVASCRIPT);
|
||||
else if (filetype == "css")
|
||||
resp->setContentTypeCode(CT_TEXT_CSS);
|
||||
else if (filetype == "xml")
|
||||
resp->setContentTypeCode(CT_TEXT_XML);
|
||||
else if (filetype == "xsl")
|
||||
resp->setContentTypeCode(CT_TEXT_XSL);
|
||||
else if (filetype == "txt")
|
||||
resp->setContentTypeCode(CT_TEXT_PLAIN);
|
||||
else if (filetype == "svg")
|
||||
resp->setContentTypeCode(CT_IMAGE_SVG_XML);
|
||||
else if (filetype == "ttf")
|
||||
resp->setContentTypeCode(CT_APPLICATION_X_FONT_TRUETYPE);
|
||||
else if (filetype == "otf")
|
||||
resp->setContentTypeCode(CT_APPLICATION_X_FONT_OPENTYPE);
|
||||
else if (filetype == "woff2")
|
||||
resp->setContentTypeCode(CT_APPLICATION_FONT_WOFF2);
|
||||
else if (filetype == "woff")
|
||||
resp->setContentTypeCode(CT_APPLICATION_FONT_WOFF);
|
||||
else if (filetype == "eot")
|
||||
resp->setContentTypeCode(CT_APPLICATION_VND_MS_FONTOBJ);
|
||||
else if (filetype == "png")
|
||||
resp->setContentTypeCode(CT_IMAGE_PNG);
|
||||
else if (filetype == "jpg")
|
||||
resp->setContentTypeCode(CT_IMAGE_JPG);
|
||||
else if (filetype == "jpeg")
|
||||
resp->setContentTypeCode(CT_IMAGE_JPG);
|
||||
else if (filetype == "gif")
|
||||
resp->setContentTypeCode(CT_IMAGE_GIF);
|
||||
else if (filetype == "bmp")
|
||||
resp->setContentTypeCode(CT_IMAGE_BMP);
|
||||
else if (filetype == "ico")
|
||||
resp->setContentTypeCode(CT_IMAGE_XICON);
|
||||
else if (filetype == "icns")
|
||||
resp->setContentTypeCode(CT_IMAGE_ICNS);
|
||||
else
|
||||
{
|
||||
resp->setContentTypeCode(CT_APPLICATION_OCTET_STREAM);
|
||||
resp->setContentTypeCode(drogon::getContentType(fullPath));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -213,7 +213,7 @@ void HttpServer::onRequest(const TcpConnectionPtr &conn, const HttpRequestImplPt
|
||||
if (HttpAppFramework::instance().isGzipEnabled() &&
|
||||
sendfileName.empty() &&
|
||||
req->getHeaderBy("accept-encoding").find("gzip") != std::string::npos &&
|
||||
std::dynamic_pointer_cast<HttpResponseImpl>(response)->getHeaderBy("content-encoding") == "" &&
|
||||
std::dynamic_pointer_cast<HttpResponseImpl>(response)->getHeaderBy("content-encoding").empty() &&
|
||||
response->getContentType() < CT_APPLICATION_OCTET_STREAM &&
|
||||
response->getBody().length() > 1024)
|
||||
{
|
||||
|
@ -375,4 +375,103 @@ const string_view &statusCodeToString(int code)
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace drogon
|
@ -19,17 +19,11 @@
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <drogon/WebSocketConnection.h>
|
||||
|
||||
#if CXX_STD >= 17
|
||||
#include <string_view>
|
||||
typedef std::string_view string_view;
|
||||
#else
|
||||
#include <experimental/string_view>
|
||||
typedef std::experimental::basic_string_view<char> string_view;
|
||||
#endif
|
||||
namespace drogon
|
||||
{
|
||||
|
||||
const string_view &webContentTypeToString(ContentType contenttype);
|
||||
const string_view &statusCodeToString(int code);
|
||||
ContentType getContentType(const std::string &extName);
|
||||
|
||||
} // namespace drogon
|
||||
|
Loading…
Reference in New Issue
Block a user