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

Build Status Build Status Build status Total alerts Join the chat at https://gitter.im/drogon-web/community Docker image

English | 简体中文 | 繁體中文

Overview

Drogon 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++. Drogon is the name of a dragon in the American TV series "Game of Thrones" that I really like.

Drogon is a cross-platform framework, It supports Linux, macOS, FreeBSD, OpenBSD, 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 Http1.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 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 build-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:

#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:

#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:

app().registerHandler("/test?username={name}",
                    [](const HttpRequestPtr& req,
                       std::function<void (const HttpResponsePtr &)> &&callback,
                       const std::string &name)
                    {
                        Json::Value json;
                        json["result"]="ok";
                        json["message"]=std::string("hello,")+name;
                        auto 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:

/// The TestCtrl.h file
#pragma once
#include <drogon/HttpSimpleController.h>
using namespace drogon;
class TestCtrl:public drogon::HttpSimpleController<TestCtrl>
{
public:
    virtual 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
    auto resp = HttpResponse::newHttpResponse();
    resp->setBody("<p>Hello, world!</p>");
    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:

/// The header file
#pragma once
#include <drogon/HttpSimpleController.h>
using namespace drogon;
class JsonCtrl : public drogon::HttpSimpleController<JsonCtrl>
{
  public:
    virtual 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!";
    auto 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):

/// The header file
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api
{
namespace v1
{
class User : public drogon::HttpController<User>
{
  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 v1
} // namespace api

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 wiki or DocsForge

Contributions

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

Owner
An Tao
Drogon QQ群: 1137909452
An Tao
Comments
  • MariaDB connection issue

    MariaDB connection issue

    Describe the bug Fail on the connection to MariaDB when testing the model create ctl. Copied the default model.json into a folder named /models/testModel and run the following command.

    drogon_ctl create model testModel
    
    Create model
    mysql
    Connect to server...
    Source files in the eshop folder will be overwritten, continue(y/n)?
    20200202 17:04:55.725077 UTC 3864 ERROR Failed to mysql_real_connect() - MysqlConnection.cc:229
    20200202 17:04:56.725637 UTC 3864 ERROR Failed to mysql_real_connect() - MysqlConnection.cc:229
    20200202 17:04:57.725777 UTC 3864 ERROR Failed to mysql_real_connect() - MysqlConnection.cc:229
    ...
    

    The database is running which I can use PhpMyAdmin to access it. Created a database name "cppwebserver" and some other credentials for testing. I tried different ports (failed) and thought there was a bug in the model.json ( "passwd" to "password" ) still failed.

    Capture

    BTW, THANKS for the good works to the C++ community. I would like to take deeper understanding on this web-framework and compare with the Cutelyst and Wt. Are there some examples for the ORM part? Say, using the default model in the model.json.

    To Reproduce Steps to reproduce the behavior:

    1. Go to '/models' directory
    2. mkdir testModel && cp model.json testModel/
    3. edit the parameters "rdbms": mysql, "port":3306, "host":"127.0.0.1", "dbname":"cppwebserver".......
    4. drogon_ctl create model testModel and see error

    Expected behavior ERROR Failed to mysql_real_connect() - MysqlConnection.cc:229 Desktop (please complete the following information):

    • OS: Linux
  • Put generated views in a namespace to avoid name conflicts

    Put generated views in a namespace to avoid name conflicts

    Describe the bug Using the same name of a view in the global namespace cause conflicts.

    To Reproduce

    1. Create a view search.csp named "search"
    2. Create a controller named "search" (in the global namespace)
    3. Controller will be ignored

    It could be useful to be able to choose in wich namespace the views will be generated.

  • Can't connect to database (M1 MacOS)

    Can't connect to database (M1 MacOS)

    main.cpp

    #include <drogon/drogon.h>
    
    int main()
    {
      drogon::app().addListener("127.0.0.1", 8848);
      // Load config file
      // drogon::app().loadConfigFile("../config.json");
      drogon::app().createDbClient("mysql", "127.0.0.1", 3306, "database_test", "root", "******").run();
    
      return 0;
    }
    
    

    When I start the project, I get an unknown error. image

    drogon_ctl -v

    A utility for drogon
    Version: 1.7.5
    Git commit: f017b09947badc2e0516355358c8eb7843b44e19
    Compilation: 
      Compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
      Compiler ID: AppleClang
      Compilation flags: -std=c++17 -I/opt/homebrew/include -I/usr/local/include
    Libraries: 
      postgresql: no  (batch mode: no)
      mariadb: yes
      sqlite3: yes
      openssl: no
      brotli: no
      boost: no
      hiredis: yes
      c-ares: no
    

    brew ls

    ca-certificates	glew		hiredis		mariadb		msgpack		pcre2		ruby
    cmake		glfw		jsoncpp		mecab		[email protected]	readline	sqlite
    cocoapods	groonga		libyaml		mecab-ipadic	pcre		redis		zstd
    

    I can connect to MariaDB by DBeaver: image

  • Question: how to use the resolver class

    Question: how to use the resolver class

    Hi everyone, I need to resolve smtp.gmail.com to an ip, I'd like to use the mail plugin which only accept ip.

    //Send an email
    auto *smtpmailPtr = app().getPlugin<SMTPMail>();
    auto id = smtpmailPtr->sendEmail(
             "127.0.0.1",                  //The server IP, dns not support by drogon tcp-socket at the moment
             587,                          //The port
             "[email protected]",       //Who send the email
             "[email protected]",    //Send to whom
             "Testing SMTPMail Function",  //Email Subject/Title
             "Hello from drogon plugin",   //Content
             "[email protected]",       //Login user
             "123456"                      //User password
             );
    

    in the readme it says dns not supported by drogon tcp-socket, but I see in trantor the Resolver.h and in the inner folder more of Resolver. I've tried

                            auto callbackPtr = std::make_shared<
                                std::function<void(const HttpResponsePtr &)>>(
                                std::move(callback));
                            auto &loopApp = trantor::EventLoop(app().getLoop(10));
    
                            std::shared_ptr<trantor::Resolver> resolvePtr =
                                trantor::Resolver::newResolver(*loopApp, 600);
                            auto resolve = resolvePtr.resolve(
                                "smpt.gmail.com",
                                [data,
                                 callbackPtr](const trantor::InetAddress &addr) {
                                    LOG_DEBUG << "****IIII " << addr.toIpPort();
                                });
    

    but with no success. I'm new to c++ grammar, btw I would thank you for this great project, much appreciated work.

  • Websocket not connected to server in native

    Websocket not connected to server in native

    #include <drogon/WebSocketClient.h>
    #include <drogon/WebSocketController.h>
    #include <drogon/HttpAppFramework.h>
    #include <trantor/net/EventLoopThread.h>
    
    #include <iostream>
    
    using namespace drogon;
    using namespace std::chrono_literals;
    
    class WebSocketTest : public drogon::WebSocketController<WebSocketTest> {
    public:
        virtual void handleNewMessage(const WebSocketConnectionPtr &,
                                      std::string &&,
                                      const WebSocketMessageType &) override;
    
        virtual void handleConnectionClosed(
                const WebSocketConnectionPtr &) override;
    
        virtual void handleNewConnection(const HttpRequestPtr &,
                                         const WebSocketConnectionPtr &) override;
    
        WS_PATH_LIST_BEGIN
        WS_PATH_ADD("/chat", "drogon::LocalHostFilter", Get);
        WS_PATH_LIST_END
    };
    
    
    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";
        }
    }
    
    void WebSocketTest::handleConnectionClosed(const WebSocketConnectionPtr &) {
        LOG_DEBUG << "websocket closed!";
    }
    
    void WebSocketTest::handleNewConnection(const HttpRequestPtr &,
                                            const WebSocketConnectionPtr &conn) {
        LOG_DEBUG << "new websocket connection!";
        conn->send("haha!!!");
    }
    
    
    int main(int argc, char *argv[]) {
    
        std::string host("0.0.0.0");
        int port(8848);
        app()
        .setLogLevel(trantor::Logger::kDebug)
        .addListener(host, port);
    
        auto wsPtr = WebSocketClient::newWebSocketClient(host, port);
        auto req = HttpRequest::newHttpRequest();
        req->setPath("/chat");
        wsPtr->setMessageHandler([](const std::string &message,
                                               const WebSocketClientPtr &wsPtr,
                                               const WebSocketMessageType &type) {
            std::cout << "new message:" << message << std::endl;
            if (type == WebSocketMessageType::Pong) {
                std::cout << "recv a pong" << std::endl;
            }
        });
        wsPtr->setConnectionClosedHandler([](const WebSocketClientPtr &wsPtr) {
            std::cout << "ws closed!" << std::endl;
        });
        wsPtr->connectToServer(req,
                               [](ReqResult r,
                                             const HttpResponsePtr &resp,
                                             const WebSocketClientPtr &wsPtr) {
                                   if (r == ReqResult::Ok) {
                                       std::cout << "ws connected!" << std::endl;
                                       wsPtr->getConnection()->setPingMessage("",
                                                                              2s);
                                       wsPtr->getConnection()->send("hello!");
                                   } else {
                                       std::cout << "ws failed!" << std::endl;
                                   }
                               });
        app().run();
    }
    

    Output:

    ws failed!
    

    But with:

    <!DOCTYPE html>
    <pre id="log"></pre>
    <script>
      // helper function: log message to screen
      function log(msg) {
        document.getElementById('log').textContent += msg + '\n';
      }
    
      // setup websocket with callbacks
      var ws = new WebSocket("ws://0.0.0.0:8848/chat");
      ws.onopen = function() {
        log('CONNECT');
        ws.send("hello!!!");
      };
      ws.onclose = function() {
        log('DISCONNECT');
      };
      ws.onmessage = function(event) {
        log('MESSAGE: ' + event.data);
        ws.send(event.data);
        ws.send(event.data);
      };
    </script>
    

    Works totaly fine;

    CONNECT
    MESSAGE: haha!!!
    

    Please explain this behavior.

    Regards.

  • Async transaction execution failure

    Async transaction execution failure

    Describe the bug With DbFastClient, apply query with newTransactionAsync() keeps reporting error.

    To Reproduce

    ...
      const uint64_t id = 1;
      const ushort age = 16;
      auto clientPtr = drogon::app().getFastDbClient("FOO");
    
      clientPtr->newTransactionAsync([id=std::move(id),age=std::move(age)](const std::shared_ptr<Transaction> &transPtr) {
          assert(transPtr);
          transPtr->execSqlAsync( "select * from foo where id=$1",
            [=](const Result &r) {
                if ( 0 < r.size() )
                {
                    *transPtr << "update foo set age=$1 where id=$2"
                              << age          //Does it required to be std::string ?
                              << r[0]["id"].as<uint64_t>()
                              >> [](const Result &r)
                                 {
                                    LOG_INFO << "Updated!";
                                    //... do something about the task;
                                 }
                              >> [](const DrogonDbException &e)
                                 {
                                    LOG_ERROR << "err:" << e.base().what();
                                 };
                }
                else
                {
                    LOG_ERROR << "No new tasks found!";
                }
            },
            [](const DrogonDbException &e) {
                LOG_ERROR << "err:" << e.base().what();
            },
          id);   //Does it required to be std::string ?
        });
    
      return;
    ...
    

    Sometimes crashes but always returns the following

    20200313 04:24:41.332853 UTC 16597 ERROR error - MysqlConnection.cc:427
    20200313 04:24:41.332891 UTC 16597 ERROR Error(0) [00000] "" - MysqlConnection.cc:443
    20200313 04:24:41.349522 UTC 16597 ERROR Error occurred in transaction begin - TransactionImpl.cc:287
    20200313 04:24:41.349666 UTC 16597 ERROR err:The transaction has been rolled back - api_v0_foo.cc:163
    
    

    and sometimes with these

    20200313 04:35:36.383567 UTC 16676 ERROR error:1 status:1 - MysqlConnection.cc:258
    20200313 04:35:36.383629 UTC 16676 ERROR Error(1054) [42S22] "Unknown column '$1' in 'where clause'" - MysqlConnection.cc:443
    20200313 04:35:36.414844 UTC 16676 ERROR err:Unknown column '$1' in 'where clause' - api_v0_foo.cc:163
    20200313 04:35:36.415163 UTC 16676 DEBUG [operator()] Transaction roll back! - TransactionImpl.cc:159
    
    

    Expected behavior The record with id=1 in foo, the age field set to 16.

    Additional context Am I misunderstand the usage? Actually, I was trying to reference your example with the for update but failed with blocking call with newTransaction(). Then, I amended it with newTransactionAsync() but failed again even with the for update removed.

  • Cannot capture returned value of second sql query in transaction

    Cannot capture returned value of second sql query in transaction

    Consider following code:

    auto clientPtr = drogon::app().getFastDbClient("default");
            clientPtr->newTransactionAsync([=, this](const std::shared_ptr<Transaction> &transPtr) mutable {
                assert(transPtr);
    
                transPtr->execSqlAsync(
                    "select t.id, t.visible, t.title, t.created_at , t.posted_by, t.updated_at, t.votes, t.slug, "
                    "users.username, users.id as uid, array_agg(topic_tags.tag_id) as tag_id, array_agg(tags.name) as tags from topics t left "
                    "join users on t.posted_by=users.id left join topic_tags on topic_tags.topic_id=t.id left join "
                    "tags on topic_tags.tag_id = tags.id where t.op_id=0 group by t.id, users.id order by "
                    "t.updated_at limit $1 offset $2",
                    [=, this](const Result &rows) mutable {
                        if (rows.size() == 0)
                        {
                            return;
                        }
                        else
                        {
                            ret["topics"] = Json::arrayValue;
                            for (auto &r : rows)
                            {
                                Json::Value topic;
                                std::string answers;
    
                                topic["id"] = r["id"].as<std::string>();
                                topic["visible"] = r["visible"].as<bool>();
                                topic["title"] = r["title"].as<std::string>();
                                topic["created_at"] = r["created_at"].as<std::string>();
                                topic["updated_at"] = r["updated_at"].as<std::string>();
                                topic["votes"] = r["votes"].as<std::string>();
                                topic["slug"] = r["slug"].as<std::string>();
                                topic["username"] = r["username"].as<std::string>();
                                topic["uid"] = r["uid"].as<std::string>();
                                topic["tid"] = r["tag_id"].as<std::string>();
                                topic["tags"] = r["tags"].as<std::string>();
                                transPtr->execSqlAsync(
                                    "select count(1) from topics where op_id is not null and op_id=$1",
                                    [=, &answers](const Result &rows1) mutable {
                                        for (auto &r1 : rows1)
                                        {
                                            answers = r1["count"].as<std::string>();
                                            // topic["answers"] = r1["count"].as<std::string>();
                                            // ret["topics"].append(topic);
                                        }
                                    },
                                    [=](const DrogonDbException &e) mutable {
                                        LOG_DEBUG << e.base().what();
                                        ret["error"] = (std::string)e.base().what();
                                        callback(HttpResponse::newHttpJsonResponse(std::move(ret)));
                                    },
                                    r["id"].as<int64_t>());
                                LOG_DEBUG << answers;
                                ret["topics"].append(topic);
                            }
                        }
    
                        callback(jsonResponse(std::move(ret)));
                        return;
                    },
                    [=](const DrogonDbException &e) mutable {
                        LOG_DEBUG << e.base().what();
                        ret["error"] = (std::string)e.base().what();
                        callback(HttpResponse::newHttpJsonResponse(std::move(ret)));
                    },
                    (long)limit, (long)limit * (page_no - 1));
            });
    

    The string answers is still empty after the second sql query.

  • Should Drogon have built-in cryptographic functions?

    Should Drogon have built-in cryptographic functions?

    Happy new year!

    #646 suggested to add security guidelines. I think that's a great idea. Especially if we could make Drogon secure by default. As of now. Drogon currently doesn't come with "security features" per se. Contrast to PHP have a built-in password_hash.

    I've password hashing/verification and secure number/string generation upstream-able in my webapp. But I'm not sure if it should be upstreamed. They pull in extra dependencies that the core framework don't need and is trivial to write. And languages like Go and Rust asks user to pull in their own library.

    I guess the question is:

    • Should Drogon provide cryptographic functions? Or is it the user's job?
    • How much security could we provide without extra dependencies?
    • If yes, To what extent should Drogon assist users?
      • I don't expect drogon to support full-on SRP and GPG. That's bloat.
      • In the mean while, I want to avoid users storing password in the database.

    What are your thoughts? Thanks.

  • Allow thread to process other requests when waiting for asyncrinous HTTP requests

    Allow thread to process other requests when waiting for asyncrinous HTTP requests

    Hi, this issue is mostly related to the maximum throughput of an application. Currently Drogon have to wait if it makes a HTTP request inside a request handler. This may be a performance bottleneck when the request takes a while to response (ex: to a server across the globe, long RTT, etc...). It would be nice to be able to process other other requests while we're waiting for response.

    Now, sending requests within a handler looks like so

    app().registerHandler("/foo",[](const HttpRequestPtr& req, auto &&callback) {
    	// some API things
    	auto client = HttpClient::newHttpClient("http://my.api.endpoint.com");
    	auto req = HttpRequest::newHttpRequest();
    	std::promise<bool> valid;
    	req->setPath("/do_something");
    	client->sendRequest(req, [&](ReqResult result, const HttpResponsePtr &response) {
    		if(response == nullptr) // If no server responce
    			valid.set_value(false);
    		valid.set_value(true);
    	});
    	bool api_ok = valid.get_future().get(); // Wait for HTTP to response. Have to wait here otherwise crash the entire application
    	if(api_ok == false)
    		api_failed();
    	
    	// Keep doing our stuff
    });
    

    It would be great to do something like this.

    client->sendRequest(validation_req, [&](ReqResult result, const HttpResponsePtr &response) {
    	...});
    drogon::handleOtherRequest(valid); // Do other stuff until `valid` have been set. Don't waist time waiting
    

    Alternatively, it would be great to let HttpClient::sendRequest itself to do everything. So HttpClient::sendRequest looks to be a blocking call from the caller's perspective but in reality it only returns when it got an response or timeouts (and handle other requests in the mean time).

    bool api_ok = false;
    client->sendRequestSync(req, ...); // This looks like a blocking call from the caller
    // Since sendRequestSync only rerun when it have a response. The control flow is a lot easier
    if(api_ok == false)
    	api_failed();
    

    Thanks

  • Even after installing hiredis, getting

    Even after installing hiredis, getting "Redis is not supported by Drogon...."

    Describe the bug Even after installing hiredis, getting "Redis is not supported by Drogon...."

    Expected behavior It should have worked properly

    Screenshots If applicable, add screenshots to help explain your problem.

    Desktop (please complete the following information):

    • OS: Kubuntu 22.04
  • Add exports macro to allow Shared Library with hidden symbols by default (e.g. Windows)

    Add exports macro to allow Shared Library with hidden symbols by default (e.g. Windows)

    If you build drogon as a shared library with CMAKE_CXX_VISIBILITY_PRESET set to hidden and CMAKE_VISIBILITY_INLINES_HIDDEN to 1 all the symbols are hidden, thus linking a project with the drogon shared library will fail.

    On Windows, the symbols are hidden by default and you need to export them explicitely.

    This PR enable building trantor as a shared library on Windows.

    An abort() is raised when closing the project, I need to check if it is related to my work or not. In the meantime, I am interested in your feedback.

    This is Work In Progress as it points to my fork of trantor. I will update when https://github.com/an-tao/trantor/pull/127 is merged

  • drogon project fail to build with mingw

    drogon project fail to build with mingw

    Describe the bug drogon项目使用mysys2编译失败。操作步骤如下:

    pacman -S mingw-w64-i686-gcc mingw-w64-i686-cmake make mingw-w64-i686-c-ares mingw-w64-i686-jsoncpp
    
    git clone https://github.com/drogonframework/drogon --recursive
    mkdir drogon/build
    cd drogon/build
    cmake .. -G "MSYS Makefiles"
    make -j
    

    错误提示如下:

    $ make -j
    C:/dev/drogon/trantor/trantor/net/EventLoop.cc:31:7: error: conflicting declaration 'using ssize_t = long long int'
       31 | using ssize_t = long long;
          |       ^~~~~~~
    In file included from D:/Program Files/msys64/mingw32/include/crtdefs.h:10,
                     from D:/Program Files/msys64/mingw32/include/stdint.h:28,
                     from D:/Program Files/msys64/mingw32/lib/gcc/i686-w64-mingw32/12.2.0/include/stdint.h:9,
                     from C:/dev/drogon/trantor/trantor/utils/Date.h:18,
                     from C:/dev/drogon/trantor/trantor/net/EventLoop.h:20,
                     from C:/dev/drogon/trantor/trantor/net/EventLoop.cc:18:
    D:/Program Files/msys64/mingw32/include/corecrt.h:47:13: note: previous declaration as 'typedef int ssize_t'
       47 | typedef int ssize_t;
          |             ^~~~~~~
    make[2]: *** [trantor/CMakeFiles/trantor.dir/build.make:202: trantor/CMakeFiles/trantor.dir/trantor/net/EventLoop.cc.obj] Error 1
    make[2]: *** Waiting for unfinished jobs....
    make[1]: *** [CMakeFiles/Makefile2:182: trantor/CMakeFiles/trantor.dir/all] Error 2
    make: *** [Makefile:156: all] Error 2
    

    直接用MinGW makefile 也报了同样的错误

  • Open a WebSockets after Uploading a file

    Open a WebSockets after Uploading a file

    Description Attempting to open a WebSocket immediately after uploading a file using an XMLHttpRequest() cause a connection failure.

    To Reproduce Steps to reproduce the behavior:

    1. Create a WebPage that allow uploading as well as to create a websocket.
    2. upload a file using XMLHttpRequest()
    3. as soon as upload have been completed then create a WebSocket()
    4. See error connection fail.

    Expected behavior The connection will be completed sucessfully.

    Additional context Webserver have one single listener on port 8080 and different endpoints "/upload_endpoint" used for uploading files, "/output" used for the websockets and "/" that is the default.

  • Docsforge down

    Docsforge down

    The DocsForge documentation page is down. I've seen on the github page that the project has been discontinued. I also saw that the domain docsforge.com is for sale so I'm suspecting that this means that it's not going up anymore? The code is still available on github so maybe it's possible to host drogon.docsforge.com on the same server as drogon.org is?

    I realy liked the Docsforge documentation medium for Drogon and would like to see it (or an alternative) return.

    Thanks for creating Drogon!

  • createRedisClient  Error

    createRedisClient Error

    The following information is printed by the launcher console

    ERROR Error: (null) - RedisConnection.cc:45

    I'm not sure where the problem is. Has God ever encountered this problem

  • Drogon HTTP server is limiting the number of concurrent requests

    Drogon HTTP server is limiting the number of concurrent requests

    Drogon HTTP server is limiting the number of concurrent requests being submitted to the handler. This is observed with all the recent versions of Drogon. I've tested with v1.8.0 and above.

    See this code to reproduce

    #include <chrono>
    #include <drogon/HttpController.h>
    
    class Handler : public drogon::HttpController<Handler> {
    public:
      METHOD_LIST_BEGIN
      ADD_METHOD_TO(Handler::longRuningFunc, "/api", drogon::Get);
      METHOD_LIST_END
    
      void longRuningFunc(
          const drogon::HttpRequestPtr &req,
          std::function<void(const drogon::HttpResponsePtr &)> &&cb) const {
        using namespace std::chrono_literals;
        std::cout << "Received request.. " << std::this_thread::get_id()
                  << std::endl;
        // simulate long running process
        std::this_thread::sleep_for(1s);
        auto res = drogon::HttpResponse::newHttpResponse();
        res->setContentTypeCode(drogon::ContentType::CT_APPLICATION_JSON);
        res->setStatusCode(drogon::HttpStatusCode::k200OK);
        res->setBody("");
        cb(res);
        std::cout << "..Responded" << std::this_thread::get_id() << std::endl;
      }
    };
    
    int main(int argc, char **argv) {
      drogon::app()
          .addListener("0.0.0.0", 8080)
          .setThreadNum(std::thread::hardware_concurrency() + 2)
          .setClientMaxBodySize(1 * 1024 * 1024 * 1024)
          .run();
    }
    

    Build using

    g++ -isystem /opt/dev-setup/drogon-1.8.2/include -isystem /usr/include/jsoncpp -O3 -DNDEBUG\
     -fdiagnostics-color=always -std=c++20 -pthread -std=c++20 -MD -MT -c ~/code/oss/drogon-sample/main.cpp \
     -o main /opt/dev-setup/drogon-1.8.2/lib/libdrogon.a -lm  -lsasl2  -ldl \
     /usr/lib/x86_64-linux-gnu/libz.so /opt/dev-setup/drogon-1.8.2/lib/libtrantor.a \
     /usr/lib/x86_64-linux-gnu/libssl.so /usr/lib/x86_64-linux-gnu/libcrypto.so \
     -lpthread /usr/lib/x86_64-linux-gnu/libjsoncpp.so /usr/lib/x86_64-linux-gnu/libuuid.so
    

    Test using

    wrk -d1s -t$(nproc) -c$(nproc) http://localhost:8080/api
    

    Expected behavior The expectation is all the requests from the client needs to be accepted at once, as the IO thread count is available to handle.

    Screenshots

    https://user-images.githubusercontent.com/471374/206050311-860e3e6a-1c6c-4850-a8ed-56718e2efd27.mp4

    The screen recording shows that wrk submits 16 concurrent requests, but Drogon handles only 12 at a time. This varies with every run. On a box with 96 cores, this gets even worse. Submitting 300 requests gets only 90 requests to the handler.

    Desktop (please complete the following information):

    • OS: Ubuntu 22.04 64 bit.
🌱Light and powerful C++ web framework for highly scalable and resource-efficient web application. It's zero-dependency and easy-portable.
🌱Light and powerful C++ web framework for highly scalable and resource-efficient web application. It's zero-dependency and easy-portable.

Oat++ News Hey, meet the new oatpp version 1.2.5! See the changelog for details. Check out the new oatpp ORM - read more here. Oat++ is a modern Web F

Jan 4, 2023
TreeFrog Framework : High-speed C++ MVC Framework for Web Application

Small but Powerful and Efficient TreeFrog Framework is a high-speed and full-stack web application framework based on C++ and Qt, which supports HTTP

Dec 22, 2022
Tntnet is a web application server for web applications written in C++.

Tntnet is a web application server for web applications written in C++.

Sep 26, 2022
A high performance, middleware oriented C++14 http web framework please use matt-42/lithium instead

A high performance, middleware oriented C++14 http web framework please use matt-42/lithium instead

Dec 17, 2022
Your high performance web application C framework

facil.io is a C micro-framework for web applications. facil.io includes: A fast HTTP/1.1 and Websocket static file + application server. Support for c

Dec 29, 2022
A http/websocket server framework on linux.
A http/websocket server framework on linux.

The framework is a Web-Server on unix based system. Without using any third-party libraries, the framework writes from unix system calls and standard C library functions.

Oct 15, 2022
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.

Cutelyst - The Qt Web Framework A Web Framework built on top of Qt, using the simple and elegant approach of Catalyst (Perl) framework. Qt's meta obje

Dec 19, 2022
C library to create simple HTTP servers and Web Applications.

Onion http server library Travis status Coverity status Onion is a C library to create simple HTTP servers and Web Applications. master the developmen

Dec 31, 2022
QDjango, a Qt-based C++ web framework

QDjango - a Qt-based C++ web framework Copyright (c) 2010-2015 Jeremy Lainé About QDjango is a web framework written in C++ and built on top of the Qt

Dec 22, 2022
Crow is very fast and easy to use C++ micro web framework (inspired by Python Flask)
Crow is very fast and easy to use C++ micro web framework (inspired by Python Flask)

Crow is C++ microframework for web. (inspired by Python Flask) #include "crow.h" int main() { crow::SimpleApp app; CROW_ROUTE(app, "/")([]()

Jan 8, 2023
This is a proof-of-concept of a modern C web-framework that compiles to WASM and is used for building user interfaces.
This is a proof-of-concept of a modern C web-framework that compiles to WASM and is used for building user interfaces.

DanCing Web ?? ?? (DCW) Getting Started Dancing Web is now distributed with the Tarantella Package Manager — a tool I've made to simplify setup of pro

Sep 11, 2021
CppCMS - High Performance C++ Web Framework

CppCMS - High Performance C++ Web Framework What is CppCMS? CppCMS is a Free High Performance Web Development Framework (not a CMS) aimed at Rapid Web

Dec 25, 2022
Pistache is a modern and elegant HTTP and REST framework for C++

Pistache is a modern and elegant HTTP and REST framework for C++. It is entirely written in pure-C++17* and provides a clear and pleasant API.

Jan 4, 2023
C++ application development framework, to help developers create and deploy applications quickly and simply

ULib - C++ library Travis CI: Coverity Scan: ULib is a highly optimized class framework for writing C++ applications. I wrote this framework as my too

Dec 24, 2022
The application framework for developer module of EdgeGallery platform

crane-framework crane-framework将可复用的计算和软件功能抽象成插件,APP开发者面向使用插件进行MEC APP开发。这样屏蔽了和MEC平台交互的细节,实现MCE APP和MEC平台的松耦合。而且插件框架基础能力可裁剪,按需提供最小的APP系统。 特性介绍 为了方便开发者

Aug 30, 2021
Embeddable Event-based Asynchronous Message/HTTP Server library for C/C++

libasyncd Embeddable Event-based Asynchronous Message/HTTP Server library for C/C++. What is libasyncd? Libasyncd is an embeddable event-driven asynch

Dec 5, 2022
Embedded C/C++ web server

CivetWeb The official home of CivetWeb is https://github.com/civetweb/civetweb Continuous integration for Linux and macOS (Travis CI): Continuous inte

Jan 8, 2023
A C++11 RESTful web server library
A C++11 RESTful web server library

Served Overview Served is a C++ library for building high performance RESTful web servers. Served builds upon Boost.ASIO to provide a simple API for d

Dec 28, 2022
cserv is an event-driven and non-blocking web server

cserv is an event-driven and non-blocking web server. It ideally has one worker process per cpu or processor core, and each one is capable of handling thousands of incoming network connections per worker. There is no need to create new threads or processes for each connection.

Nov 6, 2022