From c6b65485e1ee9698ec506e0e6f027c14fb15d9be Mon Sep 17 00:00:00 2001 From: Martin Chang Date: Tue, 1 Jun 2021 16:08:51 +0800 Subject: [PATCH] Add minimal server side examples (#880) --- examples/CMakeLists.txt | 19 ++++- examples/README.md | 11 ++- examples/file_upload/FileUpload.csp | 83 ++++++++++++++++++++++ examples/file_upload/file_upload.cc | 46 ++++++++++++ examples/helloworld/HelloController.cc | 40 +++++++++++ examples/helloworld/HelloView.csp | 24 +++++++ examples/helloworld/HelloViewController.cc | 29 ++++++++ examples/helloworld/main.cc | 70 ++++++++++++++++++ examples/login_session/LoginPage.csp | 22 ++++++ examples/login_session/LogoutPage.csp | 16 +++++ examples/login_session/main.cc | 67 +++++++++++++++++ 11 files changed, 423 insertions(+), 4 deletions(-) create mode 100644 examples/file_upload/FileUpload.csp create mode 100644 examples/file_upload/file_upload.cc create mode 100644 examples/helloworld/HelloController.cc create mode 100644 examples/helloworld/HelloView.csp create mode 100644 examples/helloworld/HelloViewController.cc create mode 100644 examples/helloworld/main.cc create mode 100644 examples/login_session/LoginPage.csp create mode 100644 examples/login_session/LogoutPage.csp create mode 100644 examples/login_session/main.cc diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index fb432dad..3b9bf582 100755 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -6,11 +6,28 @@ set(benchmark_sources benchmark/BenchmarkCtrl.cc benchmark/JsonCtrl.cc add_executable(client client_example/main.cc) add_executable(websocket_client websocket_client/WebSocketClient.cc) add_executable(benchmark ${benchmark_sources}) +add_executable(helloworld helloworld/main.cc + helloworld/HelloController.cc + helloworld/HelloViewController.cc) +drogon_create_views(helloworld + ${CMAKE_CURRENT_SOURCE_DIR}/helloworld + ${CMAKE_CURRENT_BINARY_DIR}) +add_executable(file_upload file_upload/file_upload.cc) +drogon_create_views(file_upload + ${CMAKE_CURRENT_SOURCE_DIR}/file_upload + ${CMAKE_CURRENT_BINARY_DIR}) +add_executable(login_session login_session/main.cc) +drogon_create_views(login_session + ${CMAKE_CURRENT_SOURCE_DIR}/login_session + ${CMAKE_CURRENT_BINARY_DIR}) set(example_targets benchmark client - websocket_client) + websocket_client + helloworld + file_upload + login_session) set_property(TARGET ${example_targets} PROPERTY CXX_STANDARD ${DROGON_CXX_STANDARD}) diff --git a/examples/README.md b/examples/README.md index 464d52dd..b2fab4b0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,10 +2,15 @@ The following examples can help you understand how to use Drogon: -1. [benchmark](https://github.com/an-tao/drogon/tree/master/examples/benchmark) - Basic benchmark example. see [wiki benchmarks](https://github.com/an-tao/drogon/wiki/13-Benchmarks) +1. [hellowrold](https://github.com/an-tao/drogon/tree/master/examples/hellowrold) - The multuple ways of "Hello, World!" 2. [client_example](https://github.com/an-tao/drogon/tree/master/examples/client_example/main.cc) - A client example. -3. [simple_reverse_proxy](https://github.com/an-tao/drogon/tree/master/examples/simple_reverse_proxy) - A Example showing how to use drogon as a http reverse proxy with a simple round robin. -4. [websocket_client](https://github.com/an-tao/drogon/tree/master/examples/websocket_client/WebSocketClient.cc) - An example on how to use the WebSocket client +3. [websocket_client](https://github.com/an-tao/drogon/tree/master/examples/websocket_client/WebSocketClient.cc) - An example on how to use the WebSocket client +4. [login_session](https://github.com/an-tao/drogon/tree/master/examples/login_session) - How to use the built-in session system to handle login and out +5. [file_upload](https://github.com/an-tao/drogon/tree/master/examples/file_upload) - How to handle file uploads in Drogon +6. [simple_reverse_proxy](https://github.com/an-tao/drogon/tree/master/examples/simple_reverse_proxy) - A Example showing how to use drogon as a http reverse +proxy with a simple round robin. +7. [benchmark](https://github.com/an-tao/drogon/tree/master/examples/benchmark) - Basic benchmark example. see [wiki benchmarks](https://github.com/an-tao/drogon/wiki/13-Benchmarks) + ### [TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks) test suite diff --git a/examples/file_upload/FileUpload.csp b/examples/file_upload/FileUpload.csp new file mode 100644 index 00000000..d21ccf9e --- /dev/null +++ b/examples/file_upload/FileUpload.csp @@ -0,0 +1,83 @@ + + + + + File upload + + + + + +

+ + + + + diff --git a/examples/file_upload/file_upload.cc b/examples/file_upload/file_upload.cc new file mode 100644 index 00000000..625c7a78 --- /dev/null +++ b/examples/file_upload/file_upload.cc @@ -0,0 +1,46 @@ +#include +using namespace drogon; + +int main() +{ + app().registerHandler( + "/", + [](const HttpRequestPtr &req, + std::function &&callback) { + auto resp = HttpResponse::newHttpViewResponse("FileUpload"); + callback(resp); + }); + + app().registerHandler( + "/upload_endpoint", + [](const HttpRequestPtr &req, + std::function &&callback) { + MultiPartParser fileUpload; + if (fileUpload.parse(req) != 0 || fileUpload.getFiles().size() != 1) + { + auto resp = HttpResponse::newHttpResponse(); + resp->setBody("Must only be 1 file"); + resp->setStatusCode(k403Forbidden); + callback(resp); + return; + } + + auto &file = fileUpload.getFiles()[0]; + auto md5 = file.getMd5(); + auto resp = HttpResponse::newHttpResponse(); + resp->setBody( + "The server has calculated the file's MD5 hash to be " + md5); + file.save(); + LOG_INFO << "The uploaded file have been saved to the ./uploads " + "directory"; + callback(resp); + }, + {Post}); + + LOG_INFO << "Server running on 127.0.0.1:8848"; + app() + .setClientMaxBodySize(20 * 2000 * 2000) + .setUploadPath("./uploads") + .addListener("127.0.0.1", 8848) + .run(); +} \ No newline at end of file diff --git a/examples/helloworld/HelloController.cc b/examples/helloworld/HelloController.cc new file mode 100644 index 00000000..39e67167 --- /dev/null +++ b/examples/helloworld/HelloController.cc @@ -0,0 +1,40 @@ +#include + +using namespace drogon; + +// HttpControllers are automatically added to Drogon upon Drogon initializing. +class SayHello : public HttpController +{ + public: + METHOD_LIST_BEGIN + // Drogon automatically appends the namespce and name of the controller to + // the handlers of the controller. In this example, although we are adding + // a handler to /. But because it is a part of the SayHello controller. It + // ends up in path /SayHello/ (IMOPRTANT! It is /SayHello/ not /SayHello + // they are different paths). + METHOD_ADD(SayHello::genericHello, "/", Get); + // Same for /hello. It ends up at /SayHello/hello + METHOD_ADD(SayHello::personalizedHello, "/hello", Get); + METHOD_LIST_END + + protected: + void genericHello(const HttpRequestPtr &req, + std::function &&callback) + { + auto resp = HttpResponse::newHttpResponse(); + resp->setBody( + "Hello, this is a generic hello message from the SayHello " + "controller"); + callback(resp); + } + + void personalizedHello( + const HttpRequestPtr &req, + std::function &&callback) + { + auto resp = HttpResponse::newHttpResponse(); + resp->setBody( + "Hi there, this is another hello from the SayHello Controller"); + callback(resp); + } +}; \ No newline at end of file diff --git a/examples/helloworld/HelloView.csp b/examples/helloworld/HelloView.csp new file mode 100644 index 00000000..064bb6eb --- /dev/null +++ b/examples/helloworld/HelloView.csp @@ -0,0 +1,24 @@ + + +<%c++ + auto name=@@.get("name"); + bool nameIsEmpty = name == ""; + if (nameIsEmpty) + name = "anonymous"; + auto message = "Hello, " + name + " from a CSP template"; +%> + + + [[ name ]] + + + <%c++ $$< + <%c++ + if (nameIsEmpty) + { + $$ << "
" + << "You can revisit the same page and append ?name=your_name to change the name"; + } + %> + + diff --git a/examples/helloworld/HelloViewController.cc b/examples/helloworld/HelloViewController.cc new file mode 100644 index 00000000..30023f7e --- /dev/null +++ b/examples/helloworld/HelloViewController.cc @@ -0,0 +1,29 @@ +#include +#include + +using namespace drogon; + +// HttpSimpleController does not allow registration of multiple handlers. +// Instead, it has one handler - asyncHandleHttpRequest. The +// HttpSimpleController is a lightweight class designed to handle really simple +// cases. +class HelloViewController : public HttpSimpleController +{ + public: + PATH_LIST_BEGIN + // Also unlike HttpContoller, HttpSimpleController does not automatically + // prepend the namespace and class name to the path. Thus the path of this + // controller is at "/view". + PATH_ADD("/view") + PATH_LIST_END + + void asyncHandleHttpRequest( + const HttpRequestPtr &req, + std::function &&callback) override + { + HttpViewData data; + data["name"] = req->getParameter("name"); + auto resp = HttpResponse::newHttpViewResponse("HelloView", data); + callback(resp); + } +}; \ No newline at end of file diff --git a/examples/helloworld/main.cc b/examples/helloworld/main.cc new file mode 100644 index 00000000..030e52db --- /dev/null +++ b/examples/helloworld/main.cc @@ -0,0 +1,70 @@ +#include +using namespace drogon; + +int main() +{ + // `registerHandler()` adds a handler to the desired path. The handler is + // responsible for generating a HTTP response upon an HTTP request being + // sent to Drogon + app().registerHandler( + "/", + [](const HttpRequestPtr &req, + std::function &&callback) { + auto resp = HttpResponse::newHttpResponse(); + resp->setBody("Hello, World!"); + callback(resp); + }, + {Get}); + + // `registrHandler()` also supports parsing and passing the path as + // parameters to the handler. Parameters are specified using {}. The text + // indide {} does not correspond to the index of parameter passed to the + // handler (nor it has any meaning). Instead, it is only to make it easier + // for users to recognize the function of each parameter. + app().registerHandler( + "/user/{user-name}", + [](const HttpRequestPtr &req, + std::function &&callback, + const std::string &name) { + auto resp = HttpResponse::newHttpResponse(); + resp->setBody("Hello, " + name + "!"); + callback(resp); + }, + {Get}); + + // You can aslo specsify that the parameter is in the query section of the + // URL! + app().registerHandler( + "/hello?user={user-name}", + [](const HttpRequestPtr &req, + std::function &&callback, + const std::string &name) { + auto resp = HttpResponse::newHttpResponse(); + resp->setBody("Hello, " + name + "!"); + callback(resp); + }, + {Get}); + + // Or, if you want to, instead of asking drogon to parse it fot you. You can + // parse the request yourselves. + app().registerHandler( + "/hello_user", + [](const HttpRequestPtr &req, + std::function &&callback) { + auto resp = HttpResponse::newHttpResponse(); + std::string name = req->getParameter("user"); + if (name == "") + resp->setBody("Please tell me your name"); + else + resp->setBody("Hello, " + name + "!"); + callback(resp); + }, + {Get}); + + // Ask Drogon to listern on 127.0.0.1 port 8848. Drogon supports listerning + // on multuiple IP addresses by adding multiple listeners. For example, if + // you want the server also Listen on 127.0.0.1 port 5555. Just add another + // line of addListener("127.0.0.1", 5555) + LOG_INFO << "Server running on 127.0.0.1:8848"; + app().addListener("127.0.0.1", 8848).run(); +} \ No newline at end of file diff --git a/examples/login_session/LoginPage.csp b/examples/login_session/LoginPage.csp new file mode 100644 index 00000000..8d521997 --- /dev/null +++ b/examples/login_session/LoginPage.csp @@ -0,0 +1,22 @@ + + + + + session example + + +
+
+ + + + + + + +
+
+ +

Please login with the credential
Username: user
Password: password123

+ + diff --git a/examples/login_session/LogoutPage.csp b/examples/login_session/LogoutPage.csp new file mode 100644 index 00000000..1363c16a --- /dev/null +++ b/examples/login_session/LogoutPage.csp @@ -0,0 +1,16 @@ + + + + + session example + + +
+
+ +
+
+ +

You can logout now

+ + diff --git a/examples/login_session/main.cc b/examples/login_session/main.cc new file mode 100644 index 00000000..8ab1e941 --- /dev/null +++ b/examples/login_session/main.cc @@ -0,0 +1,67 @@ +#include +#include +using namespace drogon; +using namespace std::chrono_literals; + +int main() +{ + app().registerHandler( + "/", + [](const HttpRequestPtr &req, + std::function &&callback) { + bool logined = + req->session()->getOptional("logined").value_or(false); + HttpResponsePtr resp; + if (logined == false) + resp = HttpResponse::newHttpViewResponse("LoginPage"); + else + resp = HttpResponse::newHttpViewResponse("LogoutPage"); + callback(resp); + }); + + app().registerHandler( + "/logout", + [](const HttpRequestPtr &req, + std::function &&callback) { + HttpResponsePtr resp = HttpResponse::newHttpResponse(); + req->session()->erase("logined"); + resp->setBody(""); + callback(resp); + }, + {Post}); + + app().registerHandler( + "/login", + [](const HttpRequestPtr &req, + std::function &&callback) { + HttpResponsePtr resp = HttpResponse::newHttpResponse(); + std::string user = req->getParameter("user"); + std::string passwd = req->getParameter("passwd"); + + // NOTE: Do not, ever, use MD5 for password hash. We only use it + // because Drogon is not a cryptography library, so deosn't come + // with a better hash. Use Argon2 or BCrypt in a real product. + // username: user, pasword: password123 + if (user == "user" && utils::getMd5("jadsjhdsajkh" + passwd) == + "5B5299CF4CEAE2D523315694B82573C9") + { + req->session()->insert("logined", true); + resp->setBody(""); + callback(resp); + } + else + { + resp->setStatusCode(k401Unauthorized); + resp->setBody(""); + callback(resp); + } + }, + {Post}); + + LOG_INFO << "Server running on 127.0.0.1:8848"; + app() + // All sessions are good for 24 Hrs + .enableSession(24h) + .addListener("127.0.0.1", 8848) + .run(); +} \ No newline at end of file