[Interest] QtHttpServer Asynchronous Request Processing

Daniel hein.dnl at gmail.com
Tue Jan 10 17:53:17 CET 2023


Hi Ievgenii,

thanks for your answer! Well, that's interesting. Both samples were
executed in the main thread, without any modification of the standard
threadpool. I did several tests and the lambdas in the first example were
never executed in parallel.

However, since I have to handle anyway arbitrarily formatted URL requests,
I'd like to subclass QAbstractHttpServer and to override the
handleRequest() function. As already mentioned, here again I could not find
a solution with a real parallel request processing.

In my opinion the handleRequest() interface is not ideal for asynchronous
request handling, since the QHttpServerRequest struct is not copyable and
given as const reference, thus not saveable for a later response. The
interface of the handleRequest() function even changed with Qt 6.5.0, here
the QTcpSocket pointer is replaced by a non-const reference to a
QHttpServerResponder struct. QHttpServerResponder is not copyable, too -
one could std::move() it for a later response, but this in turn raises
questions about lifetime and ownership of the provided QHttpServerResponder
object.

In my opinion, it would be more flexible, if the interface of the
QAbstractHttpServer would provide a signal or virtual function for
signaling a new request along with the request itself and an identifier of
the requesting client (sockets are managed and kept internally by
HttpServer). And another function for responding to this request, with the
client identifier parameter. In this way the user (i.e. subclass of
QAbstractHttpServer) would have all flexibility to either respond directly
or to e.g. generate concurrent background jobs and call the
respond function as soon as the background job has finished.

For example an interface like in the following. Response and ClientID
should be simple, copyable types / structs.

class AbstractHttpServer
{
protected:
    virtual void incomingRequest(const Request&, ClientID) = 0;
    bool sendResponse(const Response&, ClientID);
}

A user of this class could either implement a direct (blocking) behavior:

class MyHttpServer : public AbstractHttpServer
{
protected:
   void incomingRequest(const Request& request, ClientID clientID) override
   {
       auto response = generateResponse(request);
       sendResponse(response, clientID);
   }
}

Or he could initiate background jobs for each request: (I don't know, if
this actually compiles, but the idea should become clear)

class MyHttpServer : public AbstractHttpServer
{
protected:
   void incomingRequest(const Request& request, ClientID clientID) override
   {
       auto job = new QFutureWatcher<Response>;
       idMap.insert(job,clientID);
       QObject::connect(job, &QFutureWatcher<Response>::finished, this,
&MyHttpServer::onFinished);
       job->setFuture(QtConcurrent::run([request]() {  return
generateResponse(request) });
   }
private slot:
   void onFinished()
   {
       auto job = reinterpret_cast<QFutureWatcher<Response>*>(sender());
       respond(job->result(),  idMap.take(job));
       delete job;
   }
private:
    QHash<void*,ClientID> idMap;
}

Best regards,
Daniel


Am Di., 10. Jan. 2023 um 16:10 Uhr schrieb Ievgenii Meshcheriakov via
Interest <interest at qt-project.org>:

> Hi,
>
> I've tried to reproduce this on Windows and Linux and I cannot. In both
> cases
> the requests are executed in parallel. One thing that comes to mind is
> that a
> shared thread pool is used to execute request, so if it has only one
> thread
> then requests will be run sequentially. But this will not explain why the
> program with two handlers runs reuqests in parallel.
>
> Regards,
> Ievgenii
>
> середа, 4 січня 2023 р. 17:31:28 CET  написано:
> > Hi all,
> >
> > I played around with the new QHttpServer module in Qt6.4.1 and Qt6.5 and
> > ran into some questions regarding how to implement an asynchronous
> request
> > processing. I would like to implement a webservice that can handle
> several
> > requests in parallel, since each request may take significant processing
> > time to generate the corresponding response.
> >
> > Some examples in the following using QHttpServer:
> >
> >     QHttpServer server;
> >     server.route("/", []() {
> >     return QtConcurrent::run([]() {
> >                 std::cout <<
> > QTime::currentTime().toString().toLatin1().constData() << " start" <<
> > std::endl;
> >                 QThread::currentThread()->msleep(5000); //simulate some
> > processing time
> >                 std::cout <<
> > QTime::currentTime().toString().toLatin1().constData() << " stop" <<
> > std::endl;
> >                 return QHttpServerResponse(QString("1"));
> >                 });
> >         });
> >     server.listen(QHostAddress::Any, 80)
> >
> > When I run this example on a Windows machine and generate requests from
> two
> > different browser windows to "http://127.0.0.1", the registered view
> > handler lambda is processed in sequence and not (as expected) in parallel
> > (i.e. I get start/stop/start/stop). This is the same behavior with
> Qt6.4.1
> > as with Qt6.5.
> >
> > However, I could observe a different behavior, if I register a second
> rule:
> >
> >     QHttpServer server;
> >     server.route("/", []() {
> >         return QtConcurrent::run([]() {
> >                 std::cout <<
> > QTime::currentTime().toString().toLatin1().constData() << " start" <<
> > std::endl;
> >                 QThread::currentThread()->msleep(5000); //simulate some
> > processing time
> >                 std::cout <<
> > QTime::currentTime().toString().toLatin1().constData() << " stop" <<
> > std::endl;
> >                 return QHttpServerResponse(QString("1"));
> >                 });
> >         });
> >     server.route("/", [](QString id) {
> >             return QtConcurrent::run([id]() {
> >                 std::cout <<
> > QTime::currentTime().toString().toLatin1().constData() << " start" <<
> > std::endl;
> >                 QThread::currentThread()->msleep(5000); //simulate some
> > processing time
> >                 std::cout <<
> > QTime::currentTime().toString().toLatin1().constData() << " stop" <<
> > std::endl;
> >                 return QHttpServerResponse(QString("2: %1").arg(id));
> >                 });
> >             });
> >     server.listen(QHostAddress::Any, 80)
> >
> > Surprisingly, if I now enter in the first browser window "
> http://127.0.0.1"
> > while in the second browser window "http://127.0.0.1/test", both view
> > handlers are processed in parallel, i.e. I get start/start/stop/stop. So,
> > different view handlers can be run in parallel, if their (different)
> rules
> > are fired at the same time. Thus, in general an asynchronous request
> > processing is obviously possible, but unfortunately with the current API
> > and implementation only for different targets / rules.
> >
> > Since I need to handle arbitrarily formed URL requests (that does not fit
> > into the current route-API of QHttpServer), I tried similar experiments
> > using QAbstractHttpServer and overriding the handleRequest() function.
> Here
> > again, I could not find a way for an asynchronous request processing,
> i.e.
> > early return of handleRequest() function after triggering asynchronous
> > background jobs for each request, and then later sending the calculated
> > response. Every handleRequest() is called only after the response to the
> > last request was sent.
> >
> > Furthermore, the handleRequest() interface looks like it expects to send
> > the response within this function (blocking call). The interface even
> > changed from Qt6.4.1 to Qt6.5, and in Qt6.5 with the non-copyable
> > QHttpServerResponder parameter it is even harder to keep this struct for
> a
> > later response (only by std::moving).
> >
> > So, my question is, if QHttpServer is intended to support asynchronous
> > request processing (for arbitrary URL requests) sometime in the future?
> Or
> > am I missing the correct way to do this with the current API?
> >
> > Thanks in advance
> > Daniel
> > -------------- next part --------------
> > An HTML attachment was scrubbed...
> > URL:
> > <
> http://lists.qt-project.org/pipermail/interest/attachments/20230104/3b7c05
> > 8d/attachment.htm>
> _______________________________________________
> Interest mailing list
> Interest at qt-project.org
> https://lists.qt-project.org/listinfo/interest
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.qt-project.org/pipermail/interest/attachments/20230110/723a036e/attachment.htm>


More information about the Interest mailing list