From 6cbbd9bf4e7b861a484774767c9a82059efacd9d Mon Sep 17 00:00:00 2001 From: Niels Sascha Reedijk Date: Fri, 3 Jun 2022 13:37:26 +0100 Subject: [PATCH] NetServices: Implement BHttpSession::Cancel() Change-Id: Iff0a7726e57f3e6bd4e9d0ebac08a370d25a62d7 --- docs/user/netservices/HttpSession.dox | 36 +++++++++++++++++ headers/private/netservices2/HttpSession.h | 2 + .../network/libnetservices2/HttpSession.cpp | 40 +++++++++++++++++++ .../net/netservices2/HttpProtocolTest.cpp | 21 ++++++++++ .../kits/net/netservices2/HttpProtocolTest.h | 1 + 5 files changed, 100 insertions(+) diff --git a/docs/user/netservices/HttpSession.dox b/docs/user/netservices/HttpSession.dox index c07b4bcd63..6ccfb42a63 100644 --- a/docs/user/netservices/HttpSession.dox +++ b/docs/user/netservices/HttpSession.dox @@ -183,6 +183,42 @@ namespace Network { */ +/*! + \fn void BHttpSession::Cancel(int32 identifier) + \brief Cancel a running request. + + When a request that is scheduled or running is cancelled, its \ref BHttpResult object will + be set to the \ref BNetworkRequestError exception with the \c Cancelled type. + + There are three possible outcomes: + 1. If the request is not yet scheduled, then it will never start. The result will be set to + the cancelled error state. + 2. If the request was started, then processing it will be terminated. The result will be set to + the cancelled error state. + 3. If the request was already finished, then nothing happens. The result will keep the value it + had assigned. The same if the request identifier is invalid. + + \param identifier The identifier for the request to cancel. + + \exception std::bad_alloc Error in case memory cannot be allocated. + + \since Haiku R1 +*/ + + +/*! + \fn void BHttpSession::Cancel(const BHttpResult& request) + \brief Cancel a running \a request. + + See the \ref BHttpSession::Cancel(int32 identifier) method for more details on how this method + works. + + \exception std::bad_alloc Error in case memory cannot be allocated. + + \since Haiku R1 +*/ + + } // namespace Network } // namespace BPrivate diff --git a/headers/private/netservices2/HttpSession.h b/headers/private/netservices2/HttpSession.h index 87d4319088..86de76b355 100644 --- a/headers/private/netservices2/HttpSession.h +++ b/headers/private/netservices2/HttpSession.h @@ -37,6 +37,8 @@ public: BHttpResult Execute(BHttpRequest&& request, std::unique_ptr target = nullptr, BMessenger observer = BMessenger()); + void Cancel(int32 identifier); + void Cancel(const BHttpResult& request); private: struct Redirect; diff --git a/src/kits/network/libnetservices2/HttpSession.cpp b/src/kits/network/libnetservices2/HttpSession.cpp index 12ad6964f9..fd2df64b05 100644 --- a/src/kits/network/libnetservices2/HttpSession.cpp +++ b/src/kits/network/libnetservices2/HttpSession.cpp @@ -151,6 +151,7 @@ public: BHttpResult Execute(BHttpRequest&& request, std::unique_ptr target, BMessenger observer); + void Cancel(int32 identifier); private: // Thread functions @@ -239,6 +240,30 @@ BHttpSession::Impl::Execute(BHttpRequest&& request, std::unique_ptr tar } #include +void +BHttpSession::Impl::Cancel(int32 identifier) +{ + std::cout << "BHttpSession::Impl::Cancel for " << identifier << std::endl; + auto lock = AutoLocker(fLock); + // Check if the item is on the control queue + auto item = std::find_if(fControlQueue.begin(), fControlQueue.end(), + [&identifier](const auto& arg){ return arg.Id() == identifier; }); + if (item != fControlQueue.end()) { + try { + throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::Canceled); + } catch (...) { + item->SetError(std::current_exception()); + } + fControlQueue.erase(item); + return; + } + + // Get it on the list for deletion in the data queue + fCancelList.push_back(identifier); + release_sem(fDataQueueSem); +} + + /*static*/ status_t BHttpSession::Impl::ControlThreadFunc(void* arg) { @@ -389,6 +414,7 @@ BHttpSession::Impl::DataThreadFunc(void* arg) for (auto it = data->connectionMap.cbegin(); it != data->connectionMap.cend(); it++) { offset++; if (it->second.Id() == id) { + std::cout << "DataThreadFunc() [" << id << "] cancelling request" << std::endl; data->objectList[offset].events = EVENT_CANCELLED; break; } @@ -585,6 +611,20 @@ BHttpSession::Execute(BHttpRequest&& request, std::unique_ptr target, B } +void +BHttpSession::Cancel(int32 identifier) +{ + fImpl->Cancel(identifier); +} + + +void +BHttpSession::Cancel(const BHttpResult& request) +{ + fImpl->Cancel(request.Identity()); +} + + // #pragma mark -- BHttpSession::Request (helpers) BHttpSession::Request::Request(BHttpRequest&& request, std::unique_ptr target, diff --git a/src/tests/kits/net/netservices2/HttpProtocolTest.cpp b/src/tests/kits/net/netservices2/HttpProtocolTest.cpp index 1bb9632eec..62af4e0758 100644 --- a/src/tests/kits/net/netservices2/HttpProtocolTest.cpp +++ b/src/tests/kits/net/netservices2/HttpProtocolTest.cpp @@ -496,6 +496,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent) testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest); testCaller->addThread("BasicAuthTest", &HttpIntegrationTest::BasicAuthTest); testCaller->addThread("StopOnErrorTest", &HttpIntegrationTest::StopOnErrorTest); + testCaller->addThread("RequestCancelTest", &HttpIntegrationTest::RequestCancelTest); suite.addTest(testCaller); parent.addTest("HttpIntegrationTest", &suite); @@ -518,6 +519,7 @@ HttpIntegrationTest::AddTests(BTestSuite& parent) testCaller->addThread("AutoRedirectTest", &HttpIntegrationTest::AutoRedirectTest); testCaller->addThread("BasicAuthTest", &HttpIntegrationTest::BasicAuthTest); testCaller->addThread("StopOnErrorTest", &HttpIntegrationTest::StopOnErrorTest); + testCaller->addThread("RequestCancelTest", &HttpIntegrationTest::RequestCancelTest); suite.addTest(testCaller); parent.addTest("HttpsIntegrationTest", &suite); @@ -714,3 +716,22 @@ HttpIntegrationTest::StopOnErrorTest() CPPUNIT_ASSERT(result.Fields().CountFields() == 0); CPPUNIT_ASSERT(result.Body().text.Length() == 0); } + + +void +HttpIntegrationTest::RequestCancelTest() +{ + // Test the cancellation functionality + // TODO: this test potentially fails if the case is executed before the cancellation is + // processed. In practise, the cancellation always comes first. When the server + // supports a wait parameter, then this test can be made more robust. + auto request = BHttpRequest(BUrl(fTestServer.BaseUrl(), "/")); + auto result = fSession.Execute(std::move(request)); + fSession.Cancel(result); + try { + result.Body(); + CPPUNIT_FAIL("Expected exception because request was cancelled"); + } catch (const BNetworkRequestError& e) { + CPPUNIT_ASSERT(e.Type() == BNetworkRequestError::Canceled); + } +} diff --git a/src/tests/kits/net/netservices2/HttpProtocolTest.h b/src/tests/kits/net/netservices2/HttpProtocolTest.h index 41cb424138..d49c53dd72 100644 --- a/src/tests/kits/net/netservices2/HttpProtocolTest.h +++ b/src/tests/kits/net/netservices2/HttpProtocolTest.h @@ -45,6 +45,7 @@ public: void AutoRedirectTest(); void BasicAuthTest(); void StopOnErrorTest(); + void RequestCancelTest(); static void AddTests(BTestSuite& suite);