5

I'm working on an http server as part of my C++ application, based on Poco::Net HTTP. It's pretty straight-forward, using Poco::Net::HTTPServer, then a Poco::Net::HTTPRequestHandler subclass, implementing void handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp). In this method, processes can become time-consuming, and may even run endless in some situations.

Browsers use those server from inside a web application, by ajax requests (XMLHttpRequest), and sometimes abort() those requests (which closes the connection?!).

How can I detect this situation inside handleRequest(...) in order to stop processing?

btw: I literally need this flow of things. Recommendations regarding different, maybe asynchronous ways e.g. of passing the data will not work. If Poco can't do that, I have to replace it.

Neither the documentation nor some experiments with Poco::Net::HTTPServerRequest and Poco::Net::HTTPServerResponse helped me.

ginger
  • 271
  • 1
  • 2
  • 9

2 Answers2

2

I have also had this issue and asked this more specific question on StackOverflow about detecting disconnection for an image streamer. After working on this problem for a while, I figured out a solution.

TLDR

The key for being able to detect disconnections is to have access to the StreamSocket created by the HTTPServer. By having this, you can create a HTTPServerSession, and use the session.socket().sendBytes() function in a loop to send information to the client. This function is useful, because when the client disconnects, it throws an I/O Error, which can be handled in a try catch block, and break out from any loop that keeps sending the information. Also, in the Poco HTTPRequestHandler the HTTPServerResponse and the HTTPServerRequest is also created from this HTTPServerSession behind the scenes, but somewhy the session was not provided in the HTTPRequestHandler's handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) function, which I believe is a heavy defect.

So to come around this issue, I used some of the Poco::Net classes as base classes and made my own ICustomHTTPRequestHandler interface, which had the function void handleRequest(Poco::Net::HTTPServerSession& session, Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) = 0 abstract function, containing the session variable.

With this, detecting the disconnected clients looks like:

while (true) {
    try {
        std::string someString = "someString";
        session.socket().sendBytes(someString.c_str(), static_cast<int>(someString.length()));
    } catch (std::exception& e) {
        break;
    }
}

Long version with implementation

So to start with, we have the main.cpp, which contains a Poco ServerApplication as an example:

#include <cassert>
#include <iostream>

#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Util/Application.h"
#include "Poco/Util/HelpFormatter.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
#include "Poco/Util/ServerApplication.h"

#include "CustomHTTPRequestHandlerFactory.hpp"
#include "CustomHTTPServer.hpp"

class App final : public Poco::Util::ServerApplication {
 private:
    bool helpRequested{false};

    void initialize(Application& self) override {
        loadConfiguration();
        ServerApplication::initialize(self);
    }

    int main(const std::vector<std::string>& args) override {
        if (helpRequested) {
            return Application::EXIT_OK;
        }

        Poco::UInt16 port = static_cast<Poco::UInt16>(config().getInt("HTTPServer.port"));
        CustomHTTPServer httpServer(new CustomHTTPRequestHandlerFactory{},
                                   port,
                                   new Poco::Net::HTTPServerParams{});
        httpServer.start();

        waitForTerminationRequest();
        httpServer.stop();

        return Poco::Util::Application::EXIT_OK;
    }

    void defineOptions(Poco::Util::OptionSet& options) override {
        Poco::Util::ServerApplication::defineOptions(options);

        options.addOption(
            Poco::Util::Option("help", "h", "Display argument help information.")
                .required(false)
                .repeatable(false)
                .callback(Poco::Util::OptionCallback<App>(this, &App::handleHelp)));
    }

    void handleHelp(const std::string& name, const std::string& value) {
        std::ignore = name;
        std::ignore = value;
        Poco::Util::HelpFormatter helpFormatter(options());
        helpFormatter.format(std::cout);
        stopOptionsProcessing();
        helpRequested = true;
    }
};

int main(int argc, char** argv) {
    App app;
    return app.run(argc, argv);
}

In this, we create a CustomHTTPServer, and give it a CustomHTTPRequestHandlerFactory::Ptr, just like when creating a Poco::Net::HTTPServer. The CustomHTTPRequestHandlerFactory object is customized by the user and derived from the ICustomHTTPRequestHandlerFactory interface. I will show an example for the CustomHTTPRequestHandlerFactory class later, but first lets see the interface class. This will handle the incoming HTTP/TCP connections and call the specific requestHandlers based on the requested path.

ICustomHTTPRequestHandlerFactory.hpp

#pragma once

#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Net/HTTPRequestHandlerFactory.h"
#include "Poco/Net/HTTPResponse.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerSession.h"

#include "ICustomHTTPRequestHandler.hpp"

