复现已有算法

This commit is contained in:
cloud
2026-07-14 15:43:18 +08:00
parent abebd2a683
commit 50b8111fd9
860 changed files with 182250 additions and 18 deletions
+139
View File
@@ -0,0 +1,139 @@
link_libraries(${PROJECT_NAME})
if(WIN32)
link_libraries(iphlpapi)
endif(WIN32)
set(UNITTEST_SOURCES
unittests/main.cc
unittests/Base64Test.cc
unittests/UrlCodecTest.cc
unittests/GzipTest.cc
unittests/HttpViewDataTest.cc
unittests/CookieTest.cc
unittests/ClassNameTest.cc
unittests/HttpDateTest.cc
unittests/HttpHeaderTest.cc
unittests/MD5Test.cc
unittests/MsgBufferTest.cc
unittests/OStringStreamTest.cc
unittests/PubSubServiceUnittest.cc
unittests/Sha1Test.cc
unittests/FileTypeTest.cc
unittests/DrObjectTest.cc
unittests/HttpFullDateTest.cc
unittests/MainLoopTest.cc
unittests/CacheMapTest.cc
unittests/StringOpsTest.cc
unittests/ControllerCreationTest.cc
unittests/MultiPartParserTest.cc
unittests/SlashRemoverTest.cc
unittests/UtilitiesTest.cc
unittests/UuidUnittest.cc
)
if(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
set(UNITTEST_SOURCES ${UNITTEST_SOURCES} unittests/CoroutineTest.cc)
endif()
if(Brotli_FOUND)
set(UNITTEST_SOURCES ${UNITTEST_SOURCES} unittests/BrotliTest.cc)
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND BUILD_SHARED_LIBS)
set(UNITTEST_SOURCES ${UNITTEST_SOURCES} ../src/HttpUtils.cc)
else()
set(UNITTEST_SOURCES ${UNITTEST_SOURCES} ../src/HttpFileImpl.cc
unittests/HttpFileTest.cc
unittests/WebsocketResponseTest.cc)
endif()
add_executable(unittest ${UNITTEST_SOURCES})
if (BUILD_CTL)
set(INTEGRATION_TEST_CLIENT_SOURCES
integration_test/client/main.cc
integration_test/client/WebSocketTest.cc
integration_test/client/MultipleWsTest.cc
integration_test/client/HttpPipeliningTest.cc
integration_test/client/RequestStreamTest.cc)
add_executable(integration_test_client ${INTEGRATION_TEST_CLIENT_SOURCES})
set(INTEGRATION_TEST_SERVER_SOURCES
integration_test/server/CustomCtrl.cc
integration_test/server/CustomHeaderFilter.cc
integration_test/server/DoNothingPlugin.cc
integration_test/server/ForwardCtrl.cc
integration_test/server/JsonTestController.cc
integration_test/server/ListParaCtl.cc
integration_test/server/PipeliningTest.cc
integration_test/server/TestController.cc
integration_test/server/TestPlugin.cc
integration_test/server/TestViewCtl.cc
integration_test/server/WebSocketTest.cc
integration_test/server/api_Attachment.cc
integration_test/server/api_v1_ApiTest.cc
integration_test/server/TimeFilter.cc
integration_test/server/DigestAuthFilter.cc
integration_test/server/MethodTest.cc
integration_test/server/RangeTestController.cc
integration_test/server/BeginAdviceTest.cc
integration_test/server/MiddlewareTest.cc
integration_test/server/RequestStreamTestCtrl.cc
integration_test/server/main.cc)
if(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
set(INTEGRATION_TEST_SERVER_SOURCES
${INTEGRATION_TEST_SERVER_SOURCES}
integration_test/server/CoroFilter.cpp
integration_test/server/api_v1_CoroTest.cc)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
endif(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
add_executable(integration_test_server ${INTEGRATION_TEST_SERVER_SOURCES})
drogon_create_views(integration_test_server
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server
${CMAKE_CURRENT_BINARY_DIR})
add_dependencies(integration_test_server drogon_ctl)
add_custom_command(
TARGET integration_test_server POST_BUILD
COMMAND ${CMAKE_COMMAND}
-E
copy_if_different
${PROJECT_SOURCE_DIR}/config.example.json
${PROJECT_SOURCE_DIR}/drogon.jpg
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server/index.html
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server/main.cc
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server/test.md
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server/index.html.gz
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server/中文.txt
${PROJECT_SOURCE_DIR}/trantor/trantor/tests/server.crt
${PROJECT_SOURCE_DIR}/trantor/trantor/tests/server.key
$<TARGET_FILE_DIR:integration_test_server>)
add_custom_command(
TARGET integration_test_server POST_BUILD
COMMAND ${CMAKE_COMMAND}
-E
copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server/a-directory
$<TARGET_FILE_DIR:integration_test_server>/a-directory)
endif(BUILD_CTL)
set(COOKIE_SAME_SITE
main_CookieSameSite.cc
CookieSameSite.cc
)
add_executable(cookie_same_site ${COOKIE_SAME_SITE})
add_executable(real_ip_resolver RealIpResolverTest.cc)
set(tests unittest cookie_same_site real_ip_resolver)
if (BUILD_CTL)
list(APPEND tests integration_test_server integration_test_client)
endif(BUILD_CTL)
set_property(TARGET ${tests} PROPERTY CXX_STANDARD ${DROGON_CXX_STANDARD})
set_property(TARGET ${tests} PROPERTY CXX_STANDARD_REQUIRED ON)
set_property(TARGET ${tests} PROPERTY CXX_EXTENSIONS OFF)
ParseAndAddDrogonTests(unittest)
ParseAndAddDrogonTests(cookie_same_site)
ParseAndAddDrogonTests(real_ip_resolver)
+78
View File
@@ -0,0 +1,78 @@
#include <drogon/Cookie.h>
#include <drogon/drogon_test.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/HttpClient.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <drogon/HttpTypes.h>
using namespace drogon;
struct CookieSameSiteSequence
{
CookieSameSiteSequence()
{
i = valid_sameSite_values.begin();
sessionCookie.setKey("JSESSIONID");
}
std::vector<std::string> valid_sameSite_values{"Null",
"Lax",
"None",
"Strict"};
std::vector<std::string>::const_iterator i;
Cookie sessionCookie;
};
DROGON_TEST(CookieSameSite)
{
auto client =
HttpClient::newHttpClient("https://127.0.0.1:8855",
HttpAppFramework::instance().getLoop(),
false,
false);
auto req = HttpRequest::newHttpRequest();
CookieSameSiteSequence seq;
while (seq.i != seq.valid_sameSite_values.end())
{
std::promise<void> p1;
std::future<void> f1 = p1.get_future();
req->setPath(std::string("/CookieSameSiteController/set/") + *seq.i);
if (seq.sessionCookie.getValue() != "")
{
// add session cookie
req->addCookie(seq.sessionCookie.key(), seq.sessionCookie.value());
} // endif
client->sendRequest(
req,
[TEST_CTX, &seq, &p1](ReqResult res, const HttpResponsePtr &resp) {
REQUIRE(res == ReqResult::Ok);
REQUIRE(resp != nullptr);
CHECK(resp->getStatusCode() == HttpStatusCode::k200OK);
CHECK(resp->contentType() == CT_APPLICATION_JSON);
auto json = resp->getJsonObject();
auto cookie = resp->getCookie("JSESSIONID");
LOG_INFO << "Client: cookie-value == " << cookie.value()
<< ", requested value == " << (*seq.i)
<< ", new value == "
<< (*json)["new value"].asString()
<< ", received value == "
<< Cookie::convertSameSite2String(
cookie.getSameSite());
seq.sessionCookie = resp->getCookie("JSESSIONID");
CHECK(*seq.i ==
Cookie::convertSameSite2String(cookie.getSameSite()));
p1.set_value();
});
f1.get();
seq.i++;
}
}
+147
View File
@@ -0,0 +1,147 @@
#define DROGON_TEST_MAIN
#include <drogon/drogon_test.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/HttpClient.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <drogon/plugins/RealIpResolver.h>
#include <drogon/drogon.h>
#include <drogon/HttpTypes.h>
using namespace drogon;
DROGON_TEST(RealIpResolver)
{
auto client =
HttpClient::newHttpClient("http://127.0.0.1:8017",
HttpAppFramework::instance().getLoop());
auto newRequest = []() {
auto req = HttpRequest::newHttpRequest();
req->setPath("/RealIpController/my-ip");
return req;
};
// 1. No headers
{
auto req = newRequest();
client->sendRequest(
req, [TEST_CTX](ReqResult res, const HttpResponsePtr &resp) {
REQUIRE(res == ReqResult::Ok);
CHECK(resp->getStatusCode() == HttpStatusCode::k200OK);
CHECK(resp->contentType() == drogon::CT_TEXT_PLAIN);
CHECK(resp->body() == "127.0.0.1");
});
}
// 2. header only contains real ip
{
auto req = newRequest();
req->addHeader("x-forwarded-for", "1.1.1.1");
client->sendRequest(
req, [TEST_CTX](ReqResult res, const HttpResponsePtr &resp) {
REQUIRE(res == ReqResult::Ok);
CHECK(resp->getStatusCode() == HttpStatusCode::k200OK);
CHECK(resp->contentType() == drogon::CT_TEXT_PLAIN);
CHECK(resp->body() == "1.1.1.1");
});
}
// 3. Ip with port
{
auto req = newRequest();
req->addHeader("x-forwarded-for", "1.1.1.1:7777");
client->sendRequest(
req, [TEST_CTX](ReqResult res, const HttpResponsePtr &resp) {
REQUIRE(res == ReqResult::Ok);
CHECK(resp->getStatusCode() == HttpStatusCode::k200OK);
CHECK(resp->contentType() == drogon::CT_TEXT_PLAIN);
CHECK(resp->body() == "1.1.1.1");
});
}
// 3. multiple ips
{
auto req = newRequest();
req->addHeader("x-forwarded-for",
"2.2.2.2,1.1.1.1:7001, 172.16.0.100:7002,127.0.0.1");
client->sendRequest(
req, [TEST_CTX](ReqResult res, const HttpResponsePtr &resp) {
REQUIRE(res == ReqResult::Ok);
CHECK(resp->getStatusCode() == HttpStatusCode::k200OK);
CHECK(resp->contentType() == drogon::CT_TEXT_PLAIN);
CHECK(resp->body() == "1.1.1.1");
});
}
// 4. Ignore error in header
{
auto req = newRequest();
req->addHeader("x-forwarded-for",
"2.2.2.2,1.1.1.1:7001, wrong,9.9.9.9");
client->sendRequest(
req, [TEST_CTX](ReqResult res, const HttpResponsePtr &resp) {
REQUIRE(res == ReqResult::Ok);
CHECK(resp->getStatusCode() == HttpStatusCode::k200OK);
CHECK(resp->contentType() == drogon::CT_TEXT_PLAIN);
CHECK(resp->body() == "1.1.1.1");
});
}
};
class RealIpController : public drogon::HttpController<RealIpController>
{
public:
METHOD_LIST_BEGIN
METHOD_ADD(RealIpController::getRealIp, "/my-ip", Get);
METHOD_LIST_END
void getRealIp(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto addr = req->attributes()->get<trantor::InetAddress>("real-ip");
auto resp = HttpResponse::newHttpResponse();
resp->setContentTypeCode(drogon::CT_TEXT_PLAIN);
resp->setBody(addr.toIp());
callback(resp);
}
};
// -- main
int main(int argc, char **argv)
{
trantor::Logger::setLogLevel(trantor::Logger::kInfo);
std::promise<void> p1;
std::future<void> f1 = p1.get_future();
std::stringstream ss;
ss << R"({
"listeners": [
{
"address": "0.0.0.0",
"port": 8017
}
],
"plugins": [
{
"name": "drogon::plugin::RealIpResolver",
"config": {
"trust_ips": ["127.0.0.1", "172.16.0.0/12", "9.9.9.9/32"],
"from_header": "x-forwarded-for",
"attribute_key": "real-ip"
}
}
]
})";
Json::Value config;
ss >> config;
std::thread thr([&]() {
app().loadConfigJson(config);
app().getLoop()->queueInLoop([&p1]() { p1.set_value(); });
app().run();
});
f1.get();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
int testStatus = test::run(argc, argv);
app().getLoop()->queueInLoop([]() { app().quit(); });
thr.join();
return testStatus;
}
@@ -0,0 +1,92 @@
#include <drogon/HttpClient.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/drogon_test.h>
#include <string>
#include <iostream>
#include <atomic>
using namespace drogon;
static int counter = -1;
DROGON_TEST(HttpPipeliningTest)
{
auto client = HttpClient::newHttpClient("127.0.0.1", 8848);
client->setPipeliningDepth(64);
auto request1 = HttpRequest::newHttpRequest();
request1->setPath("/pipe");
request1->setMethod(Head);
client->sendRequest(
request1, [TEST_CTX](ReqResult r, const HttpResponsePtr &resp) {
REQUIRE(r == ReqResult::Ok);
auto counterHeader = resp->getHeader("counter");
int c = atoi(counterHeader.data());
if (c <= counter)
FAIL("The response was received in the wrong order!");
else
SUCCESS();
counter = c;
REQUIRE(resp->body().empty());
});
auto request2 = HttpRequest::newHttpRequest();
request2->setPath("/drogon.jpg");
client->sendRequest(request2,
[TEST_CTX](ReqResult r, const HttpResponsePtr &resp) {
REQUIRE(r == ReqResult::Ok);
REQUIRE(resp->getBody().length() == 44618UL);
});
for (int i = 0; i < 19; ++i)
{
client->sendRequest(
request1, [TEST_CTX](ReqResult r, const HttpResponsePtr &resp) {
REQUIRE(r == ReqResult::Ok);
auto counterHeader = resp->getHeader("counter");
int c = atoi(counterHeader.data());
if (c <= counter)
FAIL("The response was received in the wrong order!");
else
SUCCESS();
counter = c;
REQUIRE(resp->body().empty());
});
}
}
DROGON_TEST(HttpPipeliningStrangeTest1)
{
auto client = HttpClient::newHttpClient("127.0.0.1", 8848);
client->setPipeliningDepth(64);
for (int i = 0; i < 4; ++i)
{
auto request = HttpRequest::newHttpRequest();
request->setPath("/pipe/strange-1");
request->setBody(std::to_string(i));
client->sendRequest(request,
[TEST_CTX, i](ReqResult r,
const HttpResponsePtr &resp) {
REQUIRE(r == ReqResult::Ok);
REQUIRE(resp->body() == std::to_string(i));
});
}
}
DROGON_TEST(HttpPipeliningStrangeTest2)
{
auto client = HttpClient::newHttpClient("127.0.0.1", 8848);
client->setPipeliningDepth(64);
for (int i = 0; i < 6; ++i)
{
auto request = HttpRequest::newHttpRequest();
request->setPath("/pipe/strange-2");
request->setBody(std::to_string(i));
client->sendRequest(request,
[TEST_CTX, i](ReqResult r,
const HttpResponsePtr &resp) {
REQUIRE(r == ReqResult::Ok);
REQUIRE(resp->body() == std::to_string(i));
});
}
}
@@ -0,0 +1,71 @@
#include <drogon/WebSocketClient.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/drogon_test.h>
#include <iostream>
using namespace drogon;
using namespace std::chrono_literals;
struct DataPack
{
WebSocketClientPtr wsPtr;
std::shared_ptr<drogon::test::CaseBase> TEST_CTX;
};
static const int kClientCount = 100;
DROGON_TEST(MultipleWsTest)
{
for (size_t i = 0; i < kClientCount; i++)
{
auto wsPtr = WebSocketClient::newWebSocketClient("127.0.0.1", 8848);
auto pack = std::make_shared<DataPack *>(new DataPack{wsPtr, TEST_CTX});
wsPtr->setMessageHandler(
[pack, i](const std::string &message,
const WebSocketClientPtr &wsPtr,
const WebSocketMessageType &type) mutable {
if (pack == nullptr)
return;
auto TEST_CTX = (*pack)->TEST_CTX;
CHECK((type == WebSocketMessageType::Text ||
type == WebSocketMessageType::Pong));
if (type == WebSocketMessageType::Pong && TEST_CTX != nullptr)
{
auto wsPtr = (*pack)->wsPtr;
// Check if the correct connection got the result
wsPtr->stop();
CHECK(message == std::to_string(i));
delete *pack;
pack = nullptr;
}
});
auto req = HttpRequest::newHttpRequest();
req->setPath("/chat");
wsPtr->connectToServer(
req,
[pack, i](ReqResult r,
const HttpResponsePtr &resp,
const WebSocketClientPtr &wsPtr) mutable {
auto TEST_CTX = (*pack)->TEST_CTX;
CHECK((*pack)->wsPtr == wsPtr);
CHECK(r == ReqResult::Ok);
if (r != ReqResult::Ok)
{
wsPtr->stop();
delete *pack;
pack = nullptr;
}
REQUIRE(wsPtr != nullptr);
REQUIRE(resp != nullptr);
wsPtr->getConnection()->setPingMessage(std::to_string(i), 1.5s);
wsPtr->getConnection()->send("hello!");
CHECK(wsPtr->getConnection()->connected());
TEST_CTX = {};
});
}
}
@@ -0,0 +1,143 @@
#include <drogon/HttpClient.h>
#include <drogon/drogon_test.h>
#include <trantor/net/TcpClient.h>
#include <chrono>
#include <string>
#include <iostream>
#include <fstream>
using namespace drogon;
template <typename T>
void checkStreamRequest(T &&TEST_CTX,
trantor::EventLoop *loop,
const trantor::InetAddress &addr,
const std::vector<std::string_view> &dataToSend,
std::string_view expectedResp)
{
auto tcpClient = std::make_shared<trantor::TcpClient>(loop, addr, "test");
std::promise<void> promise;
auto respString = std::make_shared<std::string>();
tcpClient->setMessageCallback(
[respString](const trantor::TcpConnectionPtr &conn,
trantor::MsgBuffer *buf) {
respString->append(buf->read(buf->readableBytes()));
});
tcpClient->setConnectionCallback(
[TEST_CTX, &promise, respString, dataToSend, expectedResp](
const trantor::TcpConnectionPtr &conn) {
if (conn->disconnected())
{
LOG_INFO << "Disconnected from server";
CHECK(respString->substr(0, expectedResp.size()) ==
expectedResp);
promise.set_value();
return;
}
LOG_INFO << "Connected to server";
CHECK(conn->connected());
for (auto &data : dataToSend)
{
conn->send(data.data(), data.size());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
conn->shutdown();
});
tcpClient->connect();
promise.get_future().wait();
}
DROGON_TEST(RequestStreamTest)
{
const std::string ip = "127.0.0.1";
const uint16_t port = 8848;
auto client = HttpClient::newHttpClient(ip, port);
HttpRequestPtr req;
bool enabled = false;
req = HttpRequest::newHttpRequest();
req->setPath("/stream_status");
{
auto [res, resp] = client->sendRequest(req);
REQUIRE(res == ReqResult::Ok);
REQUIRE(resp->statusCode() == k200OK);
if (resp->body() == "enabled")
{
enabled = true;
}
else
{
LOG_INFO << "Server does not enable request stream.";
}
}
req = HttpRequest::newHttpRequest();
req->setPath("/stream_chunk");
req->setMethod(Post);
req->setBody("1234567890");
client->sendRequest(req,
[TEST_CTX, enabled](ReqResult r,
const HttpResponsePtr &resp) {
REQUIRE(r == ReqResult::Ok);
if (enabled)
{
CHECK(resp->statusCode() == k200OK);
CHECK(resp->body() == "1234567890");
}
else
{
CHECK(resp->statusCode() == k400BadRequest);
CHECK(resp->body() == "no stream");
}
});
if (!enabled)
{
return;
}
LOG_INFO << "Test request stream";
std::string filePath = "./中文.txt";
std::ifstream file(filePath);
std::stringstream content;
REQUIRE(file.is_open());
content << file.rdbuf();
req = HttpRequest::newFileUploadRequest({UploadFile{filePath}});
req->setPath("/stream_upload_echo");
req->setMethod(Post);
client->sendRequest(req,
[TEST_CTX,
content = content.str()](ReqResult r,
const HttpResponsePtr &resp) {
CHECK(r == ReqResult::Ok);
CHECK(resp->statusCode() == k200OK);
CHECK(resp->body() == content);
});
checkStreamRequest(TEST_CTX,
client->getLoop(),
trantor::InetAddress{ip, port},
// Good request
{"POST /stream_chunk HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n\r\n",
"1\r\nz\r\n",
"2\r\nzz\r\n0\r\n\r\n"},
// Good response
"HTTP/1.1 200 OK\r\n");
checkStreamRequest(TEST_CTX,
client->getLoop(),
trantor::InetAddress{ip, port},
// Bad request
{"POST /stream_chunk HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n\r\n",
"1\r\nz\r\n",
"1\r\nzz\r\n",
"0\r\n\r\n"},
// Bad response
"HTTP/1.1 400 Bad Request\r\n");
}
@@ -0,0 +1,70 @@
#include <drogon/WebSocketClient.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/drogon_test.h>
#include <iostream>
using namespace drogon;
using namespace std::chrono_literals;
struct DataPack
{
WebSocketClientPtr wsPtr;
std::shared_ptr<drogon::test::CaseBase> TEST_CTX;
};
static WebSocketClientPtr wsPtr_;
DROGON_TEST(WebSocketTest)
{
wsPtr_ = WebSocketClient::newWebSocketClient("127.0.0.1", 8848);
auto pack = std::make_shared<DataPack *>(new DataPack{wsPtr_, TEST_CTX});
auto req = HttpRequest::newHttpRequest();
req->setPath("/chat");
wsPtr_->setMessageHandler([pack](const std::string &message,
const WebSocketClientPtr &wsPtr,
const WebSocketMessageType &type) mutable {
if (pack == nullptr)
return;
auto TEST_CTX = (*pack)->TEST_CTX;
if (type == WebSocketMessageType::Pong)
{
auto wsPtr = (*pack)->wsPtr;
wsPtr_->stop();
CHECK(message.empty());
delete *pack;
pack = nullptr;
}
else
{
CHECK(type == WebSocketMessageType::Text);
}
});
wsPtr_->connectToServer(req,
[pack](ReqResult r,
const HttpResponsePtr &resp,
const WebSocketClientPtr &wsPtr) mutable {
auto TEST_CTX = (*pack)->TEST_CTX;
CHECK((*pack)->wsPtr == wsPtr);
if (r != ReqResult::Ok)
{
wsPtr_->stop();
wsPtr_.reset();
delete *pack;
pack = nullptr;
}
REQUIRE(r == ReqResult::Ok);
REQUIRE(wsPtr != nullptr);
REQUIRE(resp != nullptr);
wsPtr->getConnection()->setPingMessage("", 1s);
wsPtr->getConnection()->send("hello!");
CHECK(wsPtr->getConnection()->connected());
// Drop the testing context as WS controllers
// stores the lambda and never release it.
// Causing a dead lock later.
TEST_CTX = {};
pack.reset();
});
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
#include "BeginAdviceTest.h"
std::string BeginAdviceTest::content_ = "Default content";
void BeginAdviceTest::asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody(content_);
callback(resp);
}
@@ -0,0 +1,31 @@
#pragma once
#include <drogon/HttpSimpleController.h>
#include <string>
using namespace drogon;
class BeginAdviceTest : public drogon::HttpSimpleController<BeginAdviceTest>
{
public:
void asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) override;
PATH_LIST_BEGIN
// list path definitions here;
// PATH_ADD("/path","filter1","filter2",...);
PATH_ADD("/test_begin_advice", Get);
PATH_LIST_END
BeginAdviceTest()
{
LOG_DEBUG << "BeginAdviceTest constructor";
}
static void setContent(const std::string &content)
{
content_ = content;
}
private:
static std::string content_;
};
@@ -0,0 +1,12 @@
//
// Created by wanchen.he on 2022/8/16.
//
#include "CoroFilter.h"
Task<HttpResponsePtr> CoroFilter::doFilter(const HttpRequestPtr& req)
{
int secs = std::stoi(req->getParameter("secs"));
co_await sleepCoro(trantor::EventLoop::getEventLoopOfCurrentThread(), secs);
co_return {};
}
@@ -0,0 +1,18 @@
//
// Created by wanchen.he on 2022/8/16.
//
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
class CoroFilter : public drogon::HttpCoroFilter<CoroFilter>
{
public:
Task<HttpResponsePtr> doFilter(const HttpRequestPtr &req) override;
CoroFilter()
{
LOG_DEBUG << "CoroFilter constructor";
}
};
@@ -0,0 +1,12 @@
#include "CustomCtrl.h"
// add definition of your processing function here
void CustomCtrl::hello(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &userName) const
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody("<P>" + greetings_ + ", " + userName + "</P>");
callback(resp);
}
@@ -0,0 +1,26 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
class CustomCtrl : public drogon::HttpController<CustomCtrl, false>
{
public:
METHOD_LIST_BEGIN
// use METHOD_ADD to add your custom processing function here;
METHOD_ADD(CustomCtrl::hello,
"/{userName}",
Get,
"CustomHeaderFilter"); // path is /customctrl/{arg1}
METHOD_LIST_END
explicit CustomCtrl(const std::string &greetings) : greetings_(greetings)
{
}
void hello(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &userName) const;
private:
std::string greetings_;
};
@@ -0,0 +1,25 @@
/**
*
* CustomHeaderFilter.cc
*
*/
#include "CustomHeaderFilter.h"
using namespace drogon;
void CustomHeaderFilter::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if (req->getHeader(field_) == value_)
{
// Passed
fccb();
return;
}
// Check failed
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k500InternalServerError);
fcb(res);
}
@@ -0,0 +1,27 @@
/**
*
* CustomHeaderFilter.h
*
*/
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
class CustomHeaderFilter : public HttpFilter<CustomHeaderFilter, false>
{
public:
CustomHeaderFilter(const std::string &field, const std::string &value)
: field_(field), value_(value)
{
}
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
private:
std::string field_;
std::string value_;
};
@@ -0,0 +1,217 @@
#include "DigestAuthFilter.h"
#include <drogon/utils/Utilities.h>
#include <algorithm>
#include <cctype>
#include <string>
std::string method2String(HttpMethod m)
{
switch (m)
{
case Get:
return "GET";
case Post:
return "POST";
case Head:
return "HEAD";
case Put:
return "PUT";
case Delete:
return "DELETE";
case Options:
return "OPTIONS";
case Patch:
return "PATCH";
default:
return "INVALID";
}
}
std::string toLower(const std::string &in)
{
std::string out = in;
std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) {
return tolower(c);
});
return out;
}
bool DigestAuthFilter::isEndOfAttributeName(size_t pos,
size_t len,
const char *data)
{
if (pos >= len)
return true;
if (isspace(static_cast<unsigned char>(data[pos])))
return true;
// The reason for this complexity is that some attributes may contain
// trailing equal signs (like base64 tokens in Negotiate auth headers)
if ((pos + 1 < len) && (data[pos] == '=') &&
!isspace(static_cast<unsigned char>(data[pos + 1])) &&
(data[pos + 1] != '='))
{
return true;
}
return false;
}
void DigestAuthFilter::httpParseAttributes(const char *data,
size_t len,
HttpAttributeList &attributes)
{
size_t pos = 0;
while (true)
{
// Skip leading whitespace
while ((pos < len) && isspace(static_cast<unsigned char>(data[pos])))
{
++pos;
}
// End of attributes?
if (pos >= len)
return;
// Find end of attribute name
size_t start = pos;
while (!isEndOfAttributeName(pos, len, data))
{
++pos;
}
HttpAttribute attribute;
attribute.first.assign(data + start, data + pos);
// Attribute has value?
if ((pos < len) && (data[pos] == '='))
{
++pos; // Skip '='
// Check if quoted value
if ((pos < len) && (data[pos] == '"'))
{
while (++pos < len)
{
if (data[pos] == '"')
{
++pos;
break;
}
if ((data[pos] == '\\') && (pos + 1 < len))
++pos;
attribute.second.append(1, data[pos]);
}
}
else
{
while ((pos < len) &&
!isspace(static_cast<unsigned char>(data[pos])) &&
(data[pos] != ','))
{
attribute.second.append(1, data[pos++]);
}
}
}
attributes.push_back(attribute);
if ((pos < len) && (data[pos] == ','))
++pos; // Skip ','
}
}
bool DigestAuthFilter::httpHasAttribute(const HttpAttributeList &attributes,
const std::string &name,
std::string *value)
{
for (HttpAttributeList::const_iterator it = attributes.begin();
it != attributes.end();
++it)
{
if (it->first == name)
{
if (value)
{
*value = it->second;
}
return true;
}
}
return false;
}
DigestAuthFilter::DigestAuthFilter(
const std::map<std::string, std::string> &credentials,
const std::string &realm,
const std::string &opaque)
: credentials(credentials), realm(realm), opaque(opaque)
{
}
void DigestAuthFilter::doFilter(const HttpRequestPtr &req,
FilterCallback &&cb,
FilterChainCallback &&ccb)
{
if (!req->session())
{
// no session support by framework,pls enable session
auto resp = HttpResponse::newNotFoundResponse();
cb(resp);
return;
}
auto auth_header = req->getHeader("Authorization");
if (!auth_header.empty())
{
HttpAttributeList att_list;
httpParseAttributes(auth_header.c_str(), auth_header.size(), att_list);
std::string username, realm, nonce, uri, opaque, response;
if (httpHasAttribute(att_list, "username", &username) &&
httpHasAttribute(att_list, "realm", &realm) &&
httpHasAttribute(att_list, "nonce", &nonce) &&
httpHasAttribute(att_list, "uri", &uri) &&
httpHasAttribute(att_list, "opaque", &opaque) &&
httpHasAttribute(att_list, "response", &response))
{
if (credentials.find(username) != credentials.end())
{
std::string A1 =
username + ":" + realm + ":" + credentials.at(username);
std::string A2 = method2String(req->getMethod()) + ":" + uri;
std::string A1_middle_A2 = toLower(utils::getMd5(A1)) + ":" +
nonce + ":" +
toLower(utils::getMd5(A2));
std::string calculated_response =
toLower(utils::getMd5(A1_middle_A2));
if (response == calculated_response)
{
// Passed
ccb();
return;
}
else
{
LOG_DEBUG << "invalid response " << response
<< ", calculated " << calculated_response;
}
}
else
{
LOG_DEBUG << "invalid username " << username;
}
}
else
{
LOG_DEBUG << "missing attributes in WWW-Authenticate header"
<< auth_header;
}
}
// not Passed
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k401Unauthorized);
resp->addHeader("WWW-Authenticate",
" Digest realm=\"" + realm + "\", nonce=\"" +
toLower(utils::getMd5(std::to_string(time(0)))) +
"\", opaque=\"" + opaque + "\"");
cb(resp);
return;
}
@@ -0,0 +1,33 @@
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
typedef std::pair<std::string, std::string> HttpAttribute;
typedef std::vector<HttpAttribute> HttpAttributeList;
typedef std::map<std::string /*username*/, std::string /*password*/>
CredentialsMap;
class DigestAuthFilter : public drogon::HttpFilter<DigestAuthFilter, false>
{
const std::map<std::string, std::string> credentials;
const std::string realm;
const std::string opaque;
static bool isEndOfAttributeName(size_t pos, size_t len, const char *data);
static void httpParseAttributes(const char *data,
size_t len,
HttpAttributeList &attributes);
static bool httpHasAttribute(const HttpAttributeList &attributes,
const std::string &name,
std::string *value);
public:
explicit DigestAuthFilter(const CredentialsMap &credentials,
const std::string &realm,
const std::string &opaque);
void doFilter(const HttpRequestPtr &req,
FilterCallback &&cb,
FilterChainCallback &&ccb) override;
};
@@ -0,0 +1,19 @@
/**
*
* DoNothingPlugin.cc
*
*/
#include "DoNothingPlugin.h"
using namespace drogon;
void DoNothingPlugin::initAndStart(const Json::Value &config)
{
/// Initialize and start the plugin
}
void DoNothingPlugin::shutdown()
{
/// Shutdown the plugin
}
@@ -0,0 +1,26 @@
/**
*
* DoNothingPlugin.h
*
*/
#pragma once
#include <drogon/plugins/Plugin.h>
using namespace drogon;
class DoNothingPlugin : public Plugin<DoNothingPlugin>
{
public:
DoNothingPlugin()
{
}
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
void initAndStart(const Json::Value &config) override;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
void shutdown() override;
};
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>File upload</title>
<script type="text/javascript">
var xhr;
//File uploading method
function UpladFile() {
var fileObj = document.getElementById("file").files[0]; // js get file object
var url = "/api/attachment/upload";
var form = new FormData(); // FormData object
form.append("file", fileObj); // File object
xhr = new XMLHttpRequest(); // XMLHttpRequest object
xhr.open("post", url, true); //post
xhr.onload = uploadComplete;
xhr.onerror = uploadFailed;
xhr.upload.onprogress = progressFunction;
xhr.upload.onloadstart = function(){
ot = new Date().getTime();
oloaded = 0;
};
xhr.send(form);
}
function uploadComplete(evt) {
var data = JSON.parse(evt.target.responseText);
if(data.result == "ok") {
alert("Uploaded successfully!");
}else{
alert("Upload failed!");
}
}
function uploadFailed(evt) {
alert("Upload failed!");
}
function cancleUploadFile(){
xhr.abort();
}
function progressFunction(evt) {
var progressBar = document.getElementById("progressBar");
var percentageDiv = document.getElementById("percentage");
if (evt.lengthComputable) {//
progressBar.max = evt.total;
progressBar.value = evt.loaded;
percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%";
}
var time = document.getElementById("time");
var nt = new Date().getTime();
var pertime = (nt-ot)/1000;
ot = new Date().getTime();
var perload = evt.loaded - oloaded;
oloaded = evt.loaded;
var speed = perload/pertime;
var bspeed = speed;
var units = 'b/s';
if(speed/1024>1){
speed = speed/1024;
units = 'k/s';
}
if(speed/1024>1){
speed = speed/1024;
units = 'M/s';
}
speed = speed.toFixed(1);
var resttime = ((evt.total-evt.loaded)/bspeed).toFixed(1);
time.innerHTML = ',Speed: '+speed+units+', the remaining time: '+resttime+'s';
if(bspeed==0) time.innerHTML = 'Upload cancelled';
}
</script>
</head>
<body>
<progress id="progressBar" value="0" max="100" style="width: 300px;"></progress>
<span id="percentage"></span><span id="time"></span>
<br /><br />
<input type="file" id="file" name="myfile" />
<input type="button" onclick="UpladFile()" value="Upload" />
<input type="button" onclick="cancleUploadFile()" value="Cancel" />
</body>
</html>
@@ -0,0 +1,14 @@
#include "ForwardCtrl.h"
void ForwardCtrl::asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
req->setPath("/repos/an-tao/drogon/git/refs/heads/master");
app().forward(
req,
[callback = std::move(callback)](const HttpResponsePtr &resp) {
callback(resp);
},
"https://api.github.com");
}
@@ -0,0 +1,15 @@
#pragma once
#include <drogon/HttpSimpleController.h>
using namespace drogon;
class ForwardCtrl : public drogon::HttpSimpleController<ForwardCtrl>
{
public:
void asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) override;
PATH_LIST_BEGIN
// list path definitions here;
PATH_ADD("/forward", Get);
PATH_LIST_END
};
@@ -0,0 +1,24 @@
#include "JsonTestController.h"
#include <json/json.h>
void JsonTestController::asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
Json::Value json;
json["path"] = "json";
json["name"] = "json test";
Json::Value array;
for (int i = 0; i < 5; ++i)
{
Json::Value user;
user["id"] = i;
user["name"] = "none";
user["c_name"] = "张三";
array.append(user);
}
json["rows"] = array;
auto resp = HttpResponse::newHttpJsonResponse(json);
assert(resp->jsonObject().get());
callback(resp);
}
@@ -0,0 +1,18 @@
#pragma once
#include <drogon/HttpSimpleController.h>
using namespace drogon;
class JsonTestController
: public drogon::HttpSimpleController<JsonTestController>
{
public:
// TestController(){}
void asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) override;
PATH_LIST_BEGIN
PATH_ADD("/json", Get, "drogon::LocalHostFilter");
PATH_LIST_END
};
@@ -0,0 +1,14 @@
#include "ListParaCtl.h"
void ListParaCtl::asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
// write your application logic here
HttpViewData data;
data.insert("title", "list parameters");
data.insert("parameters", req->getParameters());
auto res =
drogon::HttpResponse::newHttpViewResponse("ListParaView.csp", data);
callback(res);
}
@@ -0,0 +1,16 @@
#pragma once
#include <drogon/HttpSimpleController.h>
using namespace drogon;
class ListParaCtl : public drogon::HttpSimpleController<ListParaCtl>
{
public:
void asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) override;
PATH_LIST_BEGIN
// list path definitions here;
// PATH_ADD("/path","filter1","filter2",...);
PATH_ADD("/listpara", Get);
PATH_LIST_END
};
@@ -0,0 +1,33 @@
<%inc
#include <drogon/HttpRequest.h>
%>
<!DOCTYPE html>
<html>
<%c++
auto para=@@.get<SafeStringMap<std::string>>("parameters");
%>
<head>
<meta charset="UTF-8">
<title>[[ title ]]</title>
</head>
<body>
<%view header %>
<%c++ if(para.size()>0){%>
<H1>Parameters</H1>
<table border="1">
<tr>
<th>name</th>
<th>value</th>
</tr>
<%c++ for(auto iter:para){%>
<tr>
<td>{%iter.first%}</td>
<td><%c++ $$<<iter.second;%></td>
</tr>
<%c++}%>
</table>
<%c++ }else{%>
<H1>no parameter</H1>
<%c++}%>
</body>
</html>
@@ -0,0 +1,56 @@
#include "MethodTest.h"
static void makeGetRespose(
const std::function<void(const HttpResponsePtr &)> &callback)
{
callback(drogon::HttpResponse::newHttpJsonResponse("GET"));
}
static void makePostRespose(
const std::function<void(const HttpResponsePtr &)> &callback)
{
callback(drogon::HttpResponse::newHttpJsonResponse("POST"));
}
void MethodTest::get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
LOG_DEBUG;
makeGetRespose(callback);
}
void MethodTest::post(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string str)
{
LOG_DEBUG << str;
makePostRespose(callback);
}
void MethodTest::getReg(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string regStr)
{
LOG_DEBUG << regStr;
makeGetRespose(callback);
}
void MethodTest::postReg(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string regStr,
std::string str)
{
LOG_DEBUG << regStr;
LOG_DEBUG << str;
makePostRespose(callback);
}
void MethodTest::postRegex(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string regStr)
{
LOG_DEBUG << regStr;
makePostRespose(callback);
}
@@ -0,0 +1,37 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
class MethodTest : public drogon::HttpController<MethodTest>
{
public:
METHOD_LIST_BEGIN
ADD_METHOD_TO(MethodTest::get, "/api/method/test", Get);
ADD_METHOD_TO(MethodTest::post, "/api/method/test?test={}", Post);
ADD_METHOD_TO(MethodTest::getReg, "/api/method/{}/test", Get);
ADD_METHOD_TO(MethodTest::postReg, "/api/method/{}/test?test={}", Post);
ADD_METHOD_VIA_REGEX(MethodTest::getReg,
"/api/method/regex/(.*)/test",
Get);
ADD_METHOD_VIA_REGEX(MethodTest::postRegex,
"/api/method/regex/(.*)/test",
Post);
METHOD_LIST_END
void get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void post(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string str);
void getReg(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string regStr);
void postReg(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string regStr,
std::string str);
void postRegex(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string regStr);
};
@@ -0,0 +1,164 @@
#include <drogon/HttpController.h>
#include <drogon/HttpMiddleware.h>
using namespace drogon;
class Middleware1 : public drogon::HttpMiddleware<Middleware1>
{
public:
Middleware1()
{
// do not omit constructor
void(0);
};
void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) override
{
auto ptr = std::make_shared<std::string>("1");
req->attributes()->insert("test-middleware", ptr);
nextCb([req, ptr, mcb = std::move(mcb)](const HttpResponsePtr &resp) {
ptr->append("1");
resp->setBody(*ptr);
mcb(resp);
});
}
};
class Middleware2 : public drogon::HttpMiddleware<Middleware2>
{
public:
Middleware2()
{
// do not omit constructor
void(0);
};
void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) override
{
auto ptr = req->attributes()->get<std::shared_ptr<std::string>>(
"test-middleware");
ptr->append("2");
nextCb([req, ptr, mcb = std::move(mcb)](const HttpResponsePtr &resp) {
ptr->append("2");
resp->setBody(*ptr);
mcb(resp);
});
}
};
class Middleware3 : public drogon::HttpMiddleware<Middleware3>
{
public:
Middleware3()
{
// do not omit constructor
void(0);
};
void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) override
{
auto ptr = req->attributes()->get<std::shared_ptr<std::string>>(
"test-middleware");
ptr->append("3");
nextCb([req, ptr, mcb = std::move(mcb)](const HttpResponsePtr &resp) {
ptr->append("3");
resp->setBody(*ptr);
mcb(resp);
});
}
};
class MiddlewareBlock : public drogon::HttpMiddleware<MiddlewareBlock>
{
public:
MiddlewareBlock()
{
// do not omit constructor
void(0);
};
void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) override
{
auto ptr = req->attributes()->get<std::shared_ptr<std::string>>(
"test-middleware");
ptr->append("block");
mcb(HttpResponse::newHttpResponse());
}
};
#if defined(__cpp_impl_coroutine)
class MiddlewareCoro : public drogon::HttpCoroMiddleware<MiddlewareCoro>
{
public:
MiddlewareCoro()
{
// do not omit constructor
void(0);
};
Task<HttpResponsePtr> invoke(const HttpRequestPtr &req,
MiddlewareNextAwaiter &&nextAwaiter) override
{
auto ptr = req->attributes()->get<std::shared_ptr<std::string>>(
"test-middleware");
ptr->append("coro");
auto resp = co_await nextAwaiter;
ptr->append("coro");
resp->setBody(*ptr);
co_return resp;
}
};
#endif
class MiddlewareTest : public drogon::HttpController<MiddlewareTest>
{
public:
METHOD_LIST_BEGIN
ADD_METHOD_TO(MiddlewareTest::handleRequest,
"/test-middleware",
Get,
"Middleware1",
"Middleware2",
"Middleware3",
"Middleware4");
ADD_METHOD_TO(MiddlewareTest::handleRequest,
"/test-middleware-block",
Get,
"Middleware1",
"Middleware2",
"MiddlewareBlock",
"Middleware3");
#if defined(__cpp_impl_coroutine)
ADD_METHOD_TO(MiddlewareTest::handleRequest,
"/test-middleware-coro",
Get,
"Middleware1",
"Middleware2",
"MiddlewareCoro");
#endif
METHOD_LIST_END
void handleRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
req->attributes()
->get<std::shared_ptr<std::string>>("test-middleware")
->append("test");
callback(HttpResponse::newHttpResponse());
}
};
@@ -0,0 +1,146 @@
#include "PipeliningTest.h"
#include <trantor/net/EventLoop.h>
#include <atomic>
#include <mutex>
void PipeliningTest::normalPipe(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
static std::atomic<int> counter{0};
int c = counter.fetch_add(1);
if (c % 3 == 1)
{
auto resp = HttpResponse::newHttpResponse();
auto str = utils::formattedString("<P>the %dth response</P>", c);
resp->addHeader("counter", utils::formattedString("%d", c));
resp->setBody(std::move(str));
callback(resp);
return;
}
double delay = ((double)(10 - (c % 10))) / 10.0;
if (c % 3 == 2)
{
// call the callback in another thread.
drogon::app().getLoop()->runAfter(delay, [c, callback]() {
auto resp = HttpResponse::newHttpResponse();
auto str = utils::formattedString("<P>the %dth response</P>", c);
resp->addHeader("counter", utils::formattedString("%d", c));
resp->setBody(std::move(str));
callback(resp);
});
return;
}
trantor::EventLoop::getEventLoopOfCurrentThread()->runAfter(
delay, [c, callback]() {
auto resp = HttpResponse::newHttpResponse();
auto str = utils::formattedString("<P>the %dth response</P>", c);
resp->addHeader("counter", utils::formattedString("%d", c));
resp->setBody(std::move(str));
callback(resp);
});
}
// Receive 1, cache 1
// Receive 2, send 1 send 2
void PipeliningTest::strangePipe1(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
static std::mutex mtx;
static std::vector<
std::pair<std::function<void(const HttpResponsePtr &)>, std::string>>
callbacks;
LOG_INFO << "Receive request " << req->body();
std::function<void(const HttpResponsePtr &)> cb1;
std::string body1;
std::function<void(const HttpResponsePtr &)> cb2;
std::string body2;
{
std::lock_guard<std::mutex> lock(mtx);
if (callbacks.empty())
{
callbacks.emplace_back(std::move(callback), req->getBody());
return;
}
auto item = std::move(callbacks.back());
callbacks.pop_back();
cb1 = std::move(item.first);
body1 = std::move(item.second);
cb2 = std::move(callback);
body2 = std::string{req->body()};
}
if (cb1)
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody(body1);
cb1(resp);
}
if (cb2)
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody(body2);
cb2(resp);
}
}
// Receive 1, cache 1
// Receive 2, send 1 cache 2
// Receive 3, send 2 send 3
void PipeliningTest::strangePipe2(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
static std::mutex mtx;
static std::vector<
std::pair<std::function<void(const HttpResponsePtr &)>, std::string>>
callbacks;
static uint64_t idx{0};
LOG_INFO << "Receive request " << req->body();
std::function<void(const HttpResponsePtr &)> cb1;
std::string body1;
std::function<void(const HttpResponsePtr &)> cb2;
std::string body2;
{
std::lock_guard<std::mutex> lock(mtx);
++idx;
if (idx % 3 == 1)
{
assert(callbacks.empty());
callbacks.emplace_back(std::move(callback), req->getBody());
return;
}
assert(callbacks.size() == 1);
if (idx % 3 == 2)
{
auto item = std::move(callbacks.back());
cb1 = std::move(item.first);
body1 = std::move(item.second);
callbacks.pop_back();
callbacks.emplace_back(std::move(callback), req->getBody());
}
else
{
auto item = std::move(callbacks.back());
cb1 = std::move(item.first);
body1 = std::move(item.second);
callbacks.pop_back();
cb2 = std::move(callback);
body2 = std::string{req->body()};
}
}
if (cb1)
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody(body1);
cb1(resp);
}
if (cb2)
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody(body2);
cb2(resp);
}
}
@@ -0,0 +1,33 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
// class PipeliningTest : public drogon::HttpSimpleController<PipeliningTest>
//{
// public:
// virtual void asyncHandleHttpRequest(
// const HttpRequestPtr &req,
// std::function<void(const HttpResponsePtr &)> &&callback) override;
// PATH_LIST_BEGIN
// // list path definitions here;
// PATH_ADD("/pipe", Get);
// PATH_LIST_END
// };
class PipeliningTest : public drogon::HttpController<PipeliningTest>
{
public:
METHOD_LIST_BEGIN
ADD_METHOD_TO(PipeliningTest::normalPipe, "/pipe", Get);
ADD_METHOD_TO(PipeliningTest::strangePipe1, "/pipe/strange-1", Get);
ADD_METHOD_TO(PipeliningTest::strangePipe2, "/pipe/strange-2", Get);
METHOD_LIST_END
void normalPipe(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const;
void strangePipe1(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void strangePipe2(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
};
@@ -0,0 +1,39 @@
#include "RangeTestController.h"
#include <fstream>
size_t RangeTestController::fileSize_ = 10000 * 100; // 1e6 Bytes
RangeTestController::RangeTestController()
{
std::ofstream outfile("./range-test.txt", std::ios::out | std::ios::trunc);
for (int i = 0; i < 10000; ++i)
{
outfile.write(
"01234567890123456789"
"01234567890123456789"
"01234567890123456789"
"01234567890123456789"
"01234567890123456789",
100);
}
}
void RangeTestController::getFile(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
auto resp = HttpResponse::newFileResponse("./range-test.txt");
callback(resp);
}
void RangeTestController::getFileByRange(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
size_t offset,
size_t length) const
{
auto resp =
HttpResponse::newFileResponse("./range-test.txt", offset, length);
callback(resp);
}
@@ -0,0 +1,34 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
class RangeTestController : public drogon::HttpController<RangeTestController>
{
public:
METHOD_LIST_BEGIN
// path is /RangeTestController
METHOD_ADD(RangeTestController::getFile, "/", Get);
// path is /RangeTestController/{offset}/{length}
METHOD_ADD(RangeTestController::getFileByRange, "/{offset}/{length}", Get);
METHOD_LIST_END
RangeTestController();
void getFile(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const;
// We do not provide 'Range' header decoding, simply use path as range
// parameter.
void getFileByRange(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
size_t offset,
size_t length) const;
static size_t getFileSize()
{
return fileSize_;
}
private:
static size_t fileSize_;
};
@@ -0,0 +1,150 @@
#include <fstream>
#include <drogon/HttpController.h>
#include <drogon/HttpRequest.h>
#include <drogon/RequestStream.h>
using namespace drogon;
class RequestStreamTestCtrl : public HttpController<RequestStreamTestCtrl>
{
public:
METHOD_LIST_BEGIN
ADD_METHOD_TO(RequestStreamTestCtrl::stream_status, "/stream_status", Get);
ADD_METHOD_TO(RequestStreamTestCtrl::stream_chunk, "/stream_chunk", Post);
ADD_METHOD_TO(RequestStreamTestCtrl::stream_upload_echo,
"/stream_upload_echo",
Post);
METHOD_LIST_END
void stream_status(
const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
auto resp = HttpResponse::newHttpResponse();
if (app().isRequestStreamEnabled())
{
resp->setBody("enabled");
}
else
{
resp->setBody("not enabled");
}
callback(resp);
}
void stream_chunk(
const HttpRequestPtr &,
RequestStreamPtr &&stream,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
if (!stream)
{
LOG_INFO << "stream mode is not enabled";
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
resp->setBody("no stream");
callback(resp);
return;
}
auto respBody = std::make_shared<std::string>();
auto reader = RequestStreamReader::newReader(
[respBody](const char *data, size_t length) {
respBody->append(data, length);
},
[respBody, callback = std::move(callback)](std::exception_ptr ex) {
auto resp = HttpResponse::newHttpResponse();
if (ex)
{
try
{
std::rethrow_exception(std::move(ex));
}
catch (const std::exception &e)
{
LOG_ERROR << "stream error: " << e.what();
}
resp->setStatusCode(k400BadRequest);
resp->setBody("stream error");
callback(resp);
}
else
{
resp->setBody(*respBody);
callback(resp);
}
});
stream->setStreamReader(std::move(reader));
}
void stream_upload_echo(
const HttpRequestPtr &req,
RequestStreamPtr &&stream,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
assert(drogon::app().isRequestStreamEnabled() || !stream);
if (!stream)
{
LOG_INFO << "stream mode is not enabled";
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
resp->setBody("no stream");
callback(resp);
return;
}
if (req->contentType() != CT_MULTIPART_FORM_DATA)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
resp->setBody("should upload multipart");
callback(resp);
return;
}
struct Context
{
std::string firstFileContent;
size_t currentFileIndex_{0};
};
auto ctx = std::make_shared<Context>();
auto reader = RequestStreamReader::newMultipartReader(
req,
[ctx](MultipartHeader &&header) { ctx->currentFileIndex_++; },
[ctx](const char *data, size_t length) {
if (ctx->currentFileIndex_ == 1)
{
ctx->firstFileContent.append(data, length);
}
},
[ctx, callback = std::move(callback)](std::exception_ptr ex) {
auto resp = HttpResponse::newHttpResponse();
if (ex)
{
try
{
std::rethrow_exception(std::move(ex));
}
catch (const StreamError &e)
{
LOG_ERROR << "stream error: " << e.what();
}
catch (const std::exception &e)
{
LOG_ERROR << "multipart error: " << e.what();
}
resp->setStatusCode(k400BadRequest);
resp->setBody("error\n");
callback(resp);
}
else
{
resp->setBody(ctx->firstFileContent);
callback(resp);
}
});
stream->setStreamReader(std::move(reader));
}
};
@@ -0,0 +1,19 @@
#include "TestController.h"
using namespace example;
void TestController::asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
// write your application logic here
counter_->increment();
LOG_WARN << req->matchedPathPatternData();
LOG_DEBUG << "index=" << threadIndex_.getThreadData();
++(threadIndex_.getThreadData());
auto resp = HttpResponse::newHttpResponse();
resp->setContentTypeCodeAndCustomString(CT_TEXT_PLAIN,
"content-type: plaintext\r\n");
resp->setBody("<p>Hello, world!</p>");
resp->setExpiredTime(20);
callback(resp);
}
@@ -0,0 +1,43 @@
#pragma once
#include <drogon/HttpSimpleController.h>
#include <drogon/IOThreadStorage.h>
#include <drogon/utils/monitoring/Counter.h>
#include <drogon/utils/monitoring/Collector.h>
#include <drogon/plugins/PromExporter.h>
using namespace drogon;
namespace example
{
class TestController : public drogon::HttpSimpleController<TestController>
{
public:
void asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) override;
PATH_LIST_BEGIN
// list path definitions here;
// PATH_ADD("/path","filter1","filter2",...);
PATH_ADD("/", Get);
PATH_ADD("/Test", "nonFilter");
PATH_ADD("/tpost", Post, Options);
PATH_ADD("/slow", "TimeFilter", Get);
PATH_LIST_END
TestController()
{
LOG_DEBUG << "TestController constructor";
auto collector = std::make_shared<
drogon::monitoring::Collector<drogon::monitoring::Counter>>(
"test_counter",
"The counter for requests to the root url",
std::vector<std::string>());
counter_ = collector->metric(std::vector<std::string>());
collector->registerTo(
*app().getSharedPlugin<drogon::plugin::PromExporter>());
}
private:
drogon::IOThreadStorage<int> threadIndex_;
std::shared_ptr<drogon::monitoring::Counter> counter_;
};
} // namespace example
@@ -0,0 +1,34 @@
/**
*
* TestPlugin.cc
*
*/
#include "TestPlugin.h"
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
using namespace drogon;
void TestPlugin::initAndStart(const Json::Value &config)
{
/// Initialize and start the plugin
if (config.isNull())
LOG_DEBUG << "Configuration not defined";
interval_ = config.get("heartbeat_interval", 1).asInt();
workThread_ = std::thread([this]() {
while (!stop_)
{
LOG_DEBUG << "TestPlugin heartbeat!";
std::this_thread::sleep_for(std::chrono::seconds(interval_));
}
});
}
void TestPlugin::shutdown()
{
/// Shutdown the plugin
stop_ = true;
workThread_.join();
}
@@ -0,0 +1,31 @@
/**
*
* TestPlugin.h
*
*/
#pragma once
#include <drogon/plugins/Plugin.h>
using namespace drogon;
class TestPlugin : public Plugin<TestPlugin>
{
public:
TestPlugin()
{
}
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
void initAndStart(const Json::Value &config) override;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
void shutdown() override;
private:
std::thread workThread_;
bool stop_{false};
int interval_{0};
};
@@ -0,0 +1,19 @@
<%inc
#include <iostream>
%>
<%c++
std::cout<<"this is a Http backend rendering Test"<<std::endl;
%>
<!DOCTYPE html>
<html>
<%c++ std::string title=@@.get<std::string>("title");%>
<head>
<meta charset="UTF-8">
<title><%c++ $$<<title;%></title>
</head>
<body>
<footer>
<span>CopyRight@2017 All Rights Reserved</span>
</footer>
</body>
</html>
@@ -0,0 +1,12 @@
#include "TestViewCtl.h"
void TestViewCtl::asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
// write your application logic here
drogon::HttpViewData data;
data.insert("title", std::string("TestView"));
auto res = drogon::HttpResponse::newHttpViewResponse("TestView", data);
callback(res);
}
@@ -0,0 +1,17 @@
#pragma once
#include <drogon/HttpSimpleController.h>
using namespace drogon;
class TestViewCtl : public drogon::HttpSimpleController<TestViewCtl>
{
public:
void asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) override;
PATH_LIST_BEGIN
// list path definitions here;
// PATH_ADD("/path","filter1","filter2",...);
PATH_ADD("/view");
PATH_ADD("/", Post);
PATH_LIST_END
};
@@ -0,0 +1,48 @@
//
// Created by antao on 2018/5/22.
//
#include "TimeFilter.h"
#define VDate "visitDate"
void TimeFilter::doFilter(const HttpRequestPtr &req,
FilterCallback &&cb,
FilterChainCallback &&ccb)
{
trantor::Date now = trantor::Date::date();
if (!req->session())
{
// no session support by framework,pls enable session
auto resp = HttpResponse::newNotFoundResponse();
cb(resp);
return;
}
auto lastDate = req->session()->getOptional<trantor::Date>(VDate);
if (lastDate)
{
LOG_TRACE << "last:" << lastDate->toFormattedString(false);
req->session()->modify<trantor::Date>(VDate,
[now](trantor::Date &vdate) {
vdate = now;
});
LOG_TRACE << "update visitDate";
if (now > lastDate->after(10))
{
// 10 sec later can visit again;
ccb();
return;
}
else
{
Json::Value json;
json["result"] = "error";
json["message"] = "Access interval should be at least 10 seconds";
auto res = HttpResponse::newHttpJsonResponse(json);
cb(res);
return;
}
}
LOG_TRACE << "first visit,insert visitDate";
req->session()->insert(VDate, now);
ccb();
}
@@ -0,0 +1,21 @@
//
// Created by antao on 2018/5/22.
//
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
class TimeFilter : public drogon::HttpFilter<TimeFilter>
{
public:
void doFilter(const HttpRequestPtr &req,
FilterCallback &&cb,
FilterChainCallback &&ccb) override;
TimeFilter()
{
LOG_DEBUG << "TimeFilter constructor";
}
};
@@ -0,0 +1,47 @@
#include "WebSocketTest.h"
using namespace example;
struct Subscriber
{
std::string chatRoomName_;
drogon::SubscriberID id_;
};
void WebSocketTest::handleNewMessage(const WebSocketConnectionPtr &wsConnPtr,
std::string &&message,
const WebSocketMessageType &type)
{
// write your application logic here
LOG_DEBUG << "new websocket message:" << message;
if (type == WebSocketMessageType::Ping)
{
LOG_DEBUG << "recv a ping";
}
else if (type == WebSocketMessageType::Text)
{
auto &s = wsConnPtr->getContextRef<Subscriber>();
chatRooms_.publish(s.chatRoomName_, message);
}
}
void WebSocketTest::handleConnectionClosed(const WebSocketConnectionPtr &conn)
{
LOG_DEBUG << "websocket closed!";
auto &s = conn->getContextRef<Subscriber>();
chatRooms_.unsubscribe(s.chatRoomName_, s.id_);
}
void WebSocketTest::handleNewConnection(const HttpRequestPtr &req,
const WebSocketConnectionPtr &conn)
{
LOG_DEBUG << "new websocket connection!";
conn->send("haha!!!");
Subscriber s;
s.chatRoomName_ = req->getParameter("room_name");
s.id_ = chatRooms_.subscribe(s.chatRoomName_,
[conn](const std::string &topic,
const std::string &message) {
conn->send(message);
});
conn->setContext(std::make_shared<Subscriber>(std::move(s)));
}
@@ -0,0 +1,24 @@
#pragma once
#include <drogon/WebSocketController.h>
#include <drogon/PubSubService.h>
using namespace drogon;
namespace example
{
class WebSocketTest : public drogon::WebSocketController<WebSocketTest>
{
public:
void handleNewMessage(const WebSocketConnectionPtr &,
std::string &&,
const WebSocketMessageType &) override;
void handleConnectionClosed(const WebSocketConnectionPtr &) override;
void handleNewConnection(const HttpRequestPtr &,
const WebSocketConnectionPtr &) override;
WS_PATH_LIST_BEGIN
// list path definitions here;
WS_PATH_ADD("/chat", "drogon::LocalHostFilter", Get);
WS_PATH_LIST_END
private:
PubSubService<std::string> chatRooms_;
};
} // namespace example
@@ -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 (from an implicit page)</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 built-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::kWarn);
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 command 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:
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>
@@ -0,0 +1,112 @@
#include "api_Attachment.h"
#include <fstream>
using namespace api;
// add definition of your processing function here
void Attachment::get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto resp = HttpResponse::newHttpViewResponse("FileUpload", HttpViewData());
callback(resp);
}
void Attachment::upload(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
MultiPartParser fileUpload;
if (fileUpload.parse(req) == 0)
{
// LOG_DEBUG << "upload good!";
auto &files = fileUpload.getFiles();
// LOG_DEBUG << "file num=" << files.size();
for (auto const &file : files)
{
LOG_DEBUG << "file:" << file.getFileName()
<< "(extension=" << file.getFileExtension()
<< ",type=" << file.getFileType()
<< ",len=" << file.fileLength()
<< ",md5=" << file.getMd5() << ")";
file.save();
file.save("123");
file.saveAs("456/hehe");
file.saveAs("456/7/8/9/" + file.getMd5());
file.save("..");
file.save(".xx");
file.saveAs("../xxx");
}
Json::Value json;
json["result"] = "ok";
for (auto &param : fileUpload.getParameters())
{
json[param.first] = param.second;
}
auto resp = HttpResponse::newHttpJsonResponse(json);
callback(resp);
return;
}
LOG_DEBUG << "upload error!";
// LOG_DEBUG << req->con
Json::Value json;
json["result"] = "failed";
auto resp = HttpResponse::newHttpJsonResponse(json);
callback(resp);
}
void Attachment::uploadImage(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
MultiPartParser fileUpload;
// At this endpoint, we only accept one file
if (fileUpload.parse(req) == 0 && fileUpload.getFiles().size() == 1)
{
// LOG_DEBUG << "upload image good!";
Json::Value json;
// Get the first file received
auto &file = fileUpload.getFiles()[0];
// There are 2 ways to check if the file extension is an image.
// First way
if (file.getFileType() == FT_IMAGE)
{
json["isImage"] = true;
}
// Second way
auto fileExtension = file.getFileExtension();
if (fileExtension == "png" || fileExtension == "jpeg" ||
fileExtension == "jpg" || fileExtension == "ico" /* || etc... */)
{
json["isImage"] = true;
}
else
{
json["isImage"] = false;
}
json["result"] = "ok";
for (auto &param : fileUpload.getParameters())
{
json[param.first] = param.second;
}
auto resp = HttpResponse::newHttpJsonResponse(json);
callback(resp);
return;
}
LOG_DEBUG << "upload image error!";
// LOG_DEBUG << req->con
Json::Value json;
json["result"] = "failed";
auto resp = HttpResponse::newHttpJsonResponse(json);
callback(resp);
}
void Attachment::download(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto resp = HttpResponse::newFileResponse("./drogon.jpg", "", CT_IMAGE_JPG);
callback(resp);
}
@@ -0,0 +1,27 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api
{
class Attachment : public drogon::HttpController<Attachment>
{
public:
METHOD_LIST_BEGIN
// use METHOD_ADD to add your custom processing function here;
METHOD_ADD(Attachment::get, "", Get); // Path is '/api/attachment'
METHOD_ADD(Attachment::upload, "/upload", Post);
METHOD_ADD(Attachment::uploadImage, "/uploadImage", Post);
METHOD_ADD(Attachment::download, "/download", Get);
METHOD_LIST_END
// your declaration of processing function maybe like this:
void get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void upload(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void uploadImage(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void download(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
};
} // namespace api
@@ -0,0 +1,525 @@
#include "api_v1_ApiTest.h"
using namespace api::v1;
// add definition of your processing function here
void ApiTest::rootGet(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto res = HttpResponse::newHttpResponse();
res->setBody("ROOT Get!!!");
callback(res);
}
void ApiTest::rootPost(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
std::thread([callback = std::move(callback)]() {
auto res = HttpResponse::newHttpResponse();
res->setBody("ROOT Post!!!");
callback(res);
}).detach();
}
void ApiTest::get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int p1,
std::string &&p2)
{
HttpViewData data;
data.insert("title", std::string("ApiTest::get"));
SafeStringMap<std::string> para;
para["p1"] = std::to_string(p1);
para["p2"] = p2;
data.insert("parameters", para);
auto res = HttpResponse::newHttpViewResponse("ListParaView.csp", data);
callback(res);
}
void ApiTest::your_method_name(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
double p1,
int p2) const
{
LOG_WARN << req->matchedPathPatternData();
HttpViewData data;
data.insert("title", std::string("ApiTest::get"));
SafeStringMap<std::string> para;
para["p1"] = std::to_string(p1);
para["p2"] = std::to_string(p2);
para["p3"] = HttpViewData::htmlTranslate(std::string_view(
"<script>alert(\" This should not be displayed in a browser alert "
"box.\");</script>"));
data.insert("parameters", para);
auto res = HttpResponse::newHttpViewResponse("ListParaView", data);
callback(res);
}
void ApiTest::staticApi(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody("staticApi,hello!!");
resp->setExpiredTime(0); // cache the response forever;
callback(resp);
}
void ApiTest::get2(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string &&p1)
{
// test gzip feature
auto res = HttpResponse::newHttpResponse();
res->setBody(
"Applications\n"
"Developer\n"
"Library\n"
"Network\n"
"System\n"
"Users\n"
"Volumes\n"
"bin\n"
"cores\n"
"dev\n"
"etc\n"
"home\n"
"installer.failurerequests\n"
"net\n"
"opt\n"
"private\n"
"sbin\n"
"tmp\n"
"usb\n"
"usr\n"
"var\n"
"vm\n"
"\n"
"/Applications:\n"
"Adobe\n"
"Adobe Creative Cloud\n"
"Adobe Photoshop CC\n"
"AirPlayer Pro.app\n"
"Android Studio.app\n"
"App Store.app\n"
"Autodesk\n"
"Automator.app\n"
"Axure RP Pro 7.0.app\n"
"BaiduNetdisk_mac.app\n"
"CLion.app\n"
"Calculator.app\n"
"Calendar.app\n"
"Chess.app\n"
"CleanApp.app\n"
"Contacts.app\n"
"DVD Player.app\n"
"Dashboard.app\n"
"Dictionary.app\n"
"Docs for Xcode.app\n"
"FaceTime.app\n"
"FinalShell\n"
"Firefox.app\n"
"Folx.app\n"
"Font Book.app\n"
"GitHub.app\n"
"Google Chrome.app\n"
"Grammarly.app\n"
"Image Capture.app\n"
"Lantern.app\n"
"Launchpad.app\n"
"License.rtf\n"
"MacPorts\n"
"Mail.app\n"
"Maps.app\n"
"Messages.app\n"
"Microsoft Excel.app\n"
"Microsoft Office 2011\n"
"Microsoft OneNote.app\n"
"Microsoft Outlook.app\n"
"Microsoft PowerPoint.app\n"
"Microsoft Word.app\n"
"Mindjet MindManager.app\n"
"Mission Control.app\n"
"Mockplus.app\n"
"MyEclipse 2015\n"
"Notes.app\n"
"OmniGraffle.app\n"
"Pages.app\n"
"Photo Booth.app\n"
"Photos.app\n"
"Preview.app\n"
"QJVPN.app\n"
"QQ.app\n"
"QuickTime Player.app\n"
"RAR Extractor Lite.app\n"
"Reminders.app\n"
"Remote Desktop Connection.app\n"
"Renee Undeleter.app\n"
"Sabaki.app\n"
"Safari.app\n"
"ShadowsocksX.app\n"
"Siri.app\n"
"SogouInputPad.app\n"
"Stickies.app\n"
"System Preferences.app\n"
"TeX\n"
"Telegram.app\n"
"Termius.app\n"
"Tesumego - How to Make a Professional Go Player.app\n"
"TextEdit.app\n"
"Thunder.app\n"
"Time Machine.app\n"
"Tunnelblick.app\n"
"Utilities\n"
"VPN Shield.appdownload\n"
"VirtualBox.app\n"
"WeChat.app\n"
"WinOnX2.app\n"
"Wireshark.app\n"
"Xcode.app\n"
"Yose.app\n"
"YoudaoNote.localized\n"
"finalshelldata\n"
"iBooks.app\n"
"iPhoto.app\n"
"iTools.app\n"
"iTunes.app\n"
"pgAdmin 4.app\n"
"wechatwebdevtools.app\n"
"\n"
"/Applications/Adobe:\n"
"Flash Player\n"
"\n"
"/Applications/Adobe/Flash Player:\n"
"AddIns\n"
"\n"
"/Applications/Adobe/Flash Player/AddIns:\n"
"airappinstaller\n"
"\n"
"/Applications/Adobe/Flash Player/AddIns/airappinstaller:\n"
"airappinstaller\n"
"digest.s\n"
"\n"
"/Applications/Adobe Creative Cloud:\n"
"Adobe Creative Cloud\n"
"Icon\n"
"Uninstall Adobe Creative Cloud\n"
"\n"
"/Applications/Adobe Photoshop CC:\n"
"Adobe Photoshop CC.app\n"
"Configuration\n"
"Icon\n"
"Legal\n"
"LegalNotices.pdf\n"
"Locales\n"
"Plug-ins\n"
"Presets\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop CC.app:\n"
"Contents\n"
"Linguistics\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop CC.app/Contents:\n"
"Application Data\n"
"Frameworks\n"
"Info.plist\n"
"MacOS\n"
"PkgInfo\n"
"Required\n"
"Resources\n"
"_CodeSignature\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data:\n"
"Custom File Info Panels\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels:\n"
"4.0\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels/4.0:\n"
"bin\n"
"custom\n"
"panels\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels/4.0/bin:\n"
"FileInfoFoundation.swf\n"
"FileInfoUI.swf\n"
"framework.swf\n"
"loc\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info "
"Panels/4.0/bin/loc:\n"
"FileInfo_ar_AE.dat\n"
"FileInfo_bg_BG.dat\n"
"FileInfo_cs_CZ.dat\n"
"FileInfo_da_DK.dat\n"
"FileInfo_de_DE.dat\n"
"FileInfo_el_GR.dat\n"
"FileInfo_en_US.dat\n"
"FileInfo_es_ES.dat\n"
"FileInfo_et_EE.dat\n"
"FileInfo_fi_FI.dat\n"
"FileInfo_fr_FR.dat\n"
"FileInfo_he_IL.dat\n"
"FileInfo_hr_HR.dat\n"
"FileInfo_hu_HU.dat\n"
"FileInfo_it_IT.dat\n"
"FileInfo_ja_JP.dat\n"
"FileInfo_ko_KR.dat\n"
"FileInfo_lt_LT.dat\n"
"FileInfo_lv_LV.dat\n"
"FileInfo_nb_NO.dat\n"
"FileInfo_nl_NL.dat\n"
"FileInfo_pl_PL.dat\n"
"FileInfo_pt_BR.dat\n"
"FileInfo_ro_RO.dat\n"
"FileInfo_ru_RU.dat\n"
"FileInfo_sk_SK.dat\n"
"FileInfo_sl_SI.dat\n"
"FileInfo_sv_SE.dat\n"
"FileInfo_tr_TR.dat\n"
"FileInfo_uk_UA.dat\n"
"FileInfo_zh_CN.dat\n"
"FileInfo_zh_TW.dat\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels/4.0/custom:\n"
"DICOM.xml\n"
"Mobile.xml\n"
"loc\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info "
"Panels/4.0/custom/loc:\n"
"DICOM_ar_AE.dat\n"
"DICOM_bg_BG.dat\n"
"DICOM_cs_CZ.dat\n"
"DICOM_da_DK.dat\n"
"DICOM_de_DE.dat\n"
"DICOM_el_GR.dat\n"
"DICOM_en_US.dat\n"
"DICOM_es_ES.dat\n"
"DICOM_et_EE.dat\n"
"DICOM_fi_FI.dat\n"
"DICOM_fr_FR.dat\n"
"DICOM_he_IL.dat\n"
"DICOM_hr_HR.dat\n"
"DICOM_hu_HU.dat\n"
"DICOM_it_IT.dat\n"
"DICOM_ja_JP.dat\n"
"DICOM_ko_KR.dat\n"
"DICOM_lt_LT.dat\n"
"DICOM_lv_LV.dat\n"
"DICOM_nb_NO.dat\n"
"DICOM_nl_NL.dat\n"
"DICOM_pl_PL.dat\n"
"DICOM_pt_BR.dat\n"
"DICOM_ro_RO.dat\n"
"DICOM_ru_RU.dat\n"
"DICOM_sk_SK.dat\n"
"DICOM_sl_SI.dat\n"
"DICOM_sv_SE.dat\n"
"DICOM_tr_TR.dat\n"
"DICOM_uk_UA.dat\n"
"DICOM_zh_CN.dat\n"
"DICOM_zh_TW.dat\n"
"Mobile_ar_AE.dat\n"
"Mobile_bg_BG.dat\n"
"Mobile_cs_CZ.dat\n"
"Mobile_da_DK.dat\n"
"Mobile_de_DE.dat\n"
"Mobile_el_GR.dat\n"
"Mobile_en_US.dat\n"
"Mobile_es_ES.dat\n"
"Mobile_et_EE.dat\n"
"Mobile_fi_FI.dat\n"
"Mobile_fr_FR.dat\n"
"Mobile_he_IL.dat\n"
"Mobile_hr_HR.dat\n"
"Mobile_hu_HU.dat\n"
"Mobile_it_IT.dat\n"
"Mobile_ja_JP.dat\n"
"Mobile_ko_KR.dat\n"
"Mobile_lt_LT.dat\n"
"Mobile_lv_LV.dat\n"
"Mobile_nb_NO.dat\n"
"Mobile_nl_NL.dat\n"
"Mobile_pl_PL.dat\n"
"Mobile_pt_BR.dat\n"
"Mobile_ro_RO.dat\n"
"Mobile_ru_RU.dat\n"
"Mobile_sk_SK.dat\n"
"Mobile_sl_SI.dat\n"
"Mobile_sv_SE.dat\n"
"Mobile_tr_TR.dat\n"
"Mobile_uk_UA.dat\n"
"Mobile_zh_CN.dat\n"
"Mobile_zh_TW.dat\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels/4.0/panels:\n"
"IPTC\n"
"IPTCExt\n"
"advanced\n"
"audioData\n"
"camera\n"
"categories\n"
"description\n"
"dicom\n"
"gpsData\n"
"history\n"
"mobile\n"
"origin\n"
"rawpacket");
res->setExpiredTime(0);
callback(res);
}
void ApiTest::jsonTest(std::shared_ptr<Json::Value> &&json,
std::function<void(const HttpResponsePtr &)> &&callback)
{
Json::Value ret;
if (json)
{
ret["result"] = "ok";
}
else
{
ret["result"] = "bad";
}
auto resp = HttpResponse::newCustomHttpResponse(ret);
callback(resp);
}
void ApiTest::formTest(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto parameters = req->getParameters();
Json::Value ret;
ret["k1"] = parameters["k1"];
ret["k2"] = parameters["k2"];
ret["k3"] = parameters["k3"];
if (parameters["k1"] == "1" && parameters["k2"] == "" &&
parameters["k3"] == "test@example.com")
{
ret["result"] = "ok";
}
else
{
ret["result"] = "bad";
}
auto resp = HttpResponse::newHttpJsonResponse(ret);
callback(resp);
}
void ApiTest::attributesTest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
AttributesPtr attributes = req->getAttributes();
const std::string key = "ATTR_ADDR";
Json::Value ret;
uint64_t data = (uint64_t)req.get();
if (attributes->find(key))
{
ret["result"] = "bad";
callback(HttpResponse::newHttpJsonResponse(ret));
return;
}
attributes->insert(key, data);
if (!attributes->find(key) || attributes->get<uint64_t>(key) != data)
{
ret["result"] = "bad";
}
else
{
ret["result"] = "ok";
}
callback(HttpResponse::newHttpJsonResponse(ret));
return;
}
void ApiTest::regexTest(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int p1,
std::string &&p2)
{
Json::Value ret;
ret["p1"] = p1;
ret["p2"] = std::move(p2);
auto resp = HttpResponse::newHttpJsonResponse(std::move(ret));
callback(resp);
}
static std::mutex cacheTestMtx;
void ApiTest::cacheTest(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
std::unique_lock<std::mutex> lk(cacheTestMtx);
static size_t callCount = 0;
auto resp = HttpResponse::newHttpResponse();
resp->setBody(std::to_string(callCount));
resp->setContentTypeCode(CT_TEXT_PLAIN);
// Expire after a millennia
resp->setExpiredTime(31536000000);
callback(resp);
callCount++;
}
static std::mutex cacheTest2Mtx;
void ApiTest::cacheTest2(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
std::unique_lock<std::mutex> lk(cacheTest2Mtx);
static size_t callCount = 0;
auto resp = HttpResponse::newHttpResponse();
LOG_ERROR << callCount;
resp->setBody(std::to_string(callCount));
resp->setContentTypeCode(CT_TEXT_PLAIN);
// Expire after a millennia
if (callCount >= 2)
resp->setExpiredTime(31536000000);
callback(resp);
callCount++;
}
static std::mutex regexCacheApiMtx;
void ApiTest::cacheTestRegex(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
std::unique_lock<std::mutex> lk(regexCacheApiMtx);
static size_t callCount = 0;
auto resp = HttpResponse::newHttpResponse();
LOG_ERROR << callCount;
resp->setBody(std::to_string(callCount));
resp->setContentTypeCode(CT_TEXT_PLAIN);
// Expire after a millennia
if (callCount >= 2)
resp->setExpiredTime(31536000000);
callback(resp);
callCount++;
}
void ApiTest::echoBody(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody(std::string(req->body()));
callback(resp);
}
@@ -0,0 +1,102 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api
{
namespace v1
{
class ApiTest : public drogon::HttpController<ApiTest>
{
public:
METHOD_LIST_BEGIN
// use METHOD_ADD to add your custom processing function here;
METHOD_ADD(ApiTest::rootGet,
"",
Get,
Options,
"drogon::LocalHostFilter",
"drogon::IntranetIpFilter");
METHOD_ADD(ApiTest::rootPost, "", Post, Options);
METHOD_ADD(ApiTest::get,
"/get/{2:p2}/{1:p1}",
Get); // path is /api/v1/apitest/get/{arg2}/{arg1}
METHOD_ADD(ApiTest::your_method_name,
"/{PI}/List?P2={}",
Get); // path is /api/v1/apitest/{arg1}/list
METHOD_ADD(ApiTest::staticApi, "/static", Get, Options); // CORS
METHOD_ADD(ApiTest::staticApi, "/static", Post, Put, Delete);
METHOD_ADD(ApiTest::get2,
"/get/{}",
Get); // path is /api/v1/apitest/get/{arg1}
ADD_METHOD_TO(ApiTest::get2,
"/absolute/{}",
Get); // path is /absolute/{arg1}
ADD_METHOD_TO(ApiTest::shutdown, "/shutdown",
Get); // path is /shutdown
METHOD_ADD(ApiTest::jsonTest, "/json", Post);
METHOD_ADD(ApiTest::formTest, "/form", Post);
METHOD_ADD(ApiTest::attributesTest, "/attrs", Get);
ADD_METHOD_VIA_REGEX(ApiTest::regexTest, "/reg/([0-9]*)/(.*)", Get);
METHOD_ADD(ApiTest::cacheTest, "/cacheTest", Get);
METHOD_ADD(ApiTest::cacheTest2, "/cacheTest2", Get);
ADD_METHOD_VIA_REGEX(ApiTest::cacheTestRegex,
"/cacheTestRegex/[a-y]+",
Get);
METHOD_ADD(ApiTest::echoBody, "/echoBody", Post);
METHOD_LIST_END
void get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int p1,
std::string &&p2);
void your_method_name(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
double p1,
int p2) const;
void staticApi(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void get2(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string &&p1);
void rootGet(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void rootPost(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void jsonTest(std::shared_ptr<Json::Value> &&json,
std::function<void(const HttpResponsePtr &)> &&callback);
void formTest(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void attributesTest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void regexTest(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int p1,
std::string &&p2);
void shutdown(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
app().quit();
}
void cacheTest(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void cacheTest2(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void cacheTestRegex(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void echoBody(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
public:
ApiTest()
{
LOG_DEBUG << "ApiTest constructor!";
}
};
} // namespace v1
} // namespace api
@@ -0,0 +1,66 @@
#include "api_v1_CoroTest.h"
using namespace api::v1;
Task<> CoroTest::get(HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback)
{
// Force co_await to test awaiting works
co_await drogon::sleepCoro(
trantor::EventLoop::getEventLoopOfCurrentThread(),
std::chrono::milliseconds(100));
auto resp = HttpResponse::newHttpResponse();
resp->setBody("DEADBEEF");
callback(resp);
co_return;
}
Task<> CoroTest::get_with_param(
HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback,
std::string param)
{
// Force co_await to test awaiting works
co_await drogon::sleepCoro(
trantor::EventLoop::getEventLoopOfCurrentThread(),
std::chrono::milliseconds(100));
auto resp = HttpResponse::newHttpResponse();
resp->setBody(param);
callback(resp);
co_return;
}
Task<HttpResponsePtr> CoroTest::get_with_param2(HttpRequestPtr req,
std::string param)
{
// Force co_await to test awaiting works
co_await drogon::sleepCoro(
trantor::EventLoop::getEventLoopOfCurrentThread(),
std::chrono::milliseconds(100));
auto resp = HttpResponse::newHttpResponse();
resp->setBody(param);
co_return resp;
}
Task<HttpResponsePtr> CoroTest::get2(HttpRequestPtr req)
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody("BADDBEEF");
co_return resp;
}
Task<> CoroTest::this_will_fail(
HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback)
{
throw std::runtime_error("This is an expected exception");
callback(HttpResponse::newHttpResponse());
}
Task<HttpResponsePtr> CoroTest::this_will_fail2(HttpRequestPtr req)
{
throw std::runtime_error("This is an expected exception");
co_return HttpResponse::newHttpResponse();
}
@@ -0,0 +1,35 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api
{
namespace v1
{
class CoroTest : public drogon::HttpController<CoroTest>
{
public:
METHOD_LIST_BEGIN
METHOD_ADD(CoroTest::get, "/get", Get);
METHOD_ADD(CoroTest::get_with_param, "/get_with_param/{name}", Get);
METHOD_ADD(CoroTest::get_with_param2, "/get_with_param2/{name}", Get);
METHOD_ADD(CoroTest::get2, "/get2", Get);
METHOD_ADD(CoroTest::get2, "/delay", Get, "CoroFilter");
METHOD_ADD(CoroTest::this_will_fail, "/this_will_fail", Get);
METHOD_ADD(CoroTest::this_will_fail2, "/this_will_fail2", Get);
METHOD_LIST_END
Task<> get(HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback);
Task<> get_with_param(HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback,
std::string name);
Task<HttpResponsePtr> get_with_param2(HttpRequestPtr req, std::string name);
Task<HttpResponsePtr> get2(HttpRequestPtr req);
Task<> this_will_fail(
HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback);
Task<HttpResponsePtr> this_will_fail2(HttpRequestPtr req);
};
} // namespace v1
} // namespace api
@@ -0,0 +1 @@
<img src="https://github.com/an-tao/drogon/wiki/images/drogon-white.jpg"/>
@@ -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 built-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::kWarn);
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 command 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:
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>
@@ -0,0 +1,418 @@
#include "BeginAdviceTest.h"
#include "CustomCtrl.h"
#include "CustomHeaderFilter.h"
#include "DigestAuthFilter.h"
#include <drogon/drogon.h>
#include <iostream>
#include <string>
#include <vector>
#include <string_view>
using namespace drogon;
using namespace std::chrono_literals;
class A : public DrObjectBase
{
public:
void handle(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int p1,
const std::string &p2,
const std::string &p3,
int p4) const
{
HttpViewData data;
data.insert("title", std::string("ApiTest::get"));
SafeStringMap<std::string> para;
para["int p1"] = std::to_string(p1);
para["string p2"] = p2;
para["string p3"] = p3;
para["int p4"] = std::to_string(p4);
data.insert("parameters", para);
auto res = HttpResponse::newHttpViewResponse("ListParaView", data);
callback(res);
}
static void staticHandle(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int p1,
const std::string &p2,
const std::string &p3,
int p4)
{
HttpViewData data;
data.insert("title", std::string("ApiTest::get"));
SafeStringMap<std::string> para;
para["int p1"] = std::to_string(p1);
para["string p2"] = p2;
para["string p3"] = p3;
para["int p4"] = std::to_string(p4);
data.insert("parameters", para);
auto res = HttpResponse::newHttpViewResponse("ListParaView", data);
callback(res);
}
};
class B : public DrObjectBase
{
public:
void operator()(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int p1,
int p2)
{
HttpViewData data;
data.insert("title", std::string("ApiTest::get"));
SafeStringMap<std::string> para;
para["p1"] = std::to_string(p1);
para["p2"] = std::to_string(p2);
data.insert("parameters", para);
auto res = HttpResponse::newHttpViewResponse("ListParaView", data);
callback(res);
}
};
class C : public drogon::HttpController<C>
{
public:
METHOD_LIST_BEGIN
ADD_METHOD_TO(C::priv, "/priv/resource", Get, "DigestAuthFilter");
METHOD_LIST_END
void priv(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody("<P>private content, only for authenticated users</P>");
callback(resp);
}
};
namespace api
{
namespace v1
{
class Test : public HttpController<Test>
{
public:
METHOD_LIST_BEGIN
METHOD_ADD(Test::get,
"get/{2}/{1}",
Get); // path is /api/v1/test/get/{arg2}/{arg1}
METHOD_ADD(Test::list,
"/{2}/info",
Get); // path is /api/v1/test/{arg2}/info
METHOD_LIST_END
void get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int p1,
int p2) const
{
HttpViewData data;
data.insert("title", std::string("ApiTest::get"));
SafeStringMap<std::string> para;
para["p1"] = std::to_string(p1);
para["p2"] = std::to_string(p2);
data.insert("parameters", para);
auto res = HttpResponse::newHttpViewResponse("ListParaView", data);
callback(res);
}
void list(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int p1,
int p2) const
{
HttpViewData data;
data.insert("title", std::string("ApiTest::get"));
SafeStringMap<std::string> para;
para["p1"] = std::to_string(p1);
para["p2"] = std::to_string(p2);
data.insert("parameters", para);
auto res = HttpResponse::newHttpViewResponse("ListParaView", data);
callback(res);
}
};
} // namespace v1
} // namespace api
using namespace std::placeholders;
using namespace drogon;
namespace drogon
{
template <>
std::string_view fromRequest(const HttpRequest &req)
{
return req.body();
}
} // namespace drogon
class Middleware4 : public drogon::HttpMiddleware<Middleware4, false>
{
public:
Middleware4()
{
LOG_DEBUG << "Middleware4\n";
};
void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) override
{
auto ptr = req->attributes()->get<std::shared_ptr<std::string>>(
"test-middleware");
ptr->append("4");
nextCb([req, ptr, mcb = std::move(mcb)](const HttpResponsePtr &resp) {
ptr->append("4");
resp->setBody(*ptr);
mcb(resp);
});
}
};
/// Some examples in the main function show some common functions of drogon. In
/// practice, we don't need such a lengthy main function.
int main()
{
std::cout << banner << std::endl;
// app().addListener("::1", 8848); //ipv6
app().addListener("0.0.0.0", 8848);
// https
if (app().supportSSL())
{
drogon::app()
.setSSLFiles("server.crt", "server.key")
.addListener("0.0.0.0", 8849, true);
}
// Class function example
app().registerHandler("/api/v1/handle1/{}/{}/?p3={}&p4={}", &A::handle);
app().registerHandler(
"/api/v1/handle11/{int p1}/{string p2}/?p3={string p3}&p4={int p4}",
&A::staticHandle);
// Lambda example
app().registerHandler(
"/api/v1/handle2/{int a}/{float b}",
[](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int a, // here the `a` parameter is converted from the number 1
// parameter in the path.
float b, // here the `b` parameter is converted from the number 2
// parameter in the path.
std::string_view &&body, // here the `body` parameter is converted
// from req->as<string_view>();
const std::shared_ptr<Json::Value>
&jsonPtr // here the `jsonPtr` parameter is converted from
// req->as<std::shared_ptr<Json::Value>>();
) {
HttpViewData data;
data.insert("title", std::string("ApiTest::get"));
SafeStringMap<std::string> para;
para["a"] = std::to_string(a);
para["b"] = std::to_string(b);
data.insert("parameters", para);
auto res = HttpResponse::newHttpViewResponse("ListParaView", data);
callback(res);
LOG_DEBUG << body.data();
assert(!jsonPtr);
});
// Functor example
B b;
app().registerHandler("/api/v1/handle3/{1}/{2}", b);
// API example for std::function
A tmp;
std::function<void(const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&,
int,
const std::string &,
const std::string &,
int)>
func = std::bind(&A::handle, &tmp, _1, _2, _3, _4, _5, _6);
app().registerHandler("/api/v1/handle4/{4:p4}/{3:p3}/{1:p1}", func);
app().registerHandler(
"/api/v1/this_will_fail",
[](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
throw std::runtime_error("this should fail");
});
app().setDocumentRoot("./");
app().enableSession(60);
std::map<std::string, std::string> config_credentials;
std::string realm("drogonRealm");
std::string opaque("drogonOpaque");
// Load configuration
app().loadConfigFile("config.example.json");
app().setImplicitPageEnable(true);
app().setImplicitPage("page.html");
auto &json = app().getCustomConfig();
if (json.empty())
{
std::cout << "empty custom config!" << std::endl;
}
else
{
if (!json["realm"].empty())
{
realm = json["realm"].asString();
}
if (!json["opaque"].empty())
{
opaque = json["opaque"].asString();
}
for (auto &&i : json["credentials"])
{
config_credentials[i["user"].asString()] = i["password"].asString();
}
}
// Install Digest Authentication Filter using custom config credentials,
// used by C HttpController (/C/priv/resource)
auto auth_filter =
std::make_shared<DigestAuthFilter>(config_credentials, realm, opaque);
app().registerFilter(auth_filter);
// Install custom controller
auto ctrlPtr = std::make_shared<CustomCtrl>("Hi");
app().registerController(ctrlPtr);
// Install custom filter
auto filterPtr =
std::make_shared<CustomHeaderFilter>("custom_header", "yes");
app().registerFilter(filterPtr);
app().setIdleConnectionTimeout(30s);
// Install custom Middleware
auto middlewarePtr = std::make_shared<Middleware4>();
app().registerMiddleware(middlewarePtr);
// AOP example
app().registerBeginningAdvice(
[]() { LOG_DEBUG << "Event loop is running!"; });
app().registerNewConnectionAdvice([](const trantor::InetAddress &peer,
const trantor::InetAddress &local) {
LOG_DEBUG << "New connection: " << peer.toIpPort() << "-->"
<< local.toIpPort();
return true;
});
app().registerPreRoutingAdvice([](const drogon::HttpRequestPtr &req,
drogon::AdviceCallback &&acb,
drogon::AdviceChainCallback &&accb) {
LOG_DEBUG << "preRouting1";
accb();
});
app().registerPostRoutingAdvice([](const drogon::HttpRequestPtr &req,
drogon::AdviceCallback &&acb,
drogon::AdviceChainCallback &&accb) {
LOG_DEBUG << "postRouting1";
LOG_DEBUG << "Matched path=" << req->matchedPathPatternData();
for (auto &cookie : req->cookies())
{
LOG_DEBUG << "cookie: " << cookie.first << "=" << cookie.second;
}
accb();
});
app().registerPreHandlingAdvice([](const drogon::HttpRequestPtr &req,
drogon::AdviceCallback &&acb,
drogon::AdviceChainCallback &&accb) {
LOG_DEBUG << "preHandling1";
accb();
});
app().registerPostHandlingAdvice([](const drogon::HttpRequestPtr &,
const drogon::HttpResponsePtr &resp) {
LOG_DEBUG << "postHandling1";
resp->addHeader("Access-Control-Allow-Origin", "*");
});
app().registerPreRoutingAdvice([](const drogon::HttpRequestPtr &req) {
LOG_DEBUG << "preRouting observer";
});
app().registerPostRoutingAdvice([](const drogon::HttpRequestPtr &req) {
LOG_DEBUG << "postRouting observer";
});
app().registerPreHandlingAdvice([](const drogon::HttpRequestPtr &req) {
LOG_DEBUG << "preHanding observer";
});
app().registerSyncAdvice([](const HttpRequestPtr &req) -> HttpResponsePtr {
static const HttpResponsePtr nullResp;
if (req->path() == "/plaintext")
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody("Hello, World!");
resp->setContentTypeCodeAndCustomString(
CT_TEXT_PLAIN, "content-type: text/plain\r\n");
return resp;
}
return nullResp;
});
app().registerSessionStartAdvice([](const std::string &sessionId) {
LOG_DEBUG << "session start:" << sessionId;
});
app().registerSessionDestroyAdvice([](const std::string &sessionId) {
LOG_DEBUG << "session destroy:" << sessionId;
});
// Output information of all handlers
auto handlerInfo = app().getHandlersInfo();
for (auto &info : handlerInfo)
{
std::cout << std::get<0>(info);
switch (std::get<1>(info))
{
case Get:
std::cout << " (GET) ";
break;
case Post:
std::cout << " (POST) ";
break;
case Delete:
std::cout << " (DELETE) ";
break;
case Put:
std::cout << " (PUT) ";
break;
case Options:
std::cout << " (OPTIONS) ";
break;
case Head:
std::cout << " (Head) ";
break;
case Patch:
std::cout << " (PATCH) ";
break;
default:
break;
}
std::cout << std::get<2>(info) << std::endl;
}
auto resp = HttpResponse::newFileResponse("index.html");
resp->setExpiredTime(0);
app().setCustom404Page(resp);
app().addListener("0.0.0.0", 0);
app().enableCompressedRequest(true);
app().registerBeginningAdvice([]() {
auto addresses = app().getListeners();
for (auto &address : addresses)
{
LOG_INFO << address.toIpPort() << " LISTEN";
}
});
app().registerCustomExtensionMime("md", "text/markdown");
app().setFileTypes({"md", "html", "jpg", "cc", "txt"});
std::cout << "Date: "
<< drogon::utils::getHttpFullDateStr(trantor::Date::now())
<< std::endl;
app().registerBeginningAdvice(
[]() { BeginAdviceTest::setContent("DrogonReady"); });
app().run();
}
@@ -0,0 +1,5 @@
# Test
This is an example of a Markdown file.
@@ -0,0 +1 @@
三點一四一五九二
@@ -0,0 +1,76 @@
#define DROGON_TEST_MAIN
#include <drogon/drogon_test.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/HttpController.h>
#include <drogon/Cookie.h>
#include <trantor/utils/Logger.h>
using namespace drogon;
using namespace trantor;
class CookieSameSiteController
: public drogon::HttpController<CookieSameSiteController>
{
public:
static const char *SESSION_SAME_SITE;
METHOD_LIST_BEGIN
METHOD_ADD(CookieSameSiteController::set, "/set/{newSameSite}", Get);
METHOD_LIST_END
void set(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string &&newSameSite)
{
std::string old_session_same_site = "Null";
if (req->session()->find(SESSION_SAME_SITE))
{
old_session_same_site =
req->session()->get<std::string>(SESSION_SAME_SITE);
}
LOG_INFO << "Server: new sameSite == " << newSameSite
<< ", old sameSite == " << old_session_same_site;
drogon::HttpAppFramework::instance().enableSession(
0, Cookie::convertString2SameSite(newSameSite));
req->session()->modify<std::string>(
SESSION_SAME_SITE,
[newSameSite](std::string &sameSite) { sameSite = newSameSite; });
req->session()->changeSessionIdToClient();
Json::Value json;
json["result"] = "ok";
json["old value"] = old_session_same_site;
json["new value"] = newSameSite;
auto resp = HttpResponse::newHttpJsonResponse(std::move(json));
callback(resp);
}
};
const char *CookieSameSiteController::SESSION_SAME_SITE{"session_same_site"};
// -- main
int main(int argc, char **argv)
{
trantor::Logger::setLogLevel(trantor::Logger::kInfo);
std::promise<void> p1;
std::future<void> f1 = p1.get_future();
std::thread thr([&]() {
app()
.setSSLFiles("server.crt", "server.key")
.addListener("0.0.0.0", 8855, true)
.enableSession();
app().getLoop()->queueInLoop([&p1]() { p1.set_value(); });
app().run();
});
f1.get();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
int testStatus = test::run(argc, argv);
app().getLoop()->queueInLoop([]() { app().quit(); });
thr.join();
return testStatus;
}
@@ -0,0 +1,81 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
DROGON_TEST(Base64)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64Encode(in);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
CHECK(decoded == in);
SUBSECTION(InvalidChars)
{
auto decoded =
drogon::utils::base64Decode("ZHJvZ2*9uIGZy**YW1ld2***9yaw*=*=");
CHECK(decoded == in);
}
SUBSECTION(InvalidCharsNoPadding)
{
auto decoded =
drogon::utils::base64Decode("ZHJvZ2*9uIGZy**YW1ld2***9yaw**");
CHECK(decoded == in);
}
SUBSECTION(Unpadded)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64EncodeUnpadded(in);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");
CHECK(decoded == in);
}
SUBSECTION(LongString)
{
std::string in;
in.reserve(100000);
for (int i = 0; i < 100000; ++i)
{
in.append(1, char(i));
}
auto out = drogon::utils::base64Encode(in);
auto out2 = drogon::utils::base64Decode(out);
auto encoded = drogon::utils::base64Encode(in);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(decoded == in);
}
SUBSECTION(URLSafe)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64Encode(in, true);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
CHECK(decoded == in);
}
SUBSECTION(UnpaddedURLSafe)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64EncodeUnpadded(in, true);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");
CHECK(decoded == in);
}
SUBSECTION(LongURLSafe)
{
std::string in;
in.reserve(100000);
for (int i = 0; i < 100000; ++i)
{
in.append(1, char(i));
}
auto encoded = drogon::utils::base64Encode(in, true);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(decoded == in);
}
}
@@ -0,0 +1,30 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
#include <iostream>
using namespace drogon::utils;
DROGON_TEST(BrotliTest)
{
SUBSECTION(shortText)
{
std::string source{"123中文顶替要枯械"};
auto compressed = brotliCompress(source.data(), source.length());
auto decompressed =
brotliDecompress(compressed.data(), compressed.length());
CHECK(source == decompressed);
}
SUBSECTION(longText)
{
std::string source;
for (size_t i = 0; i < 100000; i++)
{
source.append(std::to_string(i));
}
auto compressed = brotliCompress(source.data(), source.length());
auto decompressed =
brotliDecompress(compressed.data(), compressed.length());
CHECK(source == decompressed);
}
}
@@ -0,0 +1,39 @@
#include <drogon/drogon_test.h>
#include <drogon/CacheMap.h>
#include <drogon/HttpAppFramework.h>
#include <trantor/net/EventLoopThread.h>
#include <chrono>
using namespace drogon;
using namespace std::chrono_literals;
DROGON_TEST(CacheMapTest)
{
trantor::EventLoopThread loopThread;
loopThread.run();
drogon::CacheMap<std::string, std::string> cache(loopThread.getLoop(),
0.1f,
4,
30);
for (size_t i = 1; i < 40; i++)
cache.insert(std::to_string(i), "a", i);
cache.insert("bla", "");
cache.insert("zzz", "-");
std::this_thread::sleep_for(3s);
CHECK(cache.find("0") == false); // doesn't exist
CHECK(cache.find("1") == false); // timeout
CHECK(cache.find("15") == true);
CHECK(cache.find("bla") == true);
cache.erase("30");
CHECK(cache.find("30") == false);
cache.modify("bla", [](std::string &s) { s = "asd"; });
CHECK(cache["bla"] == "asd");
std::string content;
cache.findAndFetch("zzz", content);
CHECK(content == "-");
}
@@ -0,0 +1,29 @@
#include <drogon/drogon_test.h>
namespace api
{
namespace v1
{
template <typename T>
class handler : public drogon::DrObject<T>
{
public:
static std::string name()
{
return handler<T>::classTypeName();
}
};
class hh : public handler<hh>
{
};
} // namespace v1
} // namespace api
DROGON_TEST(ClassName)
{
api::v1::hh h;
CHECK(h.className() == "api::v1::hh");
CHECK(api::v1::hh::classTypeName() == "api::v1::hh");
CHECK(h.name() == "api::v1::hh");
}
@@ -0,0 +1,48 @@
#include <drogon/HttpController.h>
#include <drogon/HttpSimpleController.h>
#include <drogon/WebSocketController.h>
#include <drogon/drogon_test.h>
class Ctrl : public drogon::HttpController<Ctrl, false>
{
public:
static void initPathRouting()
{
created = true;
};
static bool created;
};
class SimpleCtrl : public drogon::HttpController<Ctrl, false>
{
public:
static void initPathRouting()
{
created = true;
};
static bool created;
};
class WsCtrl : public drogon::WebSocketController<WsCtrl, false>
{
public:
static void initPathRouting()
{
created = true;
};
static bool created;
};
bool Ctrl::created = false;
bool SimpleCtrl::created = false;
bool WsCtrl::created = false;
DROGON_TEST(ControllerCreation)
{
REQUIRE(Ctrl::created == false);
REQUIRE(SimpleCtrl::created == false);
REQUIRE(WsCtrl::created == false);
}
@@ -0,0 +1,63 @@
#include <drogon/Cookie.h>
#include <drogon/drogon_test.h>
DROGON_TEST(CookieTest)
{
drogon::Cookie cookie1("test", "1");
CHECK(cookie1.cookieString() == "Set-Cookie: test=1; HttpOnly\r\n");
drogon::Cookie cookie2("test", "2");
cookie2.setSecure(true);
CHECK(cookie2.cookieString() == "Set-Cookie: test=2; Secure; HttpOnly\r\n");
drogon::Cookie cookie3("test", "3");
cookie3.setDomain("drogon.org");
cookie3.setExpiresDate(trantor::Date(1621561557000000L));
CHECK(cookie3.cookieString() ==
"Set-Cookie: test=3; Expires=Fri, 21 May 2021 01:45:57 GMT; "
"Domain=drogon.org; HttpOnly\r\n");
drogon::Cookie cookie4("test", "4");
cookie4.setMaxAge(3600);
cookie4.setSameSite(drogon::Cookie::SameSite::kStrict);
CHECK(cookie4.cookieString() ==
"Set-Cookie: test=4; Max-Age=3600; "
"SameSite=Strict; HttpOnly\r\n");
drogon::Cookie cookie5("test", "5");
cookie5.setSameSite(drogon::Cookie::SameSite::kNone);
CHECK(cookie5.cookieString() ==
"Set-Cookie: test=5; "
"SameSite=None; Secure; HttpOnly\r\n");
CHECK(drogon::Cookie::SameSite::kLax ==
drogon::Cookie::convertString2SameSite("Lax"));
CHECK(drogon::Cookie::SameSite::kStrict ==
drogon::Cookie::convertString2SameSite("Strict"));
CHECK(drogon::Cookie::SameSite::kNone ==
drogon::Cookie::convertString2SameSite("None"));
// Test for Partitioned attribute
drogon::Cookie cookie6("test", "6");
cookie6.setPartitioned(true);
CHECK(cookie6.cookieString() ==
"Set-Cookie: test=6; Secure; HttpOnly; Partitioned\r\n");
// Test that partitioned attribute automatically sets secure
drogon::Cookie cookie7("test", "7");
cookie7.setPartitioned(true);
CHECK(cookie7.isSecure() == true);
// Test other attributes
drogon::Cookie cookie8("test", "8");
cookie8.setPartitioned(true);
cookie8.setDomain("drogon.org");
cookie8.setMaxAge(3600);
CHECK(cookie8.cookieString() ==
"Set-Cookie: test=8; Max-Age=3600; Domain=drogon.org; Secure; "
"HttpOnly; Partitioned\r\n");
// Teset Partitioned and SameSite can coexist
drogon::Cookie cookie9("test", "9");
cookie9.setPartitioned(true);
cookie9.setSameSite(drogon::Cookie::SameSite::kLax);
CHECK(
cookie9.cookieString() ==
"Set-Cookie: test=9; SameSite=Lax; Secure; HttpOnly; Partitioned\r\n");
}
@@ -0,0 +1,330 @@
#include <drogon/drogon_test.h>
#include <drogon/utils/coroutine.h>
#include <drogon/HttpAppFramework.h>
#include <trantor/net/EventLoopThread.h>
#include <trantor/net/EventLoopThreadPool.h>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <exception>
#include <future>
#include <memory>
#include <mutex>
#include <optional>
#include <type_traits>
using namespace drogon;
namespace drogon::internal
{
struct SomeStruct
{
~SomeStruct()
{
beenDestructed = true;
}
static bool beenDestructed;
};
bool SomeStruct::beenDestructed = false;
struct StructAwaiter : public CallbackAwaiter<std::shared_ptr<SomeStruct>>
{
void await_suspend(std::coroutine_handle<> handle)
{
setValue(std::make_shared<SomeStruct>());
handle.resume();
}
};
} // namespace drogon::internal
// Workaround limitation of macros
template <typename T>
using is_int = std::is_same<T, int>;
template <typename T>
using is_void = std::is_same<T, void>;
DROGON_TEST(CroutineBasics)
{
// Basic checks making sure coroutine works as expected
STATIC_REQUIRE(is_awaitable_v<Task<>>);
STATIC_REQUIRE(is_awaitable_v<Task<int>>);
STATIC_REQUIRE(is_awaitable_v<Task<>>);
STATIC_REQUIRE(is_awaitable_v<Task<int>>);
STATIC_REQUIRE(is_int<await_result_t<Task<int>>>::value);
STATIC_REQUIRE(is_void<await_result_t<Task<>>>::value);
// No, you cannot await AsyncTask. By design
STATIC_REQUIRE(is_awaitable_v<AsyncTask> == false);
// AsyncTask should execute eagerly
int m = 0;
[&m]() -> AsyncTask {
m = 1;
co_return;
}();
REQUIRE(m == 1);
// Make sure sync_wait works
CHECK(sync_wait([]() -> Task<int> { co_return 1; }()) == 1);
// make sure it does affect the outside world
int n = 0;
sync_wait([&]() -> Task<> {
n = 1;
co_return;
}());
CHECK(n == 1);
// Testing that exceptions can propagate through coroutines
auto throw_in_task = [TEST_CTX]() -> Task<> {
auto f = []() -> Task<> { throw std::runtime_error("test error"); };
CHECK_THROWS_AS(co_await f(), std::runtime_error);
};
sync_wait(throw_in_task());
// Test sync_wait propagates exception
auto throws = []() -> Task<> {
throw std::runtime_error("bla");
co_return;
};
CHECK_THROWS_AS(sync_wait(throws()), std::runtime_error);
// Test co_return non-copyable object works
auto return_unique_ptr = [TEST_CTX]() -> Task<std::unique_ptr<int>> {
co_return std::make_unique<int>(42);
};
CHECK(*sync_wait(return_unique_ptr()) == 42);
// Test co_awaiting non-copyable object works
auto await_non_copyable = [TEST_CTX]() -> Task<> {
auto return_unique_ptr = []() -> Task<std::unique_ptr<int>> {
co_return std::make_unique<int>(123);
};
auto ptr = co_await return_unique_ptr();
CHECK(*ptr == 123);
};
sync_wait(await_non_copyable());
// This only works because async_run tries to run the coroutine as soon as
// possible and the coroutine does not wait
int testVar = 0;
async_run([&testVar]() -> Task<void> {
testVar = 1;
co_return;
});
CHECK(testVar == 1);
async_run([TEST_CTX]() -> Task<void> {
auto val =
co_await queueInLoopCoro<int>(app().getLoop(), []() { return 42; });
CHECK(val == 42);
});
async_run([TEST_CTX]() -> Task<void> {
co_await queueInLoopCoro<void>(app().getLoop(), []() { LOG_DEBUG; });
});
}
DROGON_TEST(CompilcatedCoroutineLifetime)
{
auto coro = []() -> Task<Task<std::string>> {
auto coro2 = []() -> Task<std::string> {
auto coro3 = []() -> Task<std::string> {
co_return std::string("Hello, World!");
};
auto coro4 = [coro3 = std::move(coro3)]() -> Task<std::string> {
auto coro5 = []() -> Task<> { co_return; };
co_await coro5();
co_return co_await coro3();
};
co_return co_await coro4();
};
co_return coro2();
};
auto task1 = coro();
auto task2 = sync_wait(task1);
std::string str = sync_wait(task2);
CHECK(str == "Hello, World!");
}
DROGON_TEST(CoroutineDestruction)
{
// Test coroutine destruction
auto destruct = []() -> Task<> {
auto awaitStruct = []() -> Task<std::shared_ptr<internal::SomeStruct>> {
co_return co_await internal::StructAwaiter();
};
auto awaitNothing = [awaitStruct]() -> Task<> {
co_await awaitStruct();
};
co_await awaitNothing();
};
sync_wait(destruct());
CHECK(internal::SomeStruct::beenDestructed == true);
}
DROGON_TEST(AsyncWaitLifetime)
{
app().getLoop()->queueInLoop([TEST_CTX]() {
async_run([TEST_CTX]() -> Task<> {
auto ptr = std::make_shared<std::string>("test");
CHECK(ptr.use_count() == 1);
co_await sleepCoro(drogon::app().getLoop(), 0.01);
CHECK(ptr.use_count() == 1);
});
});
app().getLoop()->queueInLoop([TEST_CTX]() {
auto ptr = std::make_shared<std::string>("test");
async_run([ptr, TEST_CTX]() -> Task<> {
CHECK(ptr.use_count() == 2);
co_await sleepCoro(drogon::app().getLoop(), 0.01);
CHECK(ptr.use_count() == 1);
});
});
auto ptr = std::make_shared<std::string>("test");
app().getLoop()->queueInLoop([ptr, TEST_CTX]() {
async_run([ptr, TEST_CTX]() -> Task<> {
co_await sleepCoro(drogon::app().getLoop(), 0.01);
CHECK(ptr.use_count() == 1);
});
});
auto ptr2 = std::make_shared<std::string>("test");
app().getLoop()->queueInLoop(async_func([ptr2, TEST_CTX]() -> Task<> {
co_await sleepCoro(drogon::app().getLoop(), 0.01);
CHECK(ptr2.use_count() == 1);
}));
}
DROGON_TEST(SwitchThread)
{
trantor::EventLoopThread thread;
thread.getLoop()->setIndex(12345);
thread.run();
auto switch_thread = [TEST_CTX, &thread]() -> Task<> {
co_await switchThreadCoro(thread.getLoop());
auto currentLoop = trantor::EventLoop::getEventLoopOfCurrentThread();
MANDATE(currentLoop != nullptr);
CHECK(currentLoop->index() == 12345);
currentLoop->quit();
};
sync_wait(switch_thread());
thread.wait();
}
DROGON_TEST(Mutex)
{
trantor::EventLoopThreadPool pool{3};
pool.start();
Mutex mutex;
async_run([&]() -> Task<> {
co_await switchThreadCoro(pool.getLoop(0));
auto guard = co_await mutex.scoped_lock();
co_await sleepCoro(pool.getLoop(1), std::chrono::seconds(2));
co_return;
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::promise<void> done;
async_run([&]() -> Task<> {
co_await switchThreadCoro(pool.getLoop(2));
auto id = std::this_thread::get_id();
co_await mutex.lock();
CHECK(id == std::this_thread::get_id());
mutex.unlock();
CHECK(id == std::this_thread::get_id());
done.set_value();
co_return;
});
done.get_future().wait();
for (int16_t i = 0; i < 3; i++)
pool.getLoop(i)->quit();
pool.wait();
}
DROGON_TEST(WhenAll)
{
using TestCtx = std::shared_ptr<drogon::test::Case>;
[](TestCtx TEST_CTX) -> AsyncTask {
size_t counter = 0;
auto t1 = [](TestCtx TEST_CTX, size_t *counter) -> Task<> {
co_await drogon::sleepCoro(app().getLoop(), 0.2);
(*counter)++;
}(TEST_CTX, &counter);
auto t2 = [](TestCtx TEST_CTX, size_t *counter) -> Task<> {
co_await drogon::sleepCoro(app().getLoop(), 0.1);
(*counter)++;
}(TEST_CTX, &counter);
std::vector<Task<void>> tasks;
tasks.emplace_back(std::move(t1));
tasks.emplace_back(std::move(t2));
co_await when_all(std::move(tasks));
CHECK(counter == 2);
}(TEST_CTX);
[](TestCtx TEST_CTX) -> AsyncTask {
std::vector<Task<void>> tasks;
co_await when_all(std::move(tasks));
SUCCESS();
}(TEST_CTX);
[](TestCtx TEST_CTX) -> AsyncTask {
auto t1 = [](TestCtx TEST_CTX) -> Task<int> { co_return 1; }(TEST_CTX);
auto t2 = [](TestCtx TEST_CTX) -> Task<int> { co_return 2; }(TEST_CTX);
std::vector<Task<int>> tasks;
tasks.emplace_back(std::move(t1));
tasks.emplace_back(std::move(t2));
auto res = co_await when_all(std::move(tasks));
CO_REQUIRE(res.size() == 2);
CHECK(res[0] == 1);
CHECK(res[1] == 2);
}(TEST_CTX);
[](TestCtx TEST_CTX) -> AsyncTask {
auto t1 = [](TestCtx TEST_CTX) -> Task<int> { co_return 1; }(TEST_CTX);
auto t2 = [](TestCtx TEST_CTX) -> Task<std::string> {
co_return "Hello";
}(TEST_CTX);
auto [num, str] = co_await when_all(std::move(t1), std::move(t2));
CHECK(num == 1);
CHECK(str == "Hello");
}(TEST_CTX);
[](TestCtx TEST_CTX) -> AsyncTask {
size_t counter = 0;
// Even on corutine throws, other coroutins run to completion
auto t1 = [](TestCtx TEST_CTX, size_t *counter) -> Task<int> {
co_await drogon::sleepCoro(app().getLoop(), 0.2);
(*counter)++;
co_return 1;
}(TEST_CTX, &counter);
auto t2 = [](TestCtx TEST_CTX) -> Task<std::string> {
co_await drogon::sleepCoro(app().getLoop(), 0.1);
throw std::runtime_error("Test exception");
}(TEST_CTX);
CO_REQUIRE_THROWS(co_await when_all(std::move(t1), std::move(t2)));
CHECK(counter == 1);
}(TEST_CTX);
[](TestCtx TEST_CTX) -> AsyncTask {
size_t counter = 0;
// void retuens gets mapped to std::false_type in the tuple API
auto t1 = [](TestCtx TEST_CTX, size_t *counter) -> Task<> {
(*counter)++;
co_return;
}(TEST_CTX, &counter);
auto [res] = co_await when_all(std::move(t1));
CHECK(counter == 1);
}(TEST_CTX);
}
@@ -0,0 +1,86 @@
#include <drogon/DrObject.h>
#include <drogon/drogon_test.h>
#include <drogon/HttpController.h>
using namespace drogon;
class TestA : public DrObject<TestA>
{
};
namespace test
{
class TestB : public DrObject<TestB>
{
};
} // namespace test
DROGON_TEST(DrObjectCreationTest)
{
using PtrType = std::shared_ptr<DrObjectBase>;
auto obj = PtrType(DrClassMap::newObject("TestA"));
CHECK(obj != nullptr);
auto objPtr = DrClassMap::getSingleInstance("TestA");
CHECK(objPtr.get() != nullptr);
auto objPtr2 = DrClassMap::getSingleInstance<TestA>();
CHECK(objPtr2.get() != nullptr);
CHECK(objPtr == objPtr2);
}
DROGON_TEST(DrObjectNamespaceTest)
{
using PtrType = std::shared_ptr<DrObjectBase>;
auto obj = PtrType(DrClassMap::newObject("test::TestB"));
CHECK(obj != nullptr);
auto objPtr = DrClassMap::getSingleInstance("test::TestB");
CHECK(objPtr.get() != nullptr);
auto objPtr2 = DrClassMap::getSingleInstance<::test::TestB>();
CHECK(objPtr2.get() != nullptr);
CHECK(objPtr == objPtr2);
}
class TestC : public DrObject<TestC>
{
public:
static constexpr bool isAutoCreation = true;
};
class TestD : public DrObject<TestD>
{
public:
static constexpr bool isAutoCreation = false;
};
class TestE : public DrObject<TestE>
{
public:
static constexpr double isAutoCreation = 3.0;
};
class CtrlA : public HttpController<CtrlA>
{
public:
METHOD_LIST_BEGIN
METHOD_LIST_END
};
class CtrlB : public HttpController<CtrlB, false>
{
public:
METHOD_LIST_BEGIN
METHOD_LIST_END
};
DROGON_TEST(IsAutoCreationClassTest)
{
STATIC_REQUIRE(isAutoCreationClass<TestA>::value == false);
STATIC_REQUIRE(isAutoCreationClass<TestC>::value == true);
STATIC_REQUIRE(isAutoCreationClass<TestD>::value == false);
STATIC_REQUIRE(isAutoCreationClass<TestE>::value == false);
STATIC_REQUIRE(isAutoCreationClass<CtrlA>::value == true);
STATIC_REQUIRE(isAutoCreationClass<CtrlB>::value == false);
}
@@ -0,0 +1,101 @@
#include "../lib/src/HttpUtils.h"
#include <drogon/drogon_test.h>
#include <string>
using namespace drogon;
DROGON_TEST(ExtensionTest)
{
SUBSECTION(normal)
{
std::string str{"drogon.jpg"};
CHECK(getFileExtension(str) == "jpg");
}
SUBSECTION(negative)
{
std::string str{"drogon."};
CHECK(getFileExtension(str) == "");
str = "drogon";
CHECK(getFileExtension(str) == "");
str = "";
CHECK(getFileExtension(str) == "");
str = "....";
CHECK(getFileExtension(str) == "");
}
}
DROGON_TEST(ContentTypeTest)
{
SUBSECTION(normal)
{
for (int i = CT_NONE + 1; i < CT_CUSTOM; i++)
{
auto contentType = ContentType(i);
const auto mimeType = contentTypeToMime(contentType);
CHECK(mimeType.empty() == false);
CHECK(parseContentType(mimeType) == contentType);
auto exts = getFileExtensions(contentType);
bool shouldBeEmpty = (contentType == CT_APPLICATION_X_FORM) ||
(contentType == CT_APPLICATION_OCTET_STREAM) ||
(contentType == CT_MULTIPART_FORM_DATA);
CHECK(exts.empty() == shouldBeEmpty);
for (const auto &ext : exts)
{
auto dummyFile{std::string("dummy.").append(ext)};
// Handle multiple mime types by setting the default ContentType
if (contentType == CT_APPLICATION_X_JAVASCRIPT)
contentType = CT_TEXT_JAVASCRIPT;
if (contentType == CT_TEXT_XML)
contentType = CT_APPLICATION_XML;
CHECK(getContentType(dummyFile) == contentType);
CHECK(parseFileType(dummyFile) != FT_UNKNOWN);
}
if (!shouldBeEmpty)
{
CHECK(getFileType(contentType) != FT_UNKNOWN);
CHECK(getFileType(contentType) != FT_CUSTOM);
}
}
}
SUBSECTION(negative)
{
CHECK(getFileType(CT_NONE) == FT_UNKNOWN);
CHECK(getFileType(CT_CUSTOM) == FT_CUSTOM);
CHECK(getFileType(CT_APPLICATION_X_FORM) == FT_UNKNOWN);
CHECK(getFileType(CT_APPLICATION_OCTET_STREAM) == FT_UNKNOWN);
CHECK(getFileType(CT_MULTIPART_FORM_DATA) == FT_UNKNOWN);
CHECK(contentTypeToMime(CT_NONE).empty());
CHECK(contentTypeToMime(CT_CUSTOM) ==
contentTypeToMime(CT_APPLICATION_OCTET_STREAM));
CHECK(parseContentType("") == CT_NONE);
CHECK(parseContentType("application/x-www-form-urlencoded") ==
CT_APPLICATION_X_FORM);
CHECK(parseContentType("multipart/form-data") ==
CT_MULTIPART_FORM_DATA);
CHECK(parseFileType("any.thing") == FT_CUSTOM);
CHECK(getContentType("any.thing") == CT_APPLICATION_OCTET_STREAM);
CHECK(getFileExtensions(CT_NONE).empty());
CHECK(getFileExtensions(CT_APPLICATION_X_FORM).empty());
CHECK(getFileExtensions(CT_APPLICATION_OCTET_STREAM).empty());
CHECK(getFileExtensions(CT_MULTIPART_FORM_DATA).empty());
CHECK(getFileExtensions(CT_CUSTOM).empty());
}
}
DROGON_TEST(FileTypeTest)
{
SUBSECTION(normal)
{
CHECK(parseFileType("jpg") == FT_IMAGE);
CHECK(parseFileType("mp4") == FT_MEDIA);
CHECK(parseFileType("csp") == FT_CUSTOM);
CHECK(parseFileType("html") == FT_DOCUMENT);
}
SUBSECTION(negative)
{
CHECK(parseFileType("") == FT_UNKNOWN);
CHECK(parseFileType("don'tknow") == FT_CUSTOM);
}
}
+319
View File
@@ -0,0 +1,319 @@
#include <drogon/drogon_test.h>
#include <drogon/utils/Utilities.h>
using namespace drogon;
DROGON_TEST(Gzip)
{
const std::string inStr =
"Applications\n"
"Developer\n"
"Library\n"
"Network\n"
"System\n"
"Users\n"
"Volumes\n"
"bin\n"
"cores\n"
"dev\n"
"etc\n"
"home\n"
"installer.failurerequests\n"
"net\n"
"opt\n"
"private\n"
"sbin\n"
"tmp\n"
"usb\n"
"usr\n"
"var\n"
"vm\n"
"\n"
"/Applications:\n"
"Adobe\n"
"Adobe Creative Cloud\n"
"Adobe Photoshop CC\n"
"AirPlayer Pro.app\n"
"Android Studio.app\n"
"App Store.app\n"
"Autodesk\n"
"Automator.app\n"
"Axure RP Pro 7.0.app\n"
"BaiduNetdisk_mac.app\n"
"CLion.app\n"
"Calculator.app\n"
"Calendar.app\n"
"Chess.app\n"
"CleanApp.app\n"
"Contacts.app\n"
"DVD Player.app\n"
"Dashboard.app\n"
"Dictionary.app\n"
"Docs for Xcode.app\n"
"FaceTime.app\n"
"FinalShell\n"
"Firefox.app\n"
"Folx.app\n"
"Font Book.app\n"
"GitHub.app\n"
"Google Chrome.app\n"
"Grammarly.app\n"
"Image Capture.app\n"
"Lantern.app\n"
"Launchpad.app\n"
"License.rtf\n"
"MacPorts\n"
"Mail.app\n"
"Maps.app\n"
"Messages.app\n"
"Microsoft Excel.app\n"
"Microsoft Office 2011\n"
"Microsoft OneNote.app\n"
"Microsoft Outlook.app\n"
"Microsoft PowerPoint.app\n"
"Microsoft Word.app\n"
"Mindjet MindManager.app\n"
"Mission Control.app\n"
"Mockplus.app\n"
"MyEclipse 2015\n"
"Notes.app\n"
"OmniGraffle.app\n"
"Pages.app\n"
"Photo Booth.app\n"
"Photos.app\n"
"Preview.app\n"
"QJVPN.app\n"
"QQ.app\n"
"QuickTime Player.app\n"
"RAR Extractor Lite.app\n"
"Reminders.app\n"
"Remote Desktop Connection.app\n"
"Renee Undeleter.app\n"
"Sabaki.app\n"
"Safari.app\n"
"ShadowsocksX.app\n"
"Siri.app\n"
"SogouInputPad.app\n"
"Stickies.app\n"
"System Preferences.app\n"
"TeX\n"
"Telegram.app\n"
"Termius.app\n"
"Tesumego - How to Make a Professional Go Player.app\n"
"TextEdit.app\n"
"Thunder.app\n"
"Time Machine.app\n"
"Tunnelblick.app\n"
"Utilities\n"
"VPN Shield.appdownload\n"
"VirtualBox.app\n"
"WeChat.app\n"
"WinOnX2.app\n"
"Wireshark.app\n"
"Xcode.app\n"
"Yose.app\n"
"YoudaoNote.localized\n"
"finalshelldata\n"
"iBooks.app\n"
"iPhoto.app\n"
"iTools.app\n"
"iTunes.app\n"
"pgAdmin 4.app\n"
"wechatwebdevtools.app\n"
"\n"
"/Applications/Adobe:\n"
"Flash Player\n"
"\n"
"/Applications/Adobe/Flash Player:\n"
"AddIns\n"
"\n"
"/Applications/Adobe/Flash Player/AddIns:\n"
"airappinstaller\n"
"\n"
"/Applications/Adobe/Flash Player/AddIns/airappinstaller:\n"
"airappinstaller\n"
"digest.s\n"
"\n"
"/Applications/Adobe Creative Cloud:\n"
"Adobe Creative Cloud\n"
"Icon\n"
"Uninstall Adobe Creative Cloud\n"
"\n"
"/Applications/Adobe Photoshop CC:\n"
"Adobe Photoshop CC.app\n"
"Configuration\n"
"Icon\n"
"Legal\n"
"LegalNotices.pdf\n"
"Locales\n"
"Plug-ins\n"
"Presets\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop CC.app:\n"
"Contents\n"
"Linguistics\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop CC.app/Contents:\n"
"Application Data\n"
"Frameworks\n"
"Info.plist\n"
"MacOS\n"
"PkgInfo\n"
"Required\n"
"Resources\n"
"_CodeSignature\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data:\n"
"Custom File Info Panels\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels:\n"
"4.0\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels/4.0:\n"
"bin\n"
"custom\n"
"panels\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info Panels/4.0/bin:\n"
"FileInfoFoundation.swf\n"
"FileInfoUI.swf\n"
"framework.swf\n"
"loc\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info "
"Panels/4.0/bin/loc:\n"
"FileInfo_ar_AE.dat\n"
"FileInfo_bg_BG.dat\n"
"FileInfo_cs_CZ.dat\n"
"FileInfo_da_DK.dat\n"
"FileInfo_de_DE.dat\n"
"FileInfo_el_GR.dat\n"
"FileInfo_en_US.dat\n"
"FileInfo_es_ES.dat\n"
"FileInfo_et_EE.dat\n"
"FileInfo_fi_FI.dat\n"
"FileInfo_fr_FR.dat\n"
"FileInfo_he_IL.dat\n"
"FileInfo_hr_HR.dat\n"
"FileInfo_hu_HU.dat\n"
"FileInfo_it_IT.dat\n"
"FileInfo_ja_JP.dat\n"
"FileInfo_ko_KR.dat\n"
"FileInfo_lt_LT.dat\n"
"FileInfo_lv_LV.dat\n"
"FileInfo_nb_NO.dat\n"
"FileInfo_nl_NL.dat\n"
"FileInfo_pl_PL.dat\n"
"FileInfo_pt_BR.dat\n"
"FileInfo_ro_RO.dat\n"
"FileInfo_ru_RU.dat\n"
"FileInfo_sk_SK.dat\n"
"FileInfo_sl_SI.dat\n"
"FileInfo_sv_SE.dat\n"
"FileInfo_tr_TR.dat\n"
"FileInfo_uk_UA.dat\n"
"FileInfo_zh_CN.dat\n"
"FileInfo_zh_TW.dat\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info "
"Panels/4.0/custom:\n"
"DICOM.xml\n"
"Mobile.xml\n"
"loc\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info "
"Panels/4.0/custom/loc:\n"
"DICOM_ar_AE.dat\n"
"DICOM_bg_BG.dat\n"
"DICOM_cs_CZ.dat\n"
"DICOM_da_DK.dat\n"
"DICOM_de_DE.dat\n"
"DICOM_el_GR.dat\n"
"DICOM_en_US.dat\n"
"DICOM_es_ES.dat\n"
"DICOM_et_EE.dat\n"
"DICOM_fi_FI.dat\n"
"DICOM_fr_FR.dat\n"
"DICOM_he_IL.dat\n"
"DICOM_hr_HR.dat\n"
"DICOM_hu_HU.dat\n"
"DICOM_it_IT.dat\n"
"DICOM_ja_JP.dat\n"
"DICOM_ko_KR.dat\n"
"DICOM_lt_LT.dat\n"
"DICOM_lv_LV.dat\n"
"DICOM_nb_NO.dat\n"
"DICOM_nl_NL.dat\n"
"DICOM_pl_PL.dat\n"
"DICOM_pt_BR.dat\n"
"DICOM_ro_RO.dat\n"
"DICOM_ru_RU.dat\n"
"DICOM_sk_SK.dat\n"
"DICOM_sl_SI.dat\n"
"DICOM_sv_SE.dat\n"
"DICOM_tr_TR.dat\n"
"DICOM_uk_UA.dat\n"
"DICOM_zh_CN.dat\n"
"DICOM_zh_TW.dat\n"
"Mobile_ar_AE.dat\n"
"Mobile_bg_BG.dat\n"
"Mobile_cs_CZ.dat\n"
"Mobile_da_DK.dat\n"
"Mobile_de_DE.dat\n"
"Mobile_el_GR.dat\n"
"Mobile_en_US.dat\n"
"Mobile_es_ES.dat\n"
"Mobile_et_EE.dat\n"
"Mobile_fi_FI.dat\n"
"Mobile_fr_FR.dat\n"
"Mobile_he_IL.dat\n"
"Mobile_hr_HR.dat\n"
"Mobile_hu_HU.dat\n"
"Mobile_it_IT.dat\n"
"Mobile_ja_JP.dat\n"
"Mobile_ko_KR.dat\n"
"Mobile_lt_LT.dat\n"
"Mobile_lv_LV.dat\n"
"Mobile_nb_NO.dat\n"
"Mobile_nl_NL.dat\n"
"Mobile_pl_PL.dat\n"
"Mobile_pt_BR.dat\n"
"Mobile_ro_RO.dat\n"
"Mobile_ru_RU.dat\n"
"Mobile_sk_SK.dat\n"
"Mobile_sl_SI.dat\n"
"Mobile_sv_SE.dat\n"
"Mobile_tr_TR.dat\n"
"Mobile_uk_UA.dat\n"
"Mobile_zh_CN.dat\n"
"Mobile_zh_TW.dat\n"
"\n"
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
"CC.app/Contents/Application Data/Custom File Info "
"Panels/4.0/panels:\n"
"IPTC\n"
"IPTCExt\n"
"advanced\n"
"audioData\n"
"camera\n"
"categories\n"
"description\n"
"dicom\n"
"gpsData\n"
"history\n"
"mobile\n"
"origin\n"
"rawpacket";
auto ret = utils::gzipCompress(inStr.c_str(), inStr.length());
REQUIRE(ret.empty() == false);
auto decompressStr = utils::gzipDecompress(ret.data(), ret.length());
CHECK(inStr == decompressStr);
}
@@ -0,0 +1,30 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
using namespace drogon;
DROGON_TEST(HttpDate)
{
// RFC 850
auto date = utils::getHttpDate("Fri, 05-Jun-20 09:19:38 GMT");
CHECK(date.microSecondsSinceEpoch() /
trantor::Date::MICRO_SECONDS_PER_SEC ==
1591348778);
// Reddit format
date = utils::getHttpDate("Fri, 05-Jun-2020 09:19:38 GMT");
CHECK(date.microSecondsSinceEpoch() /
trantor::Date::MICRO_SECONDS_PER_SEC ==
1591348778);
// Invalid
date = utils::getHttpDate("Fri, this format is invalid");
CHECK(date.microSecondsSinceEpoch() == std::numeric_limits<int64_t>::max());
// ASC Time
auto epoch = time(nullptr);
auto str = asctime(gmtime(&epoch));
date = utils::getHttpDate(str);
CHECK(date.microSecondsSinceEpoch() /
trantor::Date::MICRO_SECONDS_PER_SEC ==
epoch);
}
@@ -0,0 +1,82 @@
#include "../../lib/src/HttpFileImpl.h"
#include <drogon/drogon_test.h>
#include <filesystem>
using namespace drogon;
using namespace std;
DROGON_TEST(HttpFile)
{
SUBSECTION(SavePathUsingDefaultConfigPath)
{
HttpFileImpl file;
file.setFileName("test_file_name");
file.setFile("test", 4);
auto out = file.save();
CHECK(out == 0);
CHECK(filesystem::exists("./uploads/test_file_name"));
filesystem::remove_all("./uploads/test_file_name");
}
SUBSECTION(SavePathWithSpecificRelativePath)
{
HttpFileImpl file;
file.setFileName("test_file_name");
file.setFile("test", 4);
auto out = file.save("./test_uploads_dir");
CHECK(out == 0);
CHECK(filesystem::exists("./test_uploads_dir/test_file_name"));
filesystem::remove_all("./test_uploads_dir");
}
SUBSECTION(SavePathWithSpecificAbsolutePath)
{
auto uploadPath = filesystem::current_path() / "test_uploads_dir";
HttpFileImpl file;
file.setFileName("test_file_name");
file.setFile("test", 4);
auto out = file.save(uploadPath.string());
CHECK(out == 0);
CHECK(filesystem::exists(uploadPath / "test_file_name"));
filesystem::remove_all(uploadPath.string());
}
SUBSECTION(FileNameWithRelativePath)
{
auto uploadPath = filesystem::current_path() / "test_uploads_dir";
HttpFileImpl file;
file.setFileName("../test_malicious_file_name");
file.setFile("test", 4);
auto out = file.save(uploadPath.string());
CHECK(out == -1);
CHECK(!filesystem::exists(uploadPath / "../test_malicious_file_name"));
filesystem::remove_all(uploadPath);
filesystem::remove(uploadPath / "../test_malicious_file_name");
}
SUBSECTION(FileNameWithAbsolutePath)
{
auto fileName = filesystem::current_path() / "test_malicious_file_name";
HttpFileImpl file;
file.setFileName(fileName.string());
file.setFile("test", 4);
auto out = file.save("./test_uploads_dir");
CHECK(out == -1);
CHECK(!filesystem::exists(fileName.string()));
filesystem::remove_all("test_uploads_dir");
filesystem::remove(fileName.string());
}
}
@@ -0,0 +1,13 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
#include <iostream>
using namespace drogon;
DROGON_TEST(HttpFullDateTest)
{
auto str = utils::getHttpFullDateStr();
auto date = utils::getHttpDate(str);
CHECK(utils::getHttpFullDate(date) == str);
}
@@ -0,0 +1,68 @@
#include <drogon/drogon_test.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include "../../lib/src/HttpResponseImpl.h"
using namespace drogon;
DROGON_TEST(HttpHeaderRequest)
{
auto req = HttpRequest::newHttpRequest();
req->addHeader("Abc", "abc");
CHECK(req->getHeader("Abc") == "abc");
CHECK(req->getHeader("abc") == "abc");
req->removeHeader("Abc");
CHECK(req->getHeader("abc") == "");
}
DROGON_TEST(HttpHeaderResponse)
{
auto resp = std::dynamic_pointer_cast<HttpResponseImpl>(
HttpResponse::newHttpResponse());
REQUIRE(resp != nullptr);
resp->addHeader("Abc", "abc");
CHECK(resp->getHeader("Abc") == "abc");
CHECK(resp->getHeader("abc") == "abc");
resp->makeHeaderString();
auto buffer = resp->renderToBuffer();
auto str = std::string{buffer->peek(), buffer->readableBytes()};
CHECK(str.find("abc") != std::string::npos);
resp->removeHeader("Abc");
buffer = resp->renderToBuffer();
str = std::string{buffer->peek(), buffer->readableBytes()};
CHECK(str.find("abc") == std::string::npos);
CHECK(resp->getHeader("abc") == "");
}
DROGON_TEST(ResponseSetCustomContentTypeString)
{
auto resp = HttpResponse::newHttpResponse();
resp->setContentTypeString("text/html");
CHECK(resp->getContentType() == CT_TEXT_HTML);
resp = HttpResponse::newHttpResponse();
resp->setContentTypeString("image/bmp");
CHECK(resp->getContentType() == CT_IMAGE_BMP);
resp = HttpResponse::newHttpResponse();
resp->setContentTypeString("thisdoesnotexist/unknown");
CHECK(resp->getContentType() == CT_CUSTOM);
}
DROGON_TEST(ResquestSetCustomContentTypeString)
{
auto req = HttpRequest::newHttpRequest();
req->setContentTypeString("text/html");
CHECK(req->getContentType() == CT_TEXT_HTML);
req = HttpRequest::newHttpRequest();
req->setContentTypeString("image/bmp");
CHECK(req->getContentType() == CT_IMAGE_BMP);
req = HttpRequest::newHttpRequest();
req->setContentTypeString("thisdoesnotexist/unknown");
CHECK(req->getContentType() == CT_CUSTOM);
}
@@ -0,0 +1,41 @@
#include <drogon/HttpViewData.h>
#include <drogon/drogon_test.h>
#include <iostream>
using namespace drogon;
DROGON_TEST(HttpViewData)
{
HttpViewData data;
data.insert("1", 1);
data.insertAsString("2", 2.0);
data.insertFormattedString("3", "third value is %d", 3);
data.insertAsString("4", "4");
data.insert("5", 5);
data.insert("5", std::string("5!!!!!!!")); // Overrides the old value
char six = 6;
data.insert("6", six);
CHECK(data.get<int>("1") == 1);
CHECK(data.get<std::string>("2") == "2");
CHECK(data.get<std::string>("3") == "third value is 3");
CHECK(data.get<std::string>("4") == "4");
CHECK(data.get<std::string>("5") == "5!!!!!!!");
CHECK(data.get<char>("6") == 6);
CHECK(data.get<int>("1") == 1); // get a second time
// Bad key returns a default constructed value
CHECK_NOTHROW(data.get<int>("this_does_not_exist"));
SUBSECTION(Translate)
{
CHECK(HttpViewData::needTranslation("") == false);
CHECK(HttpViewData::needTranslation("!)(*#") == false);
CHECK(HttpViewData::needTranslation("#include <iostream>") == true);
CHECK(HttpViewData::needTranslation("<body></body>") == true);
CHECK(HttpViewData::htmlTranslate("#include <iostream>") ==
"#include &lt;iostream&gt;");
CHECK(HttpViewData::htmlTranslate("&gt;") == "&amp;gt;");
}
}
+16
View File
@@ -0,0 +1,16 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
DROGON_TEST(Md5Test)
{
CHECK(drogon::utils::getMd5("123456789012345678901234567890123456789012345"
"678901234567890123456789012345678901234567890"
"1234567890") ==
"49CB3608E2B33FAD6B65DF8CB8F49668");
CHECK(drogon::utils::getMd5("1") == "C4CA4238A0B923820DCC509A6F75849B");
CHECK(drogon::utils::getMd5("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") ==
"59F761506DFA597B0FAF1968F7CCA867");
}
@@ -0,0 +1,42 @@
#include <drogon/HttpAppFramework.h>
#include <drogon/drogon_test.h>
#include <string>
#include <iostream>
using namespace drogon;
struct TestCookie
{
TestCookie(std::shared_ptr<test::CaseBase> ctx) : TEST_CTX(ctx)
{
}
~TestCookie()
{
if (!taken)
FAIL("Test cookie not taken");
else
SUCCESS();
}
void take()
{
taken = true;
}
protected:
bool taken = false;
std::shared_ptr<test::CaseBase> TEST_CTX;
};
DROGON_TEST(MainLoopTest)
{
auto cookie = std::make_shared<TestCookie>(TEST_CTX);
drogon::app().getLoop()->queueInLoop([cookie]() { cookie->take(); });
std::thread t([TEST_CTX]() {
auto cookie2 = std::make_shared<TestCookie>(TEST_CTX);
drogon::app().getLoop()->queueInLoop([cookie2]() { cookie2->take(); });
});
t.join();
}
@@ -0,0 +1,66 @@
#include <trantor/utils/MsgBuffer.h>
#include <drogon/drogon_test.h>
#include <string>
#include <iostream>
using namespace trantor;
DROGON_TEST(MsgBufferTest)
{
SUBSECTION(readableTest)
{
MsgBuffer buffer;
CHECK(buffer.readableBytes() == 0UL);
buffer.append(std::string(128, 'a'));
CHECK(buffer.readableBytes() == 128UL);
buffer.retrieve(100);
CHECK(buffer.readableBytes() == 28UL);
CHECK(buffer.peekInt8() == 'a');
buffer.retrieveAll();
CHECK(buffer.readableBytes() == 0UL);
}
SUBSECTION(writableTest)
{
MsgBuffer buffer(100);
CHECK(buffer.writableBytes() == 100UL);
buffer.append("abcde");
CHECK(buffer.writableBytes() == 95UL);
buffer.append(std::string(100, 'x'));
CHECK(buffer.writableBytes() == 111UL);
buffer.retrieve(100);
CHECK(buffer.writableBytes() == 111UL);
buffer.append(std::string(112, 'c'));
CHECK(buffer.writableBytes() == 99UL);
buffer.retrieveAll();
CHECK(buffer.writableBytes() == 216UL);
}
SUBSECTION(addInFrontTest)
{
MsgBuffer buffer(100);
CHECK(buffer.writableBytes() == 100UL);
buffer.addInFrontInt8('a');
CHECK(buffer.writableBytes() == 100UL);
buffer.addInFrontInt64(123);
CHECK(buffer.writableBytes() == 92UL);
buffer.addInFrontInt64(100);
CHECK(buffer.writableBytes() == 84UL);
buffer.addInFrontInt8(1);
CHECK(buffer.writableBytes() == 84UL);
}
SUBSECTION(MoveAssignmentOperator)
{
MsgBuffer buf(100);
const char *bufptr = buf.peek();
size_t writable = buf.writableBytes();
MsgBuffer buffnew(1000);
buffnew = std::move(buf);
CHECK(bufptr == buffnew.peek());
CHECK(writable == buffnew.writableBytes());
}
}
@@ -0,0 +1,132 @@
#include <drogon/MultiPart.h>
#include <drogon/drogon_test.h>
#include <drogon/HttpRequest.h>
#include "../../lib/src/MultipartStreamParser.h"
DROGON_TEST(MultiPartParser)
{
drogon::MultiPartParser parser1;
auto req = drogon::HttpRequest::newHttpRequest();
req->setMethod(drogon::Post);
req->addHeader("content-type", "multipart/form-data; boundary=\"12345\"");
req->setBody(
"--12345\r\n"
"Content-Disposition: form-data; name=\"somekey\"\r\n"
"\r\n"
"Hello; World\r\n"
"--12345--");
CHECK(0 == parser1.parse(req));
CHECK(parser1.getParameters().size() == 1);
CHECK(parser1.getParameters().at("somekey") == "Hello; World");
req->setBody(
"--12345\r\n"
"Content-Disposition: form-data; name=\"somekey\"; "
"filename=\"test\"\r\n"
"\r\n"
"Hello; World\r\n"
"--12345--");
drogon::MultiPartParser parser2;
CHECK(0 == parser2.parse(req));
auto filesMap = parser2.getFilesMap();
CHECK(filesMap.size() == 1);
CHECK(filesMap.at("somekey").getFileName() == "test");
CHECK(filesMap.at("somekey").fileContent() == "Hello; World");
req->setBody(
"--12345\r\n"
"Content-Disposition: form-data; name=\"name of pdf\"; "
"filename=\"pdf-file.pdf\"\r\n"
"Content-Type: application/octet-stream\r\n"
"content-transfer-encoding: quoted-printable\r\n"
"\r\n"
"bytes of pdf file\r\n"
"--12345--");
drogon::MultiPartParser parser3;
CHECK(0 == parser3.parse(req));
filesMap = parser3.getFilesMap();
CHECK(filesMap.size() == 1);
CHECK(filesMap.at("name of pdf").getFileName() == "pdf-file.pdf");
CHECK(filesMap.at("name of pdf").fileContent() == "bytes of pdf file");
CHECK(filesMap.at("name of pdf").getContentType() ==
drogon::CT_APPLICATION_OCTET_STREAM);
CHECK(filesMap.at("name of pdf").getContentTransferEncoding() ==
"quoted-printable");
req->setBody(
"--12345\r\n"
"Content-Disposition: form-data; name=\"some;key\"\r\n"
"\r\n"
"Hello; World\r\n"
"--12345--");
drogon::MultiPartParser parser4;
CHECK(0 == parser4.parse(req));
CHECK(parser4.getParameters().size() == 1);
CHECK(parser4.getParameters().at("some;key") == "Hello; World");
}
DROGON_TEST(MultiPartStreamParser)
{
static const std::string ct = "multipart/form-data; boundary=\"12345\"";
static const std::string_view data =
"--12345\r\n"
"Content-Disposition: form-data; name=\"key1\"; filename=\"file1\"\r\n"
"\r\n"
"Hello; World\r\n"
"--12345\r\n"
"Content-Disposition: form-data; name=\"key2\"\r\n"
"\r\n"
"value2\r\n"
"--12345--";
struct Entry
{
drogon::MultipartHeader header;
std::string value;
std::string fileContent;
};
auto check = [TEST_CTX](size_t step) {
drogon::MultipartStreamParser parser(ct);
auto entries = std::make_shared<std::vector<Entry>>();
auto headerCb = [TEST_CTX, entries](drogon::MultipartHeader hdr) {
entries->emplace_back(Entry{std::move(hdr)});
};
auto dataCb = [TEST_CTX, entries](const char *data, size_t length) {
MANDATE(!entries->empty());
if (length == 0)
{
// Field finished
return;
}
if (entries->back().header.filename.empty())
{
entries->back().value.append(data, length);
}
else
{
entries->back().fileContent.append(data, length);
}
};
size_t i = 0;
while (i < data.length() && parser.isValid())
{
size_t end = i + step < data.length() ? i + step : data.length();
parser.parse(data.data() + i, end - i, headerCb, dataCb);
CHECK(parser.isValid());
i = end;
}
MANDATE(i == data.length());
MANDATE(parser.isFinished());
MANDATE(entries->size() == 2);
CHECK(entries->at(0).header.name == "key1");
CHECK(entries->at(0).fileContent == "Hello; World");
CHECK(entries->at(1).header.name == "key2");
CHECK(entries->at(1).value == "value2");
};
check(1);
check(3);
check(7);
check(20);
}
@@ -0,0 +1,77 @@
#include <drogon/utils/OStringStream.h>
#include <drogon/drogon_test.h>
#include <string>
#include <string_view>
#include <iostream>
DROGON_TEST(OStringStreamTest)
{
SUBSECTION(CanConvertToString)
{
using drogon::internal::CanConvertToString;
static_assert(CanConvertToString<int>::value);
static_assert(CanConvertToString<long>::value);
static_assert(CanConvertToString<long long>::value);
static_assert(CanConvertToString<unsigned>::value);
static_assert(CanConvertToString<unsigned long>::value);
static_assert(CanConvertToString<unsigned long long>::value);
static_assert(CanConvertToString<float>::value);
static_assert(CanConvertToString<double>::value);
static_assert(CanConvertToString<long double>::value);
static_assert(!CanConvertToString<std::string>::value);
static_assert(!CanConvertToString<std::string_view>::value);
}
SUBSECTION(integer)
{
drogon::OStringStream ss;
ss << 12;
ss << 345L;
CHECK(ss.str() == "12345");
}
SUBSECTION(float_number)
{
drogon::OStringStream ss;
ss << 3.14f;
ss << 3.1416;
CHECK(ss.str() == "3.143.1416");
}
SUBSECTION(literal_string)
{
drogon::OStringStream ss;
ss << "hello";
ss << " world";
CHECK(ss.str() == "hello world");
}
SUBSECTION(std::string_view)
{
drogon::OStringStream ss;
ss << std::string_view("hello");
ss << std::string_view(" world");
CHECK(ss.str() == "hello world");
}
SUBSECTION(std_string)
{
drogon::OStringStream ss;
ss << std::string("hello");
ss << std::string(" world");
CHECK(ss.str() == "hello world");
}
SUBSECTION(mix)
{
drogon::OStringStream ss;
ss << std::string("hello");
ss << std::string_view(" world");
ss << "!";
ss << 123;
ss << 3.14f;
CHECK(ss.str() == "hello world!1233.14");
}
}
@@ -0,0 +1,18 @@
#include <drogon/PubSubService.h>
#include <drogon/drogon_test.h>
DROGON_TEST(PubSubServiceTest)
{
drogon::PubSubService<std::string> service;
auto id = service.subscribe("topic1",
[TEST_CTX](const std::string &topic,
const std::string &message) {
CHECK(topic == "topic1");
CHECK(message == "hello world");
});
service.publish("topic1", "hello world");
service.publish("topic2", "hello world");
CHECK(service.size() == 1UL);
service.unsubscribe("topic1", id);
CHECK(service.size() == 0UL);
}
+12
View File
@@ -0,0 +1,12 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
DROGON_TEST(SHA1Test)
{
char in[] =
"1234567890123456789012345678901234567890123456789012345"
"678901234567890123456789012345678901234567890";
auto str = drogon::utils::getSha1(in, strlen((const char *)in));
CHECK(str == "FECFD28BBC9345891A66D7C1B8FF46E60192D284");
}
@@ -0,0 +1,179 @@
#include <drogon/drogon_test.h>
#include <../../lib/src/SlashRemover.cc>
#include <string>
using std::string;
DROGON_TEST(SlashRemoverTest)
{
string cleanUrl;
{ // Regular URL
const string urlNoTrail = "///home//page", urlNoDup = "/home/page/",
urlNoExcess = "/home/page";
SUBSECTION(Full)
{
const string url = "///home//page//";
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoTrail);
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoDup);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
SUBSECTION(Partial)
{
removeExcessiveSlashes(cleanUrl,
findExcessiveSlashes(urlNoTrail),
urlNoTrail);
CHECK(cleanUrl == urlNoExcess);
removeExcessiveSlashes(cleanUrl,
findExcessiveSlashes(urlNoDup),
urlNoDup);
CHECK(cleanUrl == urlNoExcess);
}
}
SUBSECTION(Root)
{
const string urlNoExcess = "/";
{ // Overlapping indices
const string url = "//";
cleanUrl = url;
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoExcess);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
{ // Intersecting indices
const string url = "///";
cleanUrl = url;
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoExcess);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
}
SUBSECTION(Overlap)
{
const string urlNoDup = "/a/", urlNoExcess = "/a";
{ // Overlapping indices
const string url = "/a//";
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoDup);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
{ // Intersecting indices
const string url = "/a///";
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoDup);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
}
SUBSECTION(NoTrail)
{
const string url = "//a", urlNoExcess = "/a";
cleanUrl.clear();
{
auto find = findTrailingSlashes(url);
if (find != string::npos)
removeTrailingSlashes(cleanUrl, find, url);
}
CHECK(cleanUrl.empty());
cleanUrl = url;
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
CHECK(cleanUrl == urlNoExcess);
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
SUBSECTION(NoDuplicate)
{
const string url = "/a/", urlNoExcess = "/a";
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
cleanUrl = url;
{
auto find = findDuplicateSlashes(cleanUrl);
if (find != string::npos)
removeDuplicateSlashes(cleanUrl, find);
}
CHECK(cleanUrl == url);
cleanUrl = url;
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
CHECK(cleanUrl == urlNoExcess);
}
SUBSECTION(None)
{
const string url = "/a";
cleanUrl.clear();
{
auto find = findTrailingSlashes(url);
if (find != string::npos)
removeTrailingSlashes(cleanUrl, find, url);
}
CHECK(cleanUrl.empty());
cleanUrl = url;
{
auto find = findDuplicateSlashes(cleanUrl);
if (find != string::npos)
removeDuplicateSlashes(cleanUrl, find);
}
CHECK(cleanUrl == url);
cleanUrl.clear();
{
auto find = findExcessiveSlashes(url);
if (find.first != string::npos || find.second != string::npos)
removeExcessiveSlashes(cleanUrl, find, url);
}
CHECK(cleanUrl.empty());
}
}
@@ -0,0 +1,92 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
struct SameContent
{
SameContent(const std::vector<std::string> &container)
: container_(container.begin(), container.end())
{
}
std::vector<std::string> container_;
};
template <typename Container1>
inline bool operator==(const Container1 &a, const SameContent &wrapper)
{
const auto &b = wrapper.container_;
if (a.size() != b.size())
return false;
auto ait = a.begin();
auto bit = b.begin();
while (ait != a.end() && bit != b.end())
{
if (*ait != *bit)
break;
ait++;
bit++;
}
return ait == a.end() && bit == b.end();
}
using namespace drogon;
DROGON_TEST(StringOpsTest)
{
SUBSECTION(SplitString)
{
std::string str = "1,2,3,3,,4";
CHECK(utils::splitString(str, ",") ==
SameContent({"1", "2", "3", "3", "4"}));
CHECK(utils::splitString(str, ",", true) ==
SameContent({"1", "2", "3", "3", "", "4"}));
CHECK(utils::splitString(str, "|", true) ==
SameContent({"1,2,3,3,,4"}));
str = "a||b||c||||";
CHECK(utils::splitString(str, "||") == SameContent({"a", "b", "c"}));
CHECK(utils::splitString(str, "||", true) ==
SameContent({"a", "b", "c", "", ""}));
str = "aabbbaabbbb";
CHECK(utils::splitString(str, "bb") == SameContent({"aa", "baa"}));
CHECK(utils::splitString(str, "bb", true) ==
SameContent({"aa", "baa", "", ""}));
str = "";
CHECK(utils::splitString(str, ",") == SameContent({}));
CHECK(utils::splitString(str, ",", true) == SameContent({""}));
}
SUBSECTION(SplitStringToSet)
{
// splitStringToSet ignores empty strings
std::string str = "1,2,3,3,,4";
auto s = utils::splitStringToSet(str, ",");
CHECK(s.size() == 4UL);
CHECK(s.count("1") == 1UL);
CHECK(s.count("2") == 1UL);
CHECK(s.count("3") == 1UL);
CHECK(s.count("4") == 1UL);
str = "a|||a";
s = utils::splitStringToSet(str, "||");
CHECK(s.size() == 2UL);
CHECK(s.count("a") == 1UL);
CHECK(s.count("|a") == 1UL);
}
SUBSECTION(ReplaceAll)
{
std::string str = "3.14159";
utils::replaceAll(str, "1", "a");
CHECK(str == "3.a4a59");
str = "aaxxxaaxxxxaaxxxxx";
utils::replaceAll(str, "xx", "oo");
CHECK(str == "aaooxaaooooaaoooox");
}
}
@@ -0,0 +1,14 @@
#include <string_view>
#include <drogon/utils/Utilities.h>
#include <iostream>
#include <drogon/drogon_test.h>
DROGON_TEST(URLCodec)
{
std::string input = "k1=1&k2=安";
auto encoded = drogon::utils::urlEncode(input);
auto decoded = drogon::utils::urlDecode(encoded);
CHECK(encoded == "k1=1&k2=%E5%AE%89");
CHECK(input == decoded);
}
@@ -0,0 +1,101 @@
#include <string>
#include <string_view>
#include <istream>
#include <array>
#include <vector>
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
struct ConvertibleFromStringStream
{
friend std::istream &operator>>(std::istream &os,
ConvertibleFromStringStream &)
{
return os;
}
};
struct NotConvertibleFromStringStream
{
};
DROGON_TEST(CanConvertFromStringStream)
{
using drogon::internal::CanConvertFromStringStream;
static_assert(CanConvertFromStringStream<unsigned short>::value);
static_assert(CanConvertFromStringStream<unsigned int>::value);
static_assert(CanConvertFromStringStream<long>::value);
static_assert(CanConvertFromStringStream<unsigned long>::value);
static_assert(CanConvertFromStringStream<long long>::value);
static_assert(CanConvertFromStringStream<unsigned long long>::value);
static_assert(CanConvertFromStringStream<float>::value);
static_assert(CanConvertFromStringStream<double>::value);
static_assert(CanConvertFromStringStream<long double>::value);
static_assert(CanConvertFromStringStream<bool>::value);
static_assert(CanConvertFromStringStream<void *>::value);
static_assert(CanConvertFromStringStream<short>::value);
static_assert(CanConvertFromStringStream<int>::value);
static_assert(
CanConvertFromStringStream<ConvertibleFromStringStream>::value);
static_assert(
!CanConvertFromStringStream<NotConvertibleFromStringStream>::value);
}
struct ConstructibleFromString
{
ConstructibleFromString(const std::string &)
{
}
};
struct NotConstructibleFromString
{
};
DROGON_TEST(CanConstructFromString)
{
using drogon::internal::CanConstructFromString;
static_assert(CanConstructFromString<ConstructibleFromString>::value);
static_assert(!CanConstructFromString<NotConstructibleFromString>::value);
static_assert(!CanConstructFromString<int>::value);
static_assert(!CanConstructFromString<double>::value);
static_assert(CanConstructFromString<std::string>::value);
static_assert(CanConstructFromString<std::string_view>::value);
static_assert(CanConstructFromString<const std::string>::value);
static_assert(!CanConstructFromString<std::string &>::value);
static_assert(CanConstructFromString<const std::string &>::value);
static_assert(!CanConstructFromString<std::string *>::value);
}
struct ConvertibleFromString
{
ConvertibleFromString &operator=(const std::string &)
{
return *this;
}
};
struct NotConvertibleFromString
{
};
DROGON_TEST(CanConvertFromString)
{
using drogon::internal::CanConvertFromString;
static_assert(CanConvertFromString<ConvertibleFromString>::value);
static_assert(!CanConvertFromString<NotConvertibleFromString>::value);
static_assert(CanConvertFromString<std::string>::value);
static_assert(!CanConvertFromString<const std::string>::value);
static_assert(!CanConvertFromString<const std::string &>::value);
static_assert(!CanConvertFromString<std::string *>::value);
static_assert(CanConvertFromString<std::string_view>::value);
static_assert(!CanConvertFromString<const char *>::value);
static_assert(!CanConvertFromString<std::vector<char>>::value);
static_assert(!CanConvertFromString<std::array<char, 5>>::value);
static_assert(!CanConvertFromString<int>::value);
static_assert(!CanConvertFromString<double>::value);
}
@@ -0,0 +1,17 @@
#include <string_view>
#include <drogon/utils/Utilities.h>
#include <iostream>
#include <drogon/drogon_test.h>
DROGON_TEST(UuidTest)
{
auto uuid = drogon::utils::getUuid();
std::cout << "uuid: " << uuid << std::endl;
CHECK(uuid[8] == '-');
CHECK(uuid[13] == '-');
CHECK(uuid[18] == '-');
CHECK(uuid[23] == '-');
CHECK(uuid.size() == 36);
}
@@ -0,0 +1,40 @@
#include <drogon/drogon_test.h>
#include "../../lib/src/HttpRequestImpl.h"
#include "../../lib/src/HttpResponseImpl.h"
#include "../../lib/src/HttpControllerBinder.h"
using namespace drogon;
DROGON_TEST(WebsocketReponseTest)
{
WebsocketControllerBinder binder;
auto reqPtr = std::make_shared<HttpRequestImpl>(nullptr);
// Value from rfc6455-1.3
reqPtr->addHeader("sec-websocket-key", "dGhlIHNhbXBsZSBub25jZQ==");
binder.handleRequest(reqPtr, [&](const HttpResponsePtr &resp) {
CHECK(resp->statusCode() == k101SwitchingProtocols);
CHECK(resp->headers().size() == 3);
CHECK(resp->getHeader("Upgrade") == "websocket");
CHECK(resp->getHeader("Connection") == "Upgrade");
// Value from rfc6455-1.3
CHECK(resp->getHeader("Sec-WebSocket-Accept") ==
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
auto implPtr = std::dynamic_pointer_cast<HttpResponseImpl>(resp);
implPtr->makeHeaderString();
auto buffer = implPtr->renderToBuffer();
auto str = std::string{buffer->peek(), buffer->readableBytes()};
CHECK(str.find("upgrade: websocket") != std::string::npos);
CHECK(str.find("connection: Upgrade") != std::string::npos);
CHECK(str.find("sec-websocket-accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") !=
std::string::npos);
CHECK(str.find("content-length:") == std::string::npos);
});
}
+52
View File
@@ -0,0 +1,52 @@
#define DROGON_TEST_MAIN
#include <drogon/drogon_test.h>
#include <drogon/HttpAppFramework.h>
using namespace drogon;
using namespace trantor;
DROGON_TEST(TestFrameworkSelfTest)
{
CHECK(TEST_CTX->name() == "TestFrameworkSelfTest");
CHECK(true);
CHECK(false != true);
CHECK(1 * 2 == 1 + 1);
CHECK(42 < 100);
CHECK(0xff <= 255);
CHECK('a' >= 'a');
CHECK(3.14159 > 2.71828);
CHECK(nullptr == nullptr);
CHECK_THROWS(throw std::runtime_error("test exception"));
CHECK_THROWS_AS(throw std::domain_error("test exception"),
std::domain_error);
CHECK_NOTHROW([] { return 0; }());
STATIC_REQUIRE(std::is_standard_layout<int>::value);
STATIC_REQUIRE(std::is_default_constructible<test::Case>::value == false);
auto child_test = std::make_shared<test::Case>(TEST_CTX, "ChildTest");
CHECK(child_test->fullname() == "TestFrameworkSelfTest.ChildTest");
// Unlike Catch2, a subsection in drogon test does not provide a fixture
// It's only a way to signify testing different for things
SUBSECTION(Subsection)
{
CHECK(TEST_CTX->fullname() == "TestFrameworkSelfTest.Subsection");
}
}
int main(int argc, char **argv)
{
std::promise<void> p1;
std::future<void> f1 = p1.get_future();
std::thread thr([&]() {
p1.set_value();
app().run();
});
f1.get();
int testStatus = test::run(argc, argv);
app().getLoop()->queueInLoop([]() { app().quit(); });
thr.join();
return testStatus;
}