drogon Documentation

Drogon: A C++14/17/20 based HTTP web application framework running on Linux/macOS/Unix/Windows

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy drogon on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name drogon --restart always drogonframework/drogon:latest

File: README.md





English | 简体中文 | 繁體中文

Overview

Drogon is a C++17/20 based HTTP application framework. Drogon can be used to easily build various types of web application server programs using C++. Drogon is the name of a dragon from the American TV series Game of Thrones, which I really enjoy.

Drogon is a cross-platform framework, It supports Linux, macOS, FreeBSD, OpenBSD, HaikuOS, and Windows. Its main features are as follows:

  • 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 TFB Tests Results for more details;
  • Provide a completely asynchronous programming mode;
  • Support HTTP 1.0/1.1 (server side and client side);
  • Based on template, a simple reflection mechanism is implemented to completely decouple the main program framework, controllers and views.
  • Support cookies and built-in sessions;
  • Support back-end rendering, the controller generates the data to the view to generate the HTML page. Views are described by CSP template files, C++ codes are embedded into HTML pages through CSP tags. And the drogon command-line tool automatically generates the C++ code files for compilation;
  • Support view page dynamic loading (dynamic compilation and loading at runtime);
  • Provide a convenient and flexible routing solution from the path to the controller handler;
  • Support filter chains to facilitate the execution of unified logic (such as login verification, Http Method constraint verification, etc.) before handling HTTP requests;
  • Support HTTPS (based on OpenSSL);
  • Support WebSocket (server side and client side);
  • Support JSON format request and response, very friendly to the Restful API application development;
  • Support file download and upload;
  • Support gzip, brotli compression transmission;
  • Support pipelining;
  • Provide a lightweight command line tool, drogon_ctl, to simplify the creation of various classes in Drogon and the generation of view code;
  • Support non-blocking I/O based asynchronously reading and writing database (PostgreSQL and MySQL(MariaDB) database);
  • Support asynchronously reading and writing sqlite3 database based on thread pool;
  • Support Redis with asynchronous reading and writing;
  • Support ARM Architecture;
  • Provide a convenient lightweight ORM implementation that supports for regular object-to-database bidirectional mapping;
  • Support plugins which can be installed by the configuration file at load time;
  • Support AOP with built-in joinpoints.
  • Support C++ coroutines

A very simple example

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.

Below is the main program of a typical drogon application:

c++
#include <drogon/drogon.h>

using namespace drogon;

int main()
{
    app().setLogPath("./")
         .setLogLevel(trantor::Logger::kWarn)
         .addListener("0.0.0.0", 80)
         .setThreadNum(16)
         .enableRunAsDaemon()
         .run();
}

It can be further simplified by using configuration file as follows:

c++
#include <drogon/drogon.h>

using namespace drogon;

int main()
{
    app().loadConfigFile("./config.json").run();
}

Drogon provides some interfaces for adding controller logic directly in the main() function, for example, user can register a handler like this in Drogon:

c++
app().registerHandler("/test?username={name}",
                    [](const HttpRequestPtr& req,
                       std::function<void (const HttpResponsePtr &)> &&callback,
                       const std::string &name) -> void
                    {
                        Json::Value json;
                        json["result"] = "ok";
                        json["message"] = "hello, " + name;
                        HttpResponsePtr resp = HttpResponse::newHttpJsonResponse(json);
                        callback(resp);
                    },
                    {Get,"LoginFilter"});

While such interfaces look intuitive, they are not suitable for complex business logic scenarios. Assuming there are tens or even hundreds of handlers that need to be registered in the framework, isn't it a better practice to implement them separately in their respective classes? So unless your logic is very simple, we don't recommend using above interfaces. Instead, we can create an HttpSimpleController as follows:

c++
/// The TestCtrl.h file
#pragma once
#include <drogon/HttpSimpleController.h>

using namespace drogon;

class TestCtrl : public HttpSimpleController<TestCtrl>
{
public:
    void asyncHandleHttpRequest(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback) override;
    PATH_LIST_BEGIN
    PATH_ADD("/test",Get);
    PATH_LIST_END
};

