From 62d20a9825fc501e0bf480c35f1589e104efcdda Mon Sep 17 00:00:00 2001 From: minggo Date: Wed, 10 Apr 2019 18:33:55 -0700 Subject: [PATCH] Add clear request and responses method to httpclient (#19598) * Added functionality to clear pending responses and requests in the http client * Responses and Requests now are processed in similar way * Fix for clearing the http requests * Added tests for HttpClient::clearResponseAndRequestQueue --- cocos/network/HttpClient-android.cpp | 35 +++++ cocos/network/HttpClient-apple.mm | 36 ++++- cocos/network/HttpClient.cpp | 35 +++++ cocos/network/HttpClient.h | 31 ++++ .../NetworkTest/HttpClientTest.cpp | 147 ++++++++++++++++++ .../NetworkTest/HttpClientTest.h | 24 +++ 6 files changed, 307 insertions(+), 1 deletion(-) diff --git a/cocos/network/HttpClient-android.cpp b/cocos/network/HttpClient-android.cpp index 2f39ee4227..f423de79bc 100644 --- a/cocos/network/HttpClient-android.cpp +++ b/cocos/network/HttpClient-android.cpp @@ -885,6 +885,8 @@ HttpClient::HttpClient() , _threadCount(0) , _cookie(nullptr) , _requestSentinel(new HttpRequest()) +, _clearRequestPredicate(nullptr) +, _clearResponsePredicate(nullptr) { CCLOG("In the constructor of HttpClient!"); increaseThreadCount(); @@ -991,6 +993,39 @@ void HttpClient::dispatchResponseCallbacks() } } +void HttpClient::clearResponseAndRequestQueue() +{ + _requestQueueMutex.lock(); + if (_requestQueue.size()) + { + for (auto it = _requestQueue.begin(); it != _requestQueue.end();) + { + if(!_clearRequestPredicate || + _clearRequestPredicate((*it))) + { + (*it)->release(); + it =_requestQueue.erase(it); + } + else + { + it++; + } + } + } + _requestQueueMutex.unlock(); + + _responseQueueMutex.lock(); + if (_clearResponsePredicate) + { + _responseQueue.erase(std::remove_if(_responseQueue.begin(), _responseQueue.end(), _clearResponsePredicate), _responseQueue.end()); + } + else + { + _responseQueue.clear(); + } + _responseQueueMutex.unlock(); +} + void HttpClient::increaseThreadCount() { _threadCountMutex.lock(); diff --git a/cocos/network/HttpClient-apple.mm b/cocos/network/HttpClient-apple.mm index 049f386a7b..6e3e4f418c 100644 --- a/cocos/network/HttpClient-apple.mm +++ b/cocos/network/HttpClient-apple.mm @@ -371,6 +371,8 @@ HttpClient::HttpClient() , _threadCount(0) , _cookie(nullptr) , _requestSentinel(new HttpRequest()) +, _clearRequestPredicate(nullptr) +, _clearResponsePredicate(nullptr) { CCLOG("In the constructor of HttpClient!"); memset(_responseMessage, 0, sizeof(char) * RESPONSE_BUFFER_SIZE); @@ -534,7 +536,39 @@ void HttpClient::processResponse(HttpResponse* response, char* responseMessage) response->setErrorBuffer(responseMessage); } } - + +void HttpClient::clearResponseAndRequestQueue() +{ + _requestQueueMutex.lock(); + if (_requestQueue.size()) + { + for (auto it = _requestQueue.begin(); it != _requestQueue.end();) + { + if(!_clearRequestPredicate || + _clearRequestPredicate((*it))) + { + (*it)->release(); + it =_requestQueue.erase(it); + } + else + { + it++; + } + } + } + _requestQueueMutex.unlock(); + + _responseQueueMutex.lock(); + if (_clearResponsePredicate) + { + _responseQueue.erase(std::remove_if(_responseQueue.begin(), _responseQueue.end(), _clearResponsePredicate), _responseQueue.end()); + } + else + { + _responseQueue.clear(); + } + _responseQueueMutex.unlock(); +} void HttpClient::increaseThreadCount() { diff --git a/cocos/network/HttpClient.cpp b/cocos/network/HttpClient.cpp index a01cd79113..e180099423 100644 --- a/cocos/network/HttpClient.cpp +++ b/cocos/network/HttpClient.cpp @@ -403,6 +403,8 @@ HttpClient::HttpClient() , _threadCount(0) , _cookie(nullptr) , _requestSentinel(new HttpRequest()) +, _clearRequestPredicate(nullptr) +, _clearResponsePredicate(nullptr) { CCLOG("In the constructor of HttpClient!"); memset(_responseMessage, 0, RESPONSE_BUFFER_SIZE * sizeof(char)); @@ -575,6 +577,39 @@ void HttpClient::processResponse(HttpResponse* response, char* responseMessage) response->setSucceed(true); } } + +void HttpClient::clearResponseAndRequestQueue() +{ + _requestQueueMutex.lock(); + if (_requestQueue.size()) + { + for (auto it = _requestQueue.begin(); it != _requestQueue.end();) + { + if(!_clearRequestPredicate || + _clearRequestPredicate((*it))) + { + (*it)->release(); + it =_requestQueue.erase(it); + } + else + { + it++; + } + } + } + _requestQueueMutex.unlock(); + + _responseQueueMutex.lock(); + if (_clearResponsePredicate) + { + _responseQueue.erase(std::remove_if(_responseQueue.begin(), _responseQueue.end(), _clearResponsePredicate), _responseQueue.end()); + } + else + { + _responseQueue.clear(); + } + _responseQueueMutex.unlock(); +} void HttpClient::increaseThreadCount() { diff --git a/cocos/network/HttpClient.h b/cocos/network/HttpClient.h index 2e31b4202d..16ed49c553 100644 --- a/cocos/network/HttpClient.h +++ b/cocos/network/HttpClient.h @@ -150,6 +150,34 @@ public: std::mutex& getCookieFileMutex() {return _cookieFileMutex;} std::mutex& getSSLCaFileMutex() {return _sslCaFileMutex;} + + typedef std::function ClearRequestPredicate; + typedef std::function ClearResponsePredicate; + + /** + * Clears the pending http responses and http requests + * If defined, the method uses the ClearRequestPredicate and ClearResponsePredicate + * to check for each request/response which to delete + */ + void clearResponseAndRequestQueue(); + + /** + * Sets a predicate function that is going to be called to determine if we proceed + * each of the pending requests + * + * @param predicate function that will be called + */ + void setClearRequestPredicate(ClearRequestPredicate predicate) { _clearRequestPredicate = predicate; } + + /** + Sets a predicate function that is going to be called to determine if we proceed + * each of the pending requests + * + * @param cb predicate function that will be called + */ + void setClearResponsePredicate(ClearResponsePredicate predicate) { _clearResponsePredicate = predicate; } + + private: HttpClient(); virtual ~HttpClient(); @@ -203,6 +231,9 @@ private: char _responseMessage[RESPONSE_BUFFER_SIZE]; HttpRequest* _requestSentinel; + + ClearRequestPredicate _clearRequestPredicate; + ClearResponsePredicate _clearResponsePredicate; }; } // namespace network diff --git a/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/HttpClientTest.cpp b/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/HttpClientTest.cpp index 6a73ca2cac..0e2e4cbdc8 100644 --- a/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/HttpClientTest.cpp +++ b/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/HttpClientTest.cpp @@ -33,6 +33,7 @@ using namespace cocos2d::network; HttpClientTests::HttpClientTests() { ADD_TEST_CASE(HttpClientTest); + ADD_TEST_CASE(HttpClientClearRequestsTest); } HttpClientTest::HttpClientTest() @@ -398,3 +399,149 @@ void HttpClientTest::onHttpRequestCompleted(HttpClient *sender, HttpResponse *re log("request ref count not 2, is %d", response->getHttpRequest()->getReferenceCount()); } } + + + + +HttpClientClearRequestsTest::HttpClientClearRequestsTest() +: _labelStatusCode(nullptr) +{ + auto winSize = Director::getInstance()->getWinSize(); + + const int MARGIN = 40; + const int SPACE = 35; + + const int CENTER = winSize.width / 2; + + auto menuRequest = Menu::create(); + menuRequest->setPosition(Vec2::ZERO); + addChild(menuRequest); + + // Get + auto labelGet = Label::createWithTTF("Test Clear all Get", "fonts/arial.ttf", 22); + auto itemGet = MenuItemLabel::create(labelGet, CC_CALLBACK_1(HttpClientClearRequestsTest::onMenuCancelAllClicked, this)); + itemGet->setPosition(CENTER, winSize.height - MARGIN - SPACE); + menuRequest->addChild(itemGet); + + // Post + auto labelPost = Label::createWithTTF("Test Clear but only with the tag DELETE", "fonts/arial.ttf", 22); + auto itemPost = MenuItemLabel::create(labelPost, CC_CALLBACK_1(HttpClientClearRequestsTest::onMenuCancelSomeClicked, this)); + itemPost->setPosition(CENTER, winSize.height - MARGIN - 2 * SPACE); + menuRequest->addChild(itemPost); + + // Response Code Label + _labelStatusCode = Label::createWithTTF("HTTP Status Code", "fonts/arial.ttf", 18); + _labelStatusCode->setPosition(winSize.width / 2, winSize.height - MARGIN - 6 * SPACE); + addChild(_labelStatusCode); + + // Tracking Data Label + _labelTrakingData = Label::createWithTTF("Got 0 of 0 expected http requests", "fonts/arial.ttf", 16); + _labelTrakingData->setPosition(CENTER, winSize.height - MARGIN - 5 * SPACE); + addChild(_labelTrakingData); + + _totalExpectedRequests = 0; + _totalProcessedRequests = 0; +} + +HttpClientClearRequestsTest::~HttpClientClearRequestsTest() +{ + HttpClient::destroyInstance(); +} + +void HttpClientClearRequestsTest::onMenuCancelAllClicked(cocos2d::Ref *sender) +{ + for (int i=0; i < 10; i++) + { + HttpRequest* request = new (std::nothrow) HttpRequest(); + std::stringstream url; + url << "http://cocos2d-x.org/images/logo.png?id=" << std::to_string(i); + request->setUrl(url.str()); + request->setRequestType(HttpRequest::Type::GET); + request->setResponseCallback(CC_CALLBACK_2(HttpClientClearRequestsTest::onHttpRequestCompleted, this)); + + url.str(""); + url << "TEST_" << std::to_string(i); + request->setTag(url.str()); + HttpClient::getInstance()->send(request); + request->release(); + } + + _totalProcessedRequests = 0; + _totalExpectedRequests = 1; + + HttpClient::getInstance()->setClearRequestPredicate(nullptr); + HttpClient::getInstance()->setClearResponsePredicate(nullptr); + HttpClient::getInstance()->clearResponseAndRequestQueue(); + + // waiting + _labelStatusCode->setString("waiting..."); +} + +void HttpClientClearRequestsTest::onMenuCancelSomeClicked(cocos2d::Ref *sender) +{ + // test 1 + for (int i=0; i < 10; i++) + { + HttpRequest* request = new (std::nothrow) HttpRequest(); + std::stringstream url; + url << "http://cocos2d-x.org/images/logo.png?id=" << std::to_string(i); + request->setUrl(url.str()); + request->setRequestType(HttpRequest::Type::GET); + request->setResponseCallback(CC_CALLBACK_2(HttpClientClearRequestsTest::onHttpRequestCompleted, this)); + + url.str(""); + if (i < 5) { + url << "TEST_" << std::to_string(i); + _totalExpectedRequests++; + } + else { + url << "DELETE_" << std::to_string(i); + } + request->setTag(url.str()); + HttpClient::getInstance()->send(request); + request->release(); + } + + HttpClient::getInstance()->setClearRequestPredicate([&](HttpRequest* req) + { + auto r = !!strstr(req->getTag(), "DELETE_"); + return r; + }); + HttpClient::getInstance()->setClearResponsePredicate(nullptr); + HttpClient::getInstance()->clearResponseAndRequestQueue(); + + + // waiting + _labelStatusCode->setString("waiting..."); + +} + +void HttpClientClearRequestsTest::onHttpRequestCompleted(HttpClient *sender, HttpResponse *response) +{ + if (!response) + { + return; + } + + // You can get original request type from: response->request->reqType + if (0 != strlen(response->getHttpRequest()->getTag())) + { + log("%s completed", response->getHttpRequest()->getTag()); + } + + long statusCode = response->getResponseCode(); + char statusString[64] = {}; + sprintf(statusString, "HTTP Status Code: %ld, tag = %s", statusCode, response->getHttpRequest()->getTag()); + _labelStatusCode->setString(statusString); + log("response code: %ld", statusCode); + + _totalProcessedRequests++; + sprintf(statusString, "Got %d of %d expected http requests", _totalProcessedRequests, _totalExpectedRequests); + _labelTrakingData->setString(statusString); + + if (!response->isSucceed()) + { + log("response failed"); + log("error buffer: %s", response->getErrorBuffer()); + } +} diff --git a/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/HttpClientTest.h b/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/HttpClientTest.h index 6605ff3b60..72f88b2357 100644 --- a/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/HttpClientTest.h +++ b/tests/cpp-tests/Classes/ExtensionsTest/NetworkTest/HttpClientTest.h @@ -56,4 +56,28 @@ private: cocos2d::Label* _labelStatusCode; }; +class HttpClientClearRequestsTest : public TestCase +{ +public: + CREATE_FUNC(HttpClientClearRequestsTest); + + HttpClientClearRequestsTest(); + virtual ~HttpClientClearRequestsTest(); + + //Menu Callbacks + void onMenuCancelAllClicked(cocos2d::Ref *sender); + void onMenuCancelSomeClicked(cocos2d::Ref *sender); + + //Http Response Callback + void onHttpRequestCompleted(cocos2d::network::HttpClient *sender, cocos2d::network::HttpResponse *response); + + virtual std::string title() const override { return "Http Request Test"; } + +private: + int _totalExpectedRequests; + int _totalProcessedRequests; + cocos2d::Label* _labelTrakingData; + cocos2d::Label* _labelStatusCode; +}; + #endif //__HTTPREQUESTHTTP_H