Add minimal server side examples (#880)

This commit is contained in:
Martin Chang 2021-06-01 16:08:51 +08:00 committed by GitHub
parent e1cbd1b987
commit c6b65485e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 423 additions and 4 deletions

View File

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

View File

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

View File

@ -0,0 +1,83 @@
<!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 = "/upload_endpoint";
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 = evt.target.responseText;
alert("File have been uploaded.\n" + data);
}
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>

View File

@ -0,0 +1,46 @@
#include <drogon/drogon.h>
using namespace drogon;
int main()
{
app().registerHandler(
"/",
[](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
auto resp = HttpResponse::newHttpViewResponse("FileUpload");
callback(resp);
});
app().registerHandler(
"/upload_endpoint",
[](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&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();
}

View File

@ -0,0 +1,40 @@
#include <drogon/HttpController.h>
using namespace drogon;
// HttpControllers are automatically added to Drogon upon Drogon initializing.
class SayHello : public HttpController<SayHello>
{
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<void(const HttpResponsePtr &)> &&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<void(const HttpResponsePtr &)> &&callback)
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody(
"Hi there, this is another hello from the SayHello Controller");
callback(resp);
}
};

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<%c++
auto name=@@.get<std::string>("name");
bool nameIsEmpty = name == "";
if (nameIsEmpty)
name = "anonymous";
auto message = "Hello, " + name + " from a CSP template";
%>
<head>
<meta charset="UTF-8">
<title>[[ name ]]</title>
</head>
<body>
<%c++ $$<<message; %>
<%c++
if (nameIsEmpty)
{
$$ << "<br>"
<< "You can revisit the same page and append ?name=<i>your_name</i> to change the name";
}
%>
</body>
</html>

View File

@ -0,0 +1,29 @@
#include <drogon/HttpSimpleController.h>
#include <drogon/HttpResponse.h>
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<HelloViewController>
{
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<void(const HttpResponsePtr &)> &&callback) override
{
HttpViewData data;
data["name"] = req->getParameter("name");
auto resp = HttpResponse::newHttpViewResponse("HelloView", data);
callback(resp);
}
};

View File

@ -0,0 +1,70 @@
#include <drogon/drogon.h>
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<void(const HttpResponsePtr &)> &&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<void(const HttpResponsePtr &)> &&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<void(const HttpResponsePtr &)> &&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<void(const HttpResponsePtr &)> &&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();
}

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>session example</title>
</head>
<body>
<form action="/login" method="post">
<div class="container">
<label for="user"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="user" required>
<label for="passwd"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="passwd" required>
<button type="submit">Login</button>
</div>
</form>
<p>Please login with the credential<br>Username: user<br>Password: password123</p>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>session example</title>
</head>
<body>
<form action="/logout" method="post">
<div class="container">
<button type="submit">Logout</button>
</div>
</form>
<p>You can logout now</p>
</body>
</html>

View File

@ -0,0 +1,67 @@
#include <drogon/drogon.h>
#include <chrono>
using namespace drogon;
using namespace std::chrono_literals;
int main()
{
app().registerHandler(
"/",
[](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
bool logined =
req->session()->getOptional<bool>("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<void(const HttpResponsePtr &)> &&callback) {
HttpResponsePtr resp = HttpResponse::newHttpResponse();
req->session()->erase("logined");
resp->setBody("<script>window.location.href = \"/\";</script>");
callback(resp);
},
{Post});
app().registerHandler(
"/login",
[](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&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("<script>window.location.href = \"/\";</script>");
callback(resp);
}
else
{
resp->setStatusCode(k401Unauthorized);
resp->setBody("<script>window.location.href = \"/\";</script>");
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();
}