Merge pull request #144 from an-tao/dev

Implement gzip_static
This commit is contained in:
An Tao 2019-05-03 17:43:05 +08:00 committed by GitHub
commit 3042327fc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 418 additions and 102 deletions

View File

@ -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": [{

View File

@ -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": [{

View File

@ -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>)

View 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&amp;utm_medium=referral&amp;utm_content=an-tao/drogon&amp;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&amp;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&amp;logoWidth=18" alt="Language grade: C/C++" /></a>
<a href="https://gitter.im/drogon-web/community?utm_source=badge&amp;utm_medium=badge&amp;utm_campaign=pr-badge&amp;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>Drogons 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 &lt;drogon/drogon.h&gt;
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 &lt;drogon/drogon.h&gt;
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&amp; req,
const std::function&lt;void (const HttpResponsePtr &amp;)&gt; &amp; callback,
const std::string &amp;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, isnt it a better practice to implement them separately in their respective classes? So unless your logic is very simple, we dont recommend using above interfaces. Instead, we can create an HttpSimpleController as follows:</p>
<p>```c++
/// The TestCtrl.h file
#pragma once
#include &lt;drogon/HttpSimpleController.h&gt;
using namespace drogon;
class TestCtrl:public drogon::HttpSimpleController<testctrl>
{
public:
virtual void asyncHandleHttpRequest(const HttpRequestPtr&amp; req,const std::function&lt;void (const HttpResponsePtr &amp;)&gt; &amp; 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&amp; req,
const std::function&lt;void (const HttpResponsePtr &amp;)&gt; &amp; callback)
{
//write your application logic here
auto resp = HttpResponse::newHttpResponse();
resp-&gt;setBody(“&lt;p&gt;Hello, world!&lt;/p&gt;”);
resp-&gt;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 &lt;drogon/HttpSimpleController.h&gt;
using namespace drogon;
class JsonCtrl : public drogon::HttpSimpleController<jsonctrl>
{
public:
virtual void asyncHandleHttpRequest(const HttpRequestPtr &amp;req, const std::function&lt;void(const HttpResponsePtr &amp;)&gt; &amp;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 &amp;req,
const std::function&lt;void(const HttpResponsePtr &amp;)&gt; &amp;callback)
{
Json::Value ret;
ret[“message”] = “Hello, World!”;
auto resp = HttpResponse::newHttpJsonResponse(ret);
callback(resp);
}
```</p>
<p>Lets 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 &lt;drogon/HttpController.h&gt;
using namespace drogon;
namespace api
{
namespace v1
{
class User : public drogon::HttpController&lt;User&gt;
{
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 &amp;req, const std::function&lt;void(const HttpResponsePtr &amp;)&gt; &amp;callback, int userId) const;
void getDetailInfo(const HttpRequestPtr &amp;req, const std::function&lt;void(const HttpResponsePtr &amp;)&gt; &amp;callback, int userId) const;
void newUser(const HttpRequestPtr &amp;req, const std::function&lt;void(const HttpResponsePtr &amp;)&gt; &amp;callback, std::string &amp;&amp;userName);
public:
User()
{
LOG_DEBUG &lt;&lt; "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>

View File

@ -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);

View File

@ -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
/**

View File

@ -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)
{

View File

@ -743,7 +743,22 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, std::fu
callback(cachedResp);
return;
}
auto resp = HttpResponse::newFileResponse(filePath);
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);
@ -776,6 +791,7 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, std::fu
callback(newCachedResp);
return;
}
}
callback(resp);
return;
}

View File

@ -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;

View File

@ -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

View File

@ -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)
{

View File

@ -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

View File

@ -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