Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Http request cannot be aborted #4757

Open
zacek opened this issue Nov 4, 2024 · 0 comments
Open

Http request cannot be aborted #4757

zacek opened this issue Nov 4, 2024 · 0 comments
Labels

Comments

@zacek
Copy link

zacek commented Nov 4, 2024

There is no way to interrupt a http request if there is no response from the other side. The connection succeeds but if the other side doesn't send any response, the requests becomes stalled - the receiveResponse() call will block forever. It is even not possible to interrupt it with session methods abort() or reset(), see the example below.

To Reproduce

  1. Start a local fake web server using net-cat, e.g. nc 127.0.0.1 8000 (it will not send anything in response)
  2. Run a program below which opens a connection to 127.0.0.1:8000 and sends a GET request

Expected behavior
There should be some internal timeout working. Method setTimeout() ensures a timeout is triggered if the connection cannot be established within the time span but once the connection is established it is waiting for the response status code and message. If no response arrives the timeout is not triggered. Even if I try to handle the timeout manually (as in the example below) it is not possible to abort it using any session method available.
I may be missing something, please advise.

Example

#include "Poco/StreamCopier.h"
#include "Poco/URI.h"
#include "Poco/Net/HTTPStreamFactory.h"
#include "Poco/Net/NetException.h"
#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include "Poco/Stopwatch.h"

#include <iostream>
#include <chrono>
#include <thread>
#include <future>
#include <atomic>
#include <condition_variable>


int main(int argc, char** argv)
{
    Poco::Net::HTTPStreamFactory::registerFactory();

    // timeoute monitoring
    std::condition_variable cv;
    std::mutex cvLock;
    std::atomic<bool> fullResponseRead = false;

    // set timeout to 3 seconds
    Poco::UInt64 timeout = 3 * 1000000L; // in microseconds
    const Poco::Timespan ts(timeout, 0L);

    // stop watch to measure total time
    Poco::Stopwatch stopWatch;
    stopWatch.start();

    // read buffer
    std::string readBuffer;
    std::future<void> handleStreamTimeout;

    // session
    Poco::URI requestUri("http://127.0.0.1:8000");
    Poco::Net::HTTPClientSession  httpSession;
    httpSession.setHost(requestUri.getHost());
    if (requestUri.getPort() > 0) {
        httpSession.setPort(requestUri.getPort());
    }

    // request
    Poco::Net::HTTPRequest  httpRequest(Poco::Net::HTTPRequest::HTTP_GET, requestUri.getPathAndQuery(), Poco::Net::HTTPMessage::HTTP_1_0);
    Poco::Net::HTTPResponse httpResponse;

    try {
        readBuffer.clear();
        fullResponseRead = false;

        httpSession.setKeepAlive(false);
        httpSession.setTimeout(ts);

        std::cout << " ---------- SEND request -------- " << std::endl;
        httpSession.sendRequest(httpRequest);
        std::cout << " ---------- SENT -------- " << std::endl;

        // for POCO++ 1.13 use httpSession.setReceiveTimeout(ts) instead
        Poco::Net::StreamSocket &str = httpSession.socket();
        str.setReceiveTimeout(ts);

        std::cout << " ---------- PREPARING TIMEOUT HANDLER -------- " << std::endl;
        std::future<void> handleStreamTimeout = std::async(
            std::launch::async,
            [&httpSession, &fullResponseRead, &stopWatch, &timeout, &cv, &cvLock]() -> void {

                auto lock = std::unique_lock<std::mutex>(cvLock);
                Poco::UInt64 elapsed = stopWatch.elapsed();
                while (!fullResponseRead.load() && timeout > elapsed) {
                    cv.wait_until(lock, std::chrono::system_clock::now() + std::chrono::microseconds(timeout - elapsed));
                    std::cout << "------- TIMEOUT WAKEUP" << std::endl;
                    if (fullResponseRead.load()) {
                        // we are notified that the response was successfully read
                        break;
                    }
                    elapsed = stopWatch.elapsed();
                }

                // timed out
                // these are attempts to abort the session, none of them works
                std::cout << "------- TIMEOUT reset" << std::endl;
                httpSession.reset();
                std::cout << "------- TIMEOUT socket close" << std::endl;
                httpSession.socket().close();
                std::cout << "------- TIMEOUT abort" << std::endl;
                httpSession.abort();
                std::cout << "------- TIMEOUT end " << elapsed << std::endl;
            }
        );

        std::cout << " ---------- WAITING -------- " << std::endl;
        std::istream& httpResponseStream = httpSession.receiveResponse(httpResponse);

        std::cout << " ---------- REPORTING -------- " << std::endl;
        std::cout << "Status: " << httpResponse.getStatus()
             << std::endl << "Reason: " << httpResponse.getReason()
             << std::endl;

        // copy response to a string
        std::cout << " ---------- COPYING -------- " << std::endl;
        Poco::StreamCopier::copyToString(httpResponseStream, readBuffer, 1);

        // stop the timeout timer
        {
            std::lock_guard<std::mutex> lock(cvLock);
            fullResponseRead = true;
            cv.notify_all();
        }
        // wait for the timeout timer job to finish
        handleStreamTimeout.get();

        std::cout << " ---------- FINISHED -------- " << std::endl;

    } catch (Poco::TimeoutException const &ex) {
        std::cout << "Timed out: " << ex.displayText() << std::endl;
    } catch (Poco::Net::NetException const &ex) {
        std::cout << "Network exception " << ex.displayText() << std::endl;
    } catch (...) {
        // just in case we get unexpected exception
        std::exception_ptr pe = std::current_exception();
        std::cout << "Unknown exception " << (pe ? pe.__cxa_exception_type()->name() : "null") << std::endl;
    }

    // wait for the timeout job to finish (if not finished due to an exception)
    stopWatch.stop();
    if (handleStreamTimeout.valid()) {
        std::lock_guard<std::mutex> lock(cvLock);
        // let the timer know it can stop, wait for it to finish
        fullResponseRead = true;
        cv.notify_all();
        handleStreamTimeout.get(); // finish the thread
    }

    std::cout << " ---------- DONE -------- " << std::endl;
    std::cout << readBuffer << std::endl;
}

Compile with

g++ -std=c++20  -c page_timeout.cpp -o page_timeout.o
g++ -std=c++20 -D_REENTRANT -Wall -o page_timeout page_timeout.o -lPocoNet -lPocoFoundation 

The output is:

 ---------- SEND request -------- 
 ---------- SENT -------- 
 ---------- PREPARING TIMEOUT HANDLER -------- 
 ---------- WAITING -------- 
------- TIMEOUT WAKEUP
------- TIMEOUT reset
------- TIMEOUT socket close
------- TIMEOUT abort

As you can see, none of the attempts to abort the session works, call to abort() even blocks forever (TIMEOUT END) is not reached at all. If I interrupt nc it also triggers network exception in the client with message No message received.

Environment information:

  • OS Type and Version: Ubuntu 24.4,
  • POCO Version: 1.11.0-4 (system)
  • g++ compiler: 13.2.0
@zacek zacek added the bug label Nov 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant