vinipsmaker / tufao

An asynchronous web framework for C++ built on top of Qt
http://vinipsmaker.github.io/tufao/
GNU Lesser General Public License v2.1
589 stars 179 forks source link

threaded with router and map my owner handler run crash when open url. #55

Open kingctan opened 9 years ago

kingctan commented 9 years ago

code below, MainHandler just copy from your other example.

    TcpServer server;
//    MainHandler h;

    server.run(4, 8090, []() {
        QSharedPointer<Tufao::HttpServerRequestRouter> router;

        return [router](Tufao::HttpServerRequest &request,
                Tufao::HttpServerResponse &response) mutable {
            if (!router) {
                MainHandler h;
                router.reset(new Tufao::HttpServerRequestRouter);
                router->map({QRegularExpression{"^/$"},
                             Tufao::UrlRewriterHandler::handler(QUrl("/index.html"))});
                router->map({QRegularExpression{""}, h});
            }

            if (!router->handleRequest(request, response)) {
                response.writeHead(Tufao::HttpResponseStatus::NOT_FOUND);
                response.headers().insert("Content-Type", "text/plain");
                response.end("Not found\n");
            }

            return true;
        };
    });
Kalileo commented 8 years ago

I can confirm this issue. I tried exactly the same, integrating the custom handler Mainhandler from the request-dumper example into the threads-with-router-example. In other words, replacing the Tufao::HttpFileServer::handler("public") in router->map with this Mainhandler. Same result as kingctan, crash. Interestingly most of the time not at the first browser request, but at the 2nd.

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    TcpServer server;

    server.run(4, 8080, []() {
        QSharedPointer<Tufao::HttpServerRequestRouter> router;
        MainHandler h;

        return [router](Tufao::HttpServerRequest &request,
                        Tufao::HttpServerResponse &response) mutable {
            if (!router) {
                router.reset(new Tufao::HttpServerRequestRouter);
                router->map({QRegularExpression{"^/([^/]+)"}, h});
            }

            if (!router->handleRequest(request, response)) {
                response.writeHead(Tufao::HttpResponseStatus::NOT_FOUND);
                response.headers().insert("Content-Type", "text/plain");
                response.end("404 Not found.\n");
            }

            return true;
        };
    });

    return a.exec();
}

Reducing to 1 worker thread ("server.run(1, 8080...") did not help. The original example threads-with-router-example works fine, as well as all the other examples.

Unfortunately this is a showstopper.

Is there something wrong with this approach? Or is there any other way to run multiple workers / threads and custom handlers?

Kalileo commented 8 years ago

Qt 5.5.1 on Mac OS X (deployment will be on Linux though).

Kalileo commented 8 years ago

Tested on linux, and the same problem, crashing as soon as a Url is requested which matches the QRegularExpression - unlike on Mac, where the first request still works.

There is some debug output on linux, when it crashes:

pure virtual method called
terminate called without an active exception
The program has unexpectedly finished.

Valgrind does not give more output than that.

In QtCreator, run in debug mode, this is where it stands after crashing:

inline AbstractHttpServerRequestHandler::operator
std::function<bool(HttpServerRequest&, HttpServerResponse&)>()
{
    return [this](HttpServerRequest &req, HttpServerResponse &res) {
        return this->handleRequest(req, res);
    };   <<=== here
}

I hope that gives enough info to fix it. Otherwise please ask.

Kalileo commented 8 years ago

As a humble workaround, while waiting for the official fix, you can move the functionality of the lambda in main() at server.run(4, 8080, []() {...}) to the Worker class, eliminating all Factory() and setFactory() stuff in Worker and TcpServer, as well as the router. Then it works multithreaded.

Main changes below:

In main.cpp :

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    TcpServer server;
    server.run(4, 8080);
    return a.exec();
}

In worker.cpp call myHandler(request, response); instead of handler(request, response); :

bool Worker::myHandler(HttpServerRequest &request,
                       HttpServerResponse &response)
{
    const QString path(request.url().path());
    QRegularExpression re = QRegularExpression("^/([^/]+)......."); // change to what you need
    QRegularExpressionMatch match(re.match(path));

    if (request.method() == "GET" && match.hasMatch())
    {
        QStringList args{match.capturedTexts().mid(1)};
        QVariant backup{request.customData()};

        if (args.size()) {
            QVariantMap options{backup.toMap()};
            options["args"] = options["ags"].toStringList() + args;
            request.setCustomData(options);
        }

        // defined in worker.h:  MainHandler h;

        if (h.handleRequest(request, response))
            return true;

        if (args.size())
            request.setCustomData(backup);
    }

    response.writeHead(Tufao::HttpResponseStatus::NOT_FOUND);
    response.headers().insert("Content-Type", "text/plain");
    response.end("404 Not found.\n");

    return true;
}

It might be nice to get the router functionality back in, I skipped that here in favour of a simple workaround.

imho having the handler code in the worker instead of in main is not less efficient, with the lambda in main and the setFactory stuff it ends up there anyway.

vinipsmaker commented 8 years ago

Mainhandler from the request-dumper example into the threads-with-router-example.

It inherits QObject. You should not copy them, but you created them in the beginning of the block and copied it during the map call? Just a hint about what might be happening.

while waiting for the official fix

This is a malformed combination of two different examples. I don't claim an abstraction here being thread-safe when it's not. Examples are learning material to speedup your understanding. Not sure what to fix here.

Kalileo commented 8 years ago

You should not copy them, but you created them in the beginning of the block and copied it during the map call?

Yes, see first code sample above.

a malformed combination of two different examples.

What would be a better example to combine the threads-with-router-example and handlers? I think such a combination is needed for any server doing more than just serving a few more or less static files or bytes of html. Especially also when handling file uploads.

Not sure what to fix here.

There might be nothing to fix in the server code, but just to show the correct way of doing it, an example for this not so uncommon use case.

Also I think that you laid the foundation to a beautiful server. I've played around with some handlers for our main use cases, and it's fun and easy.

vinipsmaker commented 8 years ago

There might be nothing to fix in the server code, but just to show the correct way of doing it, an example for this not so uncommon use case.

That's a starting point. Thanks.

Kalileo commented 8 years ago

Just to make sure that the issue reported here is not misunderstood as a general problem with Tufao: a multithreaded server application with router and many handlers, based on the code as I posted it above, is currently in production test and so far no issues at all, rock solid, without a single change or issue in the Tufao code, and faaaast! @vinipsmaker / Vinícius dos Santos Oliveira: Big thanks for Tufao!

vinipsmaker commented 8 years ago

Big thanks for Tufao!

You're welcome