/// The TestCtrl.cc file
#include "TestCtrl.h"

void TestCtrl::asyncHandleHttpRequest(const HttpRequestPtr& req,
                                      std::function<void (const HttpResponsePtr &)> &&callback)
{
    // write your application logic here
    HttpResponsePtr resp = HttpResponse::newHttpResponse();
    resp->setBody("Hello, world!");
    resp->setExpiredTime(0);
    callback(resp);
}

Most of the above programs can be automatically generated by the command line tool drogon_ctl provided by drogon (The command is drogon_ctl create controller TestCtrl). All the user needs to do is add their own business logic. In the example, the controller returns a Hello, world! string when the client accesses the http://ip/test URL.

For JSON format response, we create the controller as follows:

c++
/// The header file
#pragma once

#include <drogon/HttpSimpleController.h>

using namespace drogon;

class JsonCtrl : public HttpSimpleController<JsonCtrl>
{
  public:
    void asyncHandleHttpRequest(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback) override;
    PATH_LIST_BEGIN
    // list path definitions here;
    PATH_ADD("/json", Get);
    PATH_LIST_END
};

/// The source file
#include "JsonCtrl.h"

void JsonCtrl::asyncHandleHttpRequest(const HttpRequestPtr &req,
                                      std::function<void(const HttpResponsePtr &)> &&callback)
{
    Json::Value ret;
    ret["message"] = "Hello, World!";
    HttpResponsePtr resp = HttpResponse::newHttpJsonResponse(ret);
    callback(resp);
}

Let's go a step further and create a demo RESTful API with the HttpController class, as shown below (Omit the source file):

c++
/// The header file
#pragma once
#include <drogon/HttpController.h>

using namespace drogon;

namespace api::v1
{
class User : public HttpController
{
  public:
    METHOD_LIST_BEGIN
    // use METHOD_ADD to add your custom processing function here;
    METHOD_ADD(User::getInfo, "/{id}", Get); // path is /api/v1/User/{arg1}
    METHOD_ADD(User::getDetailInfo, "/{id}/detailinfo", Get); // path is /api/v1/User/{arg1}/detailinfo
    METHOD_ADD(User::newUser, "/{name}", Post); // path is /api/v1/User/{arg1}
    METHOD_LIST_END
    // your declaration of processing function maybe like this:
    void getInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
    void getDetailInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
    void newUser(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, std::string &&userName);
  public:
    User()
    {
        LOG_DEBUG << "User constructor!";
    }
};
} // namespace api::v1

As you can see, users can use the HttpController to map paths and parameters at the same time. This is a very convenient way to create a RESTful API application.

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.

After compiling all of the above source files, we get a very simple web application. This is a good start. For more information, please visit the documentation on GitHub.

Cross-compilation

Drogon supports cross-compilation, you should define the CMAKE_SYSTEM_NAME in toolchain file, for example:

cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

You can disable building options for examples and drogon_ctl by settings BUILD_EXAMPLES and BUILD_CTL to OFF in the toolchain file.

Building options

Drogon provides some building options, you can enable or disable them by setting the corresponding variables to ON or OFF in the cmake command line, cmake file etc...

Option name Description Default value
BUILD_CTL Build drogon_ctl ON
BUILD_EXAMPLES Build examples ON
BUILD_ORM Build orm ON
COZ_PROFILING Use coz for profiling OFF
BUILD_SHARED_LIBS Build drogon as a shared lib OFF
BUILD_DOC Build Doxygen documentation OFF
BUILD_BROTLI Build Brotli ON
BUILD_YAML_CONFIG Build yaml config ON
USE_SUBMODULE Use trantor as a submodule ON

Contributions

This project exists thanks to all the people who contribute code.

Every contribution is welcome. Please refer to the contribution guidelines for more information.


File: docker/alpine/README.md

Build Docker Image

shell
$ cd drogon/docker/alpine # from this repository
$ docker build --no-cache --build-arg UID=`id -u` --build-arg GID=`id -g` -t drogon-alpine . # include last dot(.)

Create a Drogon Project

shell
$ cd ~/drogon_app # example
$ docker run --rm -v="$PWD:/drogon/app" -w="/drogon/app" drogon-alpine drogon_ctl create project hello_world

Build the Project

shell
$ cd hello_world
$ docker run --rm --volume="$PWD:/drogon/app" -w="/drogon/app/build" drogon-alpine sh -c "cmake .. && make"

Start Server

shell
$ docker run --name drogon_test --rm -u 0 -v="$PWD/build:/drogon/app" -w="/drogon/app" -p 8080:80 -d drogon-alpine ./hello_world # expose port 80 to 8080

Stop Server

shell
$ docker kill drogon_test

File: examples/jsonstore/README.md

This folder contains a jsonstore-like storage service. But is multi-threaded and stores everything in memory. Serving as a showcase on how to build a minimally useful RESTful APIs in Drogon.

API

/get-token

Generate a token that the user can use to create, read, modify and delete records.

  • method: GET
  • URL params: None
  • Body: None
  • Success response
    • Code: 200
    • Content: {"token":"3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220"}
  • Sample call
bash
❯ curl -XGET http://localhost:8848/get-token
{"token":"3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220"}

/{token}

Create a new JSON object associated with the token

  • method: POST
  • URL params: None
  • Body: The initial JSON object to store
  • Success response
    • Code: 200
    • Content: {"ok":true}
  • Failed response:
    • Code: 500
    • Why: Either the token already is associated with the data or request body/content-type is not JSON.
    • Content: {"ok":false}
  • Sample call
bash
❯ curl -XPOST http://localhost:8848/3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220 \
        -H 'content-type: application/json' -d '{"foo":{"bar":42}}'
{"ok":true}

Delete the JSON object associated with the token

  • method: DELETE
  • URL params: None
  • Body: None
  • Success response
    • Code: 200
    • Content: {"ok":true}
  • Sample call
bash
❯ curl -XDELETE http://localhost:8848/3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220
{"ok":true}

/{token}/{some/path/to/data}

Retrieve data at and below the specified path

  • method: GET
  • URL params: None
  • Body: None
  • Success response
    • Code: 200
    • Content: {"foo":{"bar":42}}
  • Failed response:
    • Code: 500
    • Why: No data associated with the provided token, or it does not exist in the JSON object.
    • Content: {"ok":false}
  • Sample call
bash
❯ curl -XGET http://localhost:8848/3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220/
{"foo":{"bar":42}}

Update data at the specified path

  • method: PUT
  • URL params: None
  • Body: The JSON object you wish to replace to
  • Success response
    • Code: 200
    • Content: {"ok":true}
  • Failed response:
    • Code: 500
    • Why: No data associated with the provided token or it does not exist in the JSON object.
    • Content: {"ok":false}
  • Sample call
bash
❯ curl -XPUT http://localhost:8848/3a322920d42ef0763152a6efff2ed51985530aedd45370f92fd0f0b8dcc30220/foo \
        -H 'content-type: application/json' -d '{"fruit":"apple"}'
{"ok":true}

Example use

bash
export URL="http://localhost:8848"
export TOKEN=`curl $URL/get-token -s | sed 's/.*"\([0-9a-f]*\)".*/\1/'`
printf "Token is: $TOKEN\n" 

printf 'Creating new data \n> '
curl -XPOST $URL/$TOKEN -H 'content-type: application/json' -d '{"foo":{"bar":42}}'
printf '\nRetrieving value of data["foo"]["bar"] \n> '
curl $URL/$TOKEN/foo/bar
printf '\nModifing data \n> '
curl -XPUT $URL/$TOKEN/foo -H 'content-type: application/json' -d '{"zoo":"zebra"}'
printf '\nNow data["foo"]["bar"] no longer exists \n> '
curl $URL/$TOKEN/foo/bar
printf '\nDelete data \n> '
curl -XDELETE $URL/$TOKEN
echo