class ICustomHTTPRequestHandlerFactory {
 public:
    using Ptr = Poco::SharedPtr<ICustomHTTPRequestHandlerFactory>;
    ICustomHTTPRequestHandlerFactory() = default;
    virtual ~ICustomHTTPRequestHandlerFactory() = default;
    virtual ICustomHTTPRequestHandler::Ptr createRequestHandler(const Poco::Net::HTTPServerRequest& request) = 0;
};

This should return a ICustomHTTPRequestHandler::Ptr, when createRequestHandler(const Poco::Net::HTTPServerRequest& request) function is called.

ICustomHTTPRequestHandler.hpp

#pragma once

#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerSession.h"
#include "Poco/SharedPtr.h"

class ICustomHTTPRequestHandler {
 public:
    using Ptr = Poco::SharedPtr<ICustomHTTPRequestHandler>;
    ICustomHTTPRequestHandler() = default;
    virtual ~ICustomHTTPRequestHandler() = default;
    virtual void handleRequest(Poco::Net::HTTPServerSession& session,
                               Poco::Net::HTTPServerRequest& request,
                               Poco::Net::HTTPServerResponse& response) = 0;
};

This class contains the new handleRequest(Poco::Net::HTTPServerSession& session, ...) function, which will make it possible to detect disconnected clients through the session parameter.

CustomHTTPServer.hpp

#pragma once

#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/Net/TCPServer.h"
#include "Poco/ThreadPool.h"

#include "ICustomHTTPRequestHandlerFactory.hpp"
#include "CustomHTTPServerConnectionFactory.hpp"

class CustomHTTPServer : public Poco::Net::TCPServer {
 public:
    CustomHTTPServer(ICustomHTTPRequestHandlerFactory::Ptr pFactory,
                     Poco::UInt16 portNumber = 80,
                     Poco::Net::HTTPServerParams::Ptr pParams = new Poco::Net::HTTPServerParams)
      : Poco::Net::TCPServer(new CustomHTTPServerConnectionFactory(pParams, pFactory), portNumber, pParams) {}

    CustomHTTPServer(ICustomHTTPRequestHandlerFactory::Ptr pFactory,
                     const Poco::Net::ServerSocket& socket,
                     Poco::Net::HTTPServerParams::Ptr pParams = new Poco::Net::HTTPServerParams)
      : Poco::Net::TCPServer(new CustomHTTPServerConnectionFactory(pParams, pFactory), socket, pParams) {}

    CustomHTTPServer(ICustomHTTPRequestHandlerFactory::Ptr pFactory,
                     Poco::ThreadPool* threadPool,
                     const Poco::Net::ServerSocket& socket,
                     Poco::Net::HTTPServerParams::Ptr pParams = new Poco::Net::HTTPServerParams)
      : Poco::Net::TCPServer(new CustomHTTPServerConnectionFactory(pParams, pFactory), *threadPool, socket, pParams) {}
};

This class is simply forwarding the pFactory, pParams parameters to the CustomHTTPServerConnectionFactory, and initializes its base class Poco::Net::TCPServer.

CustomHTTPServerConnectionFactory.hpp

#pragma once
    
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/TCPServerConnectionFactory.h"

#include "ICustomHTTPRequestHandlerFactory.hpp"
#include "CustomHTTPServerConnection.hpp"

class CustomHTTPServerConnectionFactory : public Poco::Net::TCPServerConnectionFactory {
 private:
    Poco::Net::HTTPServerParams::Ptr pParams{};
    CustomHTTPRequestHandlerFactory::Ptr pFactory{};

 public:
    CustomHTTPServerConnectionFactory(Poco::Net::HTTPServerParams::Ptr apParams,
                                      ICustomHTTPRequestHandlerFactory::Ptr apFactory)
      : pParams{apParams}
      , pFactory{apFactory} {}

    Poco::Net::TCPServerConnection* createConnection(const Poco::Net::StreamSocket& socket) override {
        return new CustomHTTPServerConnection(socket, pParams, pFactory);
    }
};

This class provides a Poco::Net::StreamSocket through its base class Poco::Net::TCPServerConnectionFactory, which is forwarded along with pParams, pFactory to the CustomHTTPServerConnection constructor.

CustomHTTPServerConnection.hpp

#pragma once

#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/HTTPServerRequestImpl.h"
#include "Poco/Net/HTTPServerResponseImpl.h"
#include "Poco/Net/HTTPServerSession.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/TCPServerConnection.h"

#include "ICustomHTTPRequestHandlerFactory.hpp"
#include "ICustomHTTPRequestHandler.hpp"

class CustomHTTPServerConnection : public Poco::Net::TCPServerConnection {
 private:
    const Poco::Net::StreamSocket& socket{};
    Poco::Net::HTTPServerParams::Ptr pParams{};
    ICustomHTTPRequestHandlerFactory::Ptr pFactory{};

 public:
    explicit CustomHTTPServerConnection(const Poco::Net::StreamSocket& aSocket,
                                        Poco::Net::HTTPServerParams::Ptr apParams,
                                        ICustomHTTPRequestHandlerFactory::Ptr apFactory)
      : Poco::Net::TCPServerConnection(aSocket)
      , socket{aSocket}
      , pParams{apParams}
      , pFactory{apFactory} {}

    void run() override {
        Poco::Net::HTTPServerSession session(socket, pParams);
        Poco::Net::HTTPServerResponseImpl response(session);
        Poco::Net::HTTPServerRequestImpl request(response, session, pParams);
        ICustomHTTPRequestHandler::Ptr handler = pFactory->createRequestHandler(request);
        if (handler) {
            handler->handleRequest(session, request, response);
        }
    }
};

This is the class where the session, response and request variables are constructed, and passed to the appropriate handler. The handler is chosen by the request's path parameter in the user implemented CustomHTTPRequestHandlerFactory.

For example:

CustomHTTPRequestHandlerFactory.hpp

#pragma once

#include <string>

#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/URI.h"
#include "Poco/Util/ServerApplication.h"

#include "HTTPRequestHandlerAdapter.hpp"
#include "ICustomHTTPRequestHandler.hpp"
#include "ICustomHTTPRequestHandlerFactory.hpp"

#include "TimeHTTPRequestHandler.hpp"
#include "StreamHTTPRequestHandler.hpp"
#include "FaviconHTTPRequestHandler.hpp"
#include "PageHTTPRequestHandler.hpp"

class CustomHTTPRequestHandlerFactory : public ICustomHTTPRequestHandlerFactory {    
 public:
    ICustomHTTPRequestHandler::Ptr createRequestHandler(const Poco::Net::HTTPServerRequest& request) override {
        const Poco::URI uri(request.getURI());
        const std::string path = uri.getPath();

        if (path == "/") {
            return new PageHTTPRequestHandler();
        } else if (path == "/time") {
            return new HTTPRequestHandlerAdapter(new TimeHTTPRequestHandler{});
        } else if (path == "/stream") {
            return new StreamHTTPRequestHandler();
        } else if (path == "/favicon.ico") {
            return new FaviconHTTPRequestHandler();
        }
        return nullptr;
    }
};

FaviconHTTPRequestHandler.hpp

#pragma once

#include <fstream>
#include <sstream>
#include <string>

#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerSession.h"

#include "ICustomHTTPRequestHandler.hpp"
#include "ICustomHTTPRequestHandlerFactory.hpp"

class FaviconHTTPRequestHandler : public ICustomHTTPRequestHandler {
 public:
    void handleRequest(Poco::Net::HTTPServerSession& session,
                       Poco::Net::HTTPServerRequest& request,
                       Poco::Net::HTTPServerResponse& response) override {
        std::ignore = session;

        response.setVersion(request.getVersion());
        response.setStatus(Poco::Net::HTTPServerResponse::HTTP_OK);
        response.setContentType("image/gif");
        response.set("Access-Control-Allow-Origin", "*");
        std::ostream& ostr = response.send();

        std::ifstream favicon("../assets/gomb_icon.gif");
        ostr << favicon.rdbuf() << std::flush;
    }
};

PageHTTPRequestHandler.hpp

#pragma once

#include <sstream>
#include <string>

#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerSession.h"

#include "ICustomHTTPRequestHandler.hpp"
#include "ICustomHTTPRequestHandlerFactory.hpp"

class PageHTTPRequestHandler : public ICustomHTTPRequestHandler {
 public:
    void handleRequest(Poco::Net::HTTPServerSession& session,
                       Poco::Net::HTTPServerRequest& request,
                       Poco::Net::HTTPServerResponse& response) override {
        std::ignore = session;

        response.setVersion(request.getVersion());
        response.setStatus(Poco::Net::HTTPServerResponse::HTTP_OK);
        response.setContentType("text/html");
        std::ostream& ostr = response.send();

        ostr << "<html><head><title>HTTPTimeServer powered by "
                "POCO C++ Libraries</title></head>"
                "<body><p style=\"text-align: center; "
                "font-size: 48px;\">Hello"
                "</p></body></html>"
             << std::flush;
    }
};

StreamHTTPRequestHandler

#pragma once

#include <fstream>
#include <sstream>
#include <string>

#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerSession.h"
#include "Poco/Util/ServerApplication.h"

#include "ICustomHTTPRequestHandler.hpp"
#include "ICustomHTTPRequestHandlerFactory.hpp"

class StreamHTTPRequestHandler : public ICustomHTTPRequestHandler {
 public:
    void handleRequest(Poco::Net::HTTPServerSession& session,
                       Poco::Net::HTTPServerRequest& request,
                       Poco::Net::HTTPServerResponse& response) override {
        std::string boundary = "--BOUNDARY--";
        response.setVersion(request.getVersion());
        response.setStatus(Poco::Net::HTTPServerResponse::HTTP_OK);
        response.setContentType("multipart/x-mixed-replace; boundary=" + boundary);
        response.set("Access-Control-Allow-Origin", "*");
        response.set("Connection", "Close");
        response.set("Cache-Control",
                     "no-cache, no-store, must-revalidate, pre-check=0, post-check=0, max-age=0, false");
        response.set("Pragma", "no-cache");
        std::ostream& ostr = response.send();
        ostr << std::flush;

        std::ifstream favicon("../assets/gomb_icon.gif");
        std::stringstream ss;
        ss << favicon.rdbuf();
        std::string buf = ss.str();

        while (true) {
            try {
                std::string prelude =
                    boundary + "\r\nContent-Type: image/jpeg\r\nContent-Length: " + std::to_string(buf.length()) +
                    "\r\n\r\n";
                session.socket().sendBytes(prelude.c_str(), static_cast<int>(prelude.length()));
                session.socket().sendBytes(buf.c_str(), static_cast<int>(buf.length()));
            } catch (std::exception& e) {
                break;
            }
        }
    }
};

This class provides an example for detecting disconnected clients with Poco.

Adapter class

In case someone would also like to use the Poco::Net::HTTPRequestHandler class along with CustomHTTPRequestHandler and the CustomHTTPRequestHandlerFactory, they will need the following adapter class:

HTTPRequestHandlerAdapter.hpp

#pragma once

#include "Poco/Net/HTTPRequestHandler.h"

#include "ICustomHTTPRequestHandler.hpp"

class HTTPRequestHandlerAdapter : public ICustomHTTPRequestHandler {
 private:
    Poco::Net::HTTPRequestHandler* adaptee;

 public:
    explicit HTTPRequestHandlerAdapter(Poco::Net::HTTPRequestHandler* anAdaptee)
      : adaptee(anAdaptee) {}

    ~HTTPRequestHandlerAdapter() {
        delete adaptee;
    }

    void handleRequest(Poco::Net::HTTPServerSession& session,
                       Poco::Net::HTTPServerRequest& request,
                       Poco::Net::HTTPServerResponse& response) override {
        std::ignore = session;
        adaptee->handleRequest(request, response);
    }
};

For example, if someone would like to use POCO C++ Server Page Compiler, the compiler will generate Poco::Net::HTTPRequestHandler classes, something like the TimeHTTPRequestHandler class.

TimeHTTPRequestHandler.hpp

#pragma once

#include <string>

#include "Poco/Net/HTMLForm.h"
#include "Poco/Net/HTTPRequestHandler.h"

class TimeHTTPRequestHandler : public Poco::Net::HTTPRequestHandler {
 public:
    void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) override {
        response.setVersion(request.getVersion());
        response.setStatus(Poco::Net::HTTPServerResponse::HTTP_OK);
        response.setContentType("text/html");

        Poco::Net::HTMLForm form(request, request.stream());
        std::ostream& responseStream = response.send();
        responseStream << "\n";

        Poco::DateTime now;
        std::string dt(Poco::DateTimeFormatter::format(now, "%W, %e %b %y %H:%M:%S %Z"));
        responseStream << "\n";
        responseStream << "<html>\n";
        responseStream << "<head>\n";
        responseStream << "<title>HTTPTimeServer powered by POCO C++ Libraries and PageCompiler</title>\n";
        responseStream << "<meta http-equiv=\"refresh\" content=\"1\">\n";
        responseStream << "</head>\n";
        responseStream << "<body>\n";
        responseStream << "<p style=\"text-align: center; font-size: 48px;\">";
        responseStream << (dt);
        responseStream << "</p>\n";
        responseStream << "</body>\n";
        responseStream << "</html>";
    }
};

The adapter makes it possible to use these classes with the CustomHTTPRequestHandlerFactory, by returning a new Net::HTTPRequestHandlerAdapter(new TimeHTTPRequestHandler{}).

Daniel
  • 391
  • 4
  • 17
0

Few options:

  1. You can try "Poco::Net::Socket::select()", but it's OS limited.
  2. Switch to a "Poco::Net::WebSocket" with ping/pong heartbeat. If a socket is closed, "receiveFrame()" will throw an exception.
Andrey
  • 853
  • 9
  • 27