Output:

text
Token is: 5e73ba044b45e68b4856925faea268391091f39fc62ab8c58955cf20957018fa
Creating new data 
> {"ok":true}
Retrieving value of data["foo"]["bar"] 
> 42
Modifying data 
> {"ok":true}
Now data["foo"]["bar"] no longer exists 
> {"ok":false}
Delete data 
> {"ok":true}

File: examples/redis/README.md

Redis example

A simple redis example

Usage

First of all you need redis running on the port 6379

Post

text
curl --location --request POST 'localhost:8080/client/foo' \
--header 'Content-Type: application/json' \
--data-raw '{
  "value": "bar"
}'

Get

text
curl --location --request GET 'localhost:8080/client/foo'

Subscribe and Publish

Go to a websocket test website, such as https://wstool.js.org

Subscribe

Connect to ws://localhost:8080/sub

To subscribe to a channel, send channel name: mychannel

To unsubscribe from a channel, send 'unsub ' + channel name: unsub mychannel

Publish

Connect to ws://localhost:8080/pub

To publish message, send channel name and message: mychannel anything follows will be the message.


File: examples/redis_cache/README.md

Redis example

An example for using coroutines of redis clients. This example is a redis implementation of
the local sessions example on wiki.

Usage

First of all you need redis running on the port 6379;

Please use new compilers that support coroutines. Configure the project with the following
command:

shell
cmake .. -DCMAKE_CXX_FLAGS="-std=c++20 -fcoroutines" #for GCC 10
cmake .. -DCMAKE_CXX_FLAGS="-std=c++20" #for GCC >= 11
cmake .. -DCMAKE_CXX_FLAGS="/std:c++20" #for MSVC >= 16.25

File: examples/redis_chat/README.md

Redis example

A simple redis chat server

Usage

First you need redis running on the port 6379.

Connect with username

ws://localhost:8080/chat?name=

Enter room

Send message ENTER <roomNo> to enter a room.

room number should be 0 - 99.

Quit room

Send message QUIT to quit room.

Send message

Send whatever else will be a message.


File: examples/simple_reverse_proxy/README.md

An example that shows how to use drogon as an http reverse proxy with a simple round robin.

This project is created with the drogon_ctl command, please compile it after installing drogon.


File: examples/README.md

Drogon Examples

The following examples can help you understand how to use Drogon:

  1. helloworld - The multiple ways of "Hello, World!"
  2. client_example - A client example
  3. websocket_client - An example on how to use the WebSocket client
  4. login_session - How to use the built-in session system to handle login and out
  5. file_upload - How to handle file uploads in Drogon
  6. simple_reverse_proxy - An example showing how to use Drogon as a HTTP reverse
    proxy with a simple round robin
  7. benchmark - Basic benchmark(https://github.com/drogonframework/drogon/wiki/13-Benchmarks) example
  8. jsonstore - Implementation of a jsonstore-like storage service that is concurrent and stores in memory. Serving as a showcase on how to build a minimally useful RESTful APIs in Drogon
  9. redis - A simple example of Redis
  10. websocket_server - A example websocket chat room server
  11. redis_cache - An example for using coroutines of Redis clients
  12. redis_chat - A chatroom server built with websocket and Redis pub/sub service
  13. prometheus_example - An example of how to use the Prometheus exporter in Drogon
  14. cors - An example demonstrating how to implement CORS (Cross-Origin Resource Sharing) support in Drogon

[TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks) test suite

I created a benchmark suite for the tfb, see here for details.

Another test suite

I also created a test suite for another web frameworks benchmark repository, see here. In this project, Drogon is used as a sub-module (locally include in the project).


File: third_party/mman-win32/README.md

mman-win32

mman library for Windows. mirror of https://code.google.com/p/mman-win32/

A light implementation of the mmap functions for MinGW.

The mmap-win32 library implements a wrapper for mmap functions around the memory mapping Windows API.

License: MIT License


File: README.zh-CN.md





English | 简体中文 | 繁體中文

Drogon是一个基于C++17/20的Http应用框架,使用Drogon可以方便的使用C++构建各种类型的Web应用服务端程序。
本版本库是github上Drogon工程的镜像库。Drogon是作者非常喜欢的美剧《权力的游戏》中的一条龙的名字(汉译作卓耿),和龙有关但并不是dragon的误写,为了不至于引起不必要的误会这里说明一下。

Drogon是一个跨平台框架,它支持Linux,也支持macOS、FreeBSD,OpenBSD,HaikuOS,和Windows。它的主要特点如下:

  • 网络层使用基于epoll(macOS/FreeBSD下是kqueue)的非阻塞IO框架,提供高并发、高性能的网络IO。详细请见TFB Tests Results;
  • 全异步编程模式;
  • 支持HTTP 1.0/1.1(server端和client端);
  • 基于template实现了简单的反射机制,使主程序框架、控制器(controller)和视图(view)完全解耦;
  • 支持cookies和内建的session;
  • 支持后端渲染,把控制器生成的数据交给视图生成Html页面,视图由CSP模板文件描述,通过CSP标签把C++代码嵌入到HTML页面,由drogon的命令行工具在编译阶段自动生成C++代码并编译;
  • 支持运行期的视图页面动态加载(动态编译和加载so文件);
  • 非常方便灵活的路径(path)到控制器处理函数(handler)的映射方案;
  • 支持过滤器(filter)链,方便在控制器之前执行统一的逻辑(如登录验证、HTTP Method约束验证等);
  • 支持HTTPS(基于OpenSSL实现);
  • 支持WebSocket(server端和client端);
  • 支持JSON格式请求和应答, 对Restful API应用开发非常友好;
  • 支持文件下载和上传,支持sendfile系统调用;
  • 支持gzip/brotli压缩传输;
  • 支持pipelining;
  • 提供一个轻量的命令行工具drogon_ctl,帮助简化各种类的创建和视图代码的生成过程;
  • 基于非阻塞IO实现的异步数据库读写,目前支持PostgreSQL和MySQL(MariaDB)数据库;
  • 基于线程池实现sqlite3数据库的异步读写,提供与上文数据库相同的接口;
  • 支持Redis异步读写;
  • 支持ARM架构;
  • 方便的轻量级ORM实现,支持常规的对象到数据库的双向映射操作;
  • 支持插件,可通过配置文件在加载期动态拆装;
  • 支持内建插入点的AOP
  • 支持C++协程

一个非常简单的例子

不像大多数C++框架那样,drogon的主程序可以保持非常简单。 Drogon使用了一些小技巧使主程序和控制器解耦合. 控制器的路由设置可以在控制器类中定义或者配置文件中完成.

下面是一个典型的主程序的样子:

c++
#include <drogon/drogon.h>

using namespace drogon;

int main()
{
    app().setLogPath("./")
         .setLogLevel(trantor::Logger::kWarn)
         .addListener("0.0.0.0", 80)
         .setThreadNum(16)
         .enableRunAsDaemon()
         .run();
}

如果使用配置文件,可以进一步简化成如下的样子:

c++
#include <drogon/drogon.h>

using namespace drogon;

int main()
{
    app().loadConfigFile("./config.json").run();
}

当然,Drogon也提供了一些接口,使用户可以在main()函数中直接添加控制器逻辑,比如,用户可以注册一个lambda处理器到drogon框架中,如下所示:

c++
app().registerHandler("/test?username={name}",
                    [](const HttpRequestPtr& req,
                       std::function<void (const HttpResponsePtr &)> &&callback,
                       const std::string &name) -> void
                    {
                        Json::Value json;
                        json["result"] = "ok";
                        json["message"] = "hello, " + name;
                        HttpResponsePtr resp = HttpResponse::newHttpJsonResponse(json);
                        callback(resp);
                    },
                    {Get,"LoginFilter"});

这看起来是很方便,但是这并不适用于复杂的应用,试想假如有数十个或者数百个处理函数要注册进框架,main()函数将膨胀到不可读的程度。显然,让每个包含处理函数的类在自己的定义中完成注册是更好的选择。所以,除非你的应用逻辑非常简单,我们不推荐使用上述接口,更好的实践是,我们可以创建一个HttpSimpleController对象,如下:

c++
/// The TestCtrl.h file
#pragma once

#include <drogon/HttpSimpleController.h>

using namespace drogon;

class TestCtrl : public HttpSimpleController<TestCtrl>
{
public:
    void asyncHandleHttpRequest(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback) override;
    PATH_LIST_BEGIN
    PATH_ADD("/test", Get);
    PATH_LIST_END
};

/// The TestCtrl.cc file
#include "TestCtrl.h"

void TestCtrl::asyncHandleHttpRequest(const HttpRequestPtr& req,
                                      std::function<void (const HttpResponsePtr &)> &&callback)
{
    // write your application logic here
    HttpResponsePtr resp = HttpResponse::newHttpResponse();
    resp->setBody("Hello, world!");
    resp->setExpiredTime(0);
    callback(resp);
}

上面程序的大部分代码都可以由drogon_ctl命令创建(这个命令是drogon_ctl create controller TestCtr)。用户所需做的就是添加自己的业务逻辑。在这个例子中,当客户端访问URLhttp://ip/test时,控制器简单的返回了一个Hello, world!页面。

对于JSON格式的响应,我们可以像下面这样创建控制器:

c++
/// The header file
#pragma once
#include <drogon/HttpSimpleController.h>

using namespace drogon;

class JsonCtrl : public HttpSimpleController<JsonCtrl>
{
  public:
    void asyncHandleHttpRequest(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback) override;
    PATH_LIST_BEGIN
    // list path definitions here;
    PATH_ADD("/json", Get);
    PATH_LIST_END
};

/// The source file
#include "JsonCtrl.h"

void JsonCtrl::asyncHandleHttpRequest(const HttpRequestPtr &req,
                                      std::function<void(const HttpResponsePtr &)> &&callback)
{
    Json::Value ret;
    ret["message"] = "Hello, World!";
    HttpResponsePtr resp = HttpResponse::newHttpJsonResponse(ret);
    callback(resp);
}

让我们更进一步,通过HttpController类创建一个RESTful API的例子,如下所示(忽略了实现文件):

c++
/// The header file
#pragma once
#include <drogon/HttpController.h>

using namespace drogon;

namespace api::v1
{
class User : public HttpController
{
  public:
    METHOD_LIST_BEGIN
    // use METHOD_ADD to add your custom processing function here;
    METHOD_ADD(User::getInfo, "/{id}", Get); // path is /api/v1/User/{arg1}
    METHOD_ADD(User::getDetailInfo, "/{id}/detailinfo", Get); // path is /api/v1/User/{arg1}/detailinfo
    METHOD_ADD(User::newUser, "/{name}", Post); // path is /api/v1/User/{arg1}
    METHOD_LIST_END
    // your declaration of processing function maybe like this:
    void getInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
    void getDetailInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
    void newUser(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, std::string &&userName);
  public:
    User()
    {
        LOG_DEBUG << "User constructor!";
    }
};
} // namespace api::v1

如你所见,通过HttpController类,用户可以同时映射路径和路径参数,这对RESTful API应用来说非常方便。

另外,你可以发现前面所有的处理函数接口都是异步的,处理器的响应是通过回调对象返回的。这种设计是出于对高性能的考虑,因为在异步模式下,可以使用少量的线程(比如和处理器核心数相等的线程)处理大量的并发请求。

编译上述的所有源文件后,我们得到了一个非常简单的web应用程序,这是一个不错的开始。请访问GitHub上的文档

贡献方式

欢迎您的贡献。 请阅读贡献指南以获取更多的信息。

QQ交流群:1137909452

欢迎交流探讨。

微信公众号:

会不定期推送一些Drogon的使用技巧和更新信息,欢迎关注。