]> git.mxchange.org Git - simgear.git/commitdiff
HTTP request cancellation
authorJames Turner <zakalawe@mac.com>
Thu, 14 Apr 2016 08:15:57 +0000 (09:15 +0100)
committerJames Turner <zakalawe@mac.com>
Thu, 14 Apr 2016 08:16:36 +0000 (09:16 +0100)
- replaces abort() with something more structured.

simgear/canvas/elements/CanvasImage.cxx
simgear/io/HTTPClient.cxx
simgear/io/HTTPClient.hxx
simgear/io/HTTPFileRequest.cxx
simgear/io/HTTPRequest.cxx
simgear/io/HTTPRequest.hxx
simgear/io/test_HTTP.cxx
simgear/package/Install.cxx
simgear/package/Root.cxx
simgear/package/Root.hxx

index 99d332c43ee6fdf1678d8d0dbdad61f4b6fde5be..152cb11296bbd382bb838ccb4be3808a502197d4 100644 (file)
@@ -155,8 +155,9 @@ namespace canvas
   //----------------------------------------------------------------------------
   Image::~Image()
   {
-    if( _http_request )
-      _http_request->abort("image destroyed");
+      if( _http_request ) {
+          Canvas::getSystemAdapter()->getHTTPClient()->cancelRequest(_http_request, "image destroyed");
+      }
   }
 
   //----------------------------------------------------------------------------
@@ -618,7 +619,7 @@ namespace canvas
       // Abort pending request
       if( _http_request )
       {
-        _http_request->abort("setting new image");
+          Canvas::getSystemAdapter()->getHTTPClient()->cancelRequest(_http_request, "setting new image");
         _http_request.reset();
       }
 
index 03302e51e0d0d0f46eac1efc93801dfd2762f2fd..d5ef1ea5108039ff2dd8e01eea27f0141d69be03 100644 (file)
@@ -78,7 +78,6 @@ class Client::ClientPrivate
 public:
 #if defined(ENABLE_CURL)
     CURLM* curlMulti;
-    bool haveActiveRequests;
 
     void createCurlMulti()
     {
@@ -94,6 +93,9 @@ public:
 
 
     }
+
+    typedef std::map<Request_ptr, CURL*> RequestCurlMap;
+    RequestCurlMap requests;
 #else
     NetChannelPoller poller;
 // connections by host (potentially more than one)
@@ -205,6 +207,8 @@ public:
 
     // closing of the connection from the server side when getting the body,
         bool canCloseState = (state == STATE_GETTING_BODY);
+        bool isCancelling = (state == STATE_CANCELLING);
+
         if (canCloseState && activeRequest) {
             // check bodyTransferSize matches how much we actually transferred
             if (bodyTransferSize > 0) {
@@ -229,7 +233,7 @@ public:
                 sentRequests.erase(sentRequests.begin());
             }
 
-            if (activeRequest) {
+            if (activeRequest && !isCancelling) {
                 activeRequest->setFailure(500, "server closed connection");
                 // remove the failed request from sentRequests, so it does
                 // not get restored
@@ -262,6 +266,39 @@ public:
         tryStartNextRequest();
     }
 
+    void cancelRequest(const Request_ptr& r)
+    {
+        RequestList::iterator it = std::find(sentRequests.begin(),
+                                             sentRequests.end(), r);
+        if (it != sentRequests.end()) {
+            sentRequests.erase(it);
+
+            if ((r == activeRequest) || !activeRequest) {
+                // either the cancelling request is active, or we're in waiting
+                // for response state - close now
+                setState(STATE_CANCELLING);
+                close();
+
+                setState(STATE_CLOSED);
+                activeRequest = NULL;
+                _contentDecoder.reset();
+            } else if (activeRequest) {
+                SG_LOG(SG_IO, SG_INFO, "con:" << _connectionId << " cancelling non-active: " << r->url());
+
+                // has been sent but not active, let the active finish and
+                // then close. Otherwise cancelling request #2 would mess up
+                // active transfer #1
+                activeRequest->setCloseAfterComplete();
+            }
+        } // of request has been sent
+
+        // simpler case, not sent yet just remove from the queue
+        it = std::find(queuedRequests.begin(), queuedRequests.end(), r);
+        if (it != queuedRequests.end()) {
+            queuedRequests.erase(it);
+        }
+    }
+
     void beginResponse()
     {
         assert(!sentRequests.empty());
@@ -528,6 +565,7 @@ private:
         STATE_GETTING_CHUNKED_BYTES,
         STATE_GETTING_TRAILER,
         STATE_SOCKET_ERROR,
+        STATE_CANCELLING,        ///< cancelling an acitve request
         STATE_CLOSED             ///< connection should be closed now
     };
 
@@ -787,34 +825,43 @@ void Client::update(int waitTimeout)
 #if defined(ENABLE_CURL)
     int remainingActive, messagesInQueue;
     curl_multi_perform(d->curlMulti, &remainingActive);
-    d->haveActiveRequests = (remainingActive > 0);
 
     CURLMsg* msg;
     while ((msg = curl_multi_info_read(d->curlMulti, &messagesInQueue))) {
       if (msg->msg == CURLMSG_DONE) {
-        Request* req;
+        Request* rawReq = 0;
         CURL *e = msg->easy_handle;
-        curl_easy_getinfo(e, CURLINFO_PRIVATE, &req);
+        curl_easy_getinfo(e, CURLINFO_PRIVATE, &rawReq);
+
+        // ensure request stays valid for the moment
+        // eg if responseComplete cancels us
+        Request_ptr req(rawReq);
 
         long responseCode;
         curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &responseCode);
 
+          // remove from the requests map now,
+          // in case the callbacks perform a cancel. We'll use
+          // the absence from the request dict in cancel to avoid
+          // a double remove
+          ClientPrivate::RequestCurlMap::iterator it = d->requests.find(req);
+          assert(it != d->requests.end());
+          assert(it->second == e);
+          d->requests.erase(it);
+
         if (msg->data.result == 0) {
           req->responseComplete();
         } else {
-          fprintf(stderr, "Result: %d - %s\n",
-                msg->data.result, curl_easy_strerror(msg->data.result));
+          SG_LOG(SG_IO, SG_WARN, "CURL Result:" << msg->data.result << " " << curl_easy_strerror(msg->data.result));
           req->setFailure(msg->data.result, curl_easy_strerror(msg->data.result));
         }
 
         curl_multi_remove_handle(d->curlMulti, e);
-
-        // balance the reference we take in makeRequest
-        SGReferenced::put(req);
         curl_easy_cleanup(e);
-      }
-      else {
-        SG_LOG(SG_IO, SG_ALERT, "CurlMSG:" << msg->msg);
+      } else {
+          // should never happen since CURLMSG_DONE is the only code
+          // defined!
+          SG_LOG(SG_IO, SG_ALERT, "unknown CurlMSG:" << msg->msg);
       }
     } // of curl message processing loop
     SGTimeStamp::sleepForMSec(waitTimeout);
@@ -880,11 +927,14 @@ void Client::makeRequest(const Request_ptr& r)
     r->_client = this;
 
 #if defined(ENABLE_CURL)
+    ClientPrivate::RequestCurlMap::iterator rit = d->requests.find(r);
+    assert(rit == d->requests.end());
+
     CURL* curlRequest = curl_easy_init();
     curl_easy_setopt(curlRequest, CURLOPT_URL, r->url().c_str());
 
-    // manually increase the ref count of the request
-    SGReferenced::get(r.get());
+    d->requests[r] = curlRequest;
+
     curl_easy_setopt(curlRequest, CURLOPT_PRIVATE, r.get());
     // disable built-in libCurl progress feedback
     curl_easy_setopt(curlRequest, CURLOPT_NOPROGRESS, 1);
@@ -950,9 +1000,9 @@ void Client::makeRequest(const Request_ptr& r)
     }
 
     curl_multi_add_handle(d->curlMulti, curlRequest);
-    d->haveActiveRequests = true;
 
-// FIXME - premature?
+// this seems premature, but we don't have a callback from Curl we could
+// use to trigger when the requst is actually sent.
     r->requestStart();
 
 #else
@@ -1034,6 +1084,33 @@ void Client::makeRequest(const Request_ptr& r)
 #endif
 }
 
+void Client::cancelRequest(const Request_ptr &r, std::string reason)
+{
+#if defined(ENABLE_CURL)
+    ClientPrivate::RequestCurlMap::iterator it = d->requests.find(r);
+    if(it == d->requests.end()) {
+        // already being removed, presumably inside ::update()
+        // nothing more to do
+        return;
+    }
+
+    CURLMcode err = curl_multi_remove_handle(d->curlMulti, it->second);
+    assert(err == CURLM_OK);
+
+    // clear the request pointer form the curl-easy object
+    curl_easy_setopt(it->second, CURLOPT_PRIVATE, 0);
+
+    curl_easy_cleanup(it->second);
+    d->requests.erase(it);
+#else
+    ConnectionDict::iterator it = d->connections.begin();
+    for (; it != d->connections.end(); ++it) {
+        (it->second)->cancelRequest(r);
+    }
+#endif
+    r->setFailure(-1, reason);
+}
+
 //------------------------------------------------------------------------------
 FileRequestRef Client::save( const std::string& url,
                              const std::string& filename )
@@ -1088,7 +1165,7 @@ void Client::setProxy( const std::string& proxy,
 bool Client::hasActiveRequests() const
 {
   #if defined(ENABLE_CURL)
-    return d->haveActiveRequests;
+    return !d->requests.empty();
   #else
     ConnectionDict::const_iterator it = d->connections.begin();
     for (; it != d->connections.end(); ++it) {
@@ -1201,7 +1278,12 @@ size_t Client::requestHeaderCallback(char *rawBuffer, size_t size, size_t nitems
 void Client::debugDumpRequests()
 {
 #if defined(ENABLE_CURL)
-
+    SG_LOG(SG_IO, SG_INFO, "== HTTP request dump");
+    ClientPrivate::RequestCurlMap::iterator it = d->requests.begin();
+    for (; it != d->requests.end(); ++it) {
+        SG_LOG(SG_IO, SG_INFO, "\t" << it->first->url());
+    }
+    SG_LOG(SG_IO, SG_INFO, "==");
 #else
     SG_LOG(SG_IO, SG_INFO, "== HTTP connection dump");
     ConnectionDict::iterator it = d->connections.begin();
index 00682c4c7768d80ae0dd4077f83b1456d1e64c4d..a535b5b53de2728b1698293809aae59b062ed6e9 100644 (file)
@@ -49,6 +49,8 @@ public:
 
     void makeRequest(const Request_ptr& r);
 
+    void cancelRequest(const Request_ptr& r, std::string reason = std::string());
+
     /**
      * Download a resource and save it to a file.
      *
index 81ddb01231a97cae415dfbb1ff9551340123dc07..460216252ff897a492d74c518d10791cf697c0a2 100644 (file)
@@ -59,8 +59,6 @@ namespace HTTP
         SG_WARN,
         "HTTP::FileRequest: failed to open file '" << _filename << "'"
       );
-
-      abort("Failed to open file.");
     }
   }
 
index 63bae9650a20bd4a3867814200731c53bceefa42..6b06785ad00bf57d79fb12e77b971a9475362138 100644 (file)
@@ -366,23 +366,17 @@ void Request::setReadyState(ReadyState state)
 }
 
 //------------------------------------------------------------------------------
-void Request::abort()
-{
-  abort("Request aborted.");
-}
-
-//----------------------------------------------------------------------------
-void Request::abort(const std::string& reason)
+bool Request::closeAfterComplete() const
 {
-  setFailure(-1, reason);
-  _willClose = true;
+  // for non HTTP/1.1 connections, assume server closes
+  return _willClose || (_responseVersion != HTTP_1_1);
 }
 
 //------------------------------------------------------------------------------
-bool Request::closeAfterComplete() const
+
+void Request::setCloseAfterComplete()
 {
-  // for non HTTP/1.1 connections, assume server closes
-  return _willClose || (_responseVersion != HTTP_1_1);
+    _willClose = true;
 }
 
 //------------------------------------------------------------------------------
index 7c06fc8e3bd0184e6a23263767114338aabc171b..0def08880162c667d9f714f490dc99dbdd0ecbd4 100644 (file)
@@ -199,16 +199,6 @@ public:
 
     ReadyState readyState() const { return _ready_state; }
 
-    /**
-     * Request aborting this request.
-     */
-    void abort();
-
-    /**
-     * Request aborting this request and specify the reported reaseon for it.
-     */
-    void abort(const std::string& reason);
-
     bool closeAfterComplete() const;
     bool isComplete() const;
 
@@ -246,6 +236,8 @@ private:
     void processBodyBytes(const char* s, int n);
     void setReadyState(ReadyState state);
 
+    void setCloseAfterComplete();
+
     Client*       _client; // HTTP client we're active on
 
     std::string   _method;
index 4787a995b73861c5a8d16fe1e7f82aa28d7b44ac..44a6c638e8313136efab6a52921763d2de8dcdfe 100644 (file)
@@ -698,6 +698,74 @@ cout << "testing proxy close" << endl;
         COMPARE(tr->responseBytesReceived(), 0);
     }
 
+    // test cancel
+    {
+        cout <<  "cancel  request" << endl;
+        testServer.resetConnectCount();
+        cl.clearAllConnections();
+
+        cl.setProxy("", 80);
+        TestRequest* tr = new TestRequest("http://localhost:2000/test1");
+        HTTP::Request_ptr own(tr);
+        cl.makeRequest(tr);
+
+        TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
+        HTTP::Request_ptr own2(tr2);
+        cl.makeRequest(tr2);
+
+        TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
+        HTTP::Request_ptr own3(tr3);
+        cl.makeRequest(tr3);
+
+        cl.cancelRequest(tr, "my reason 1");
+
+        cl.cancelRequest(tr2, "my reason 2");
+
+        waitForComplete(&cl, tr3);
+
+        COMPARE(tr->responseCode(), -1);
+        COMPARE(tr2->responseReason(), "my reason 2");
+
+        COMPARE(tr3->responseLength(), strlen(BODY1));
+        COMPARE(tr3->responseBytesReceived(), strlen(BODY1));
+        COMPARE(tr3->bodyData, string(BODY1));
+    }
+
+    // test cancel
+    {
+        cout <<  "cancel middle request" << endl;
+        testServer.resetConnectCount();
+        cl.clearAllConnections();
+
+        cl.setProxy("", 80);
+        TestRequest* tr = new TestRequest("http://localhost:2000/test1");
+        HTTP::Request_ptr own(tr);
+        cl.makeRequest(tr);
+
+        TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
+        HTTP::Request_ptr own2(tr2);
+        cl.makeRequest(tr2);
+
+        TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
+        HTTP::Request_ptr own3(tr3);
+        cl.makeRequest(tr3);
+
+        cl.cancelRequest(tr2, "middle request");
+
+        waitForComplete(&cl, tr3);
+
+        COMPARE(tr->responseCode(), 200);
+        COMPARE(tr->responseLength(), strlen(BODY1));
+        COMPARE(tr->responseBytesReceived(), strlen(BODY1));
+        COMPARE(tr->bodyData, string(BODY1));
+
+        COMPARE(tr2->responseCode(), -1);
+
+        COMPARE(tr3->responseLength(), strlen(BODY1));
+        COMPARE(tr3->responseBytesReceived(), strlen(BODY1));
+        COMPARE(tr3->bodyData, string(BODY1));
+    }
+
     {
         cout << "get-during-response-send" << endl;
         cl.clearAllConnections();
index a240be17b1d7a62a239bfcbbdcf4fd91a5b98c65..ae6ccb5bd60e2d670a64306d497a0e1027c95f82 100644 (file)
@@ -409,7 +409,7 @@ size_t Install::downloadedBytes() const
 void Install::cancelDownload()
 {
     if (m_download.valid()) {
-        m_download->abort("User cancelled download");
+        m_package->catalog()->root()->cancelHTTPRequest(m_download, "User cancelled download");
     }
 
     if (m_revision == 0) {
index c158dbdd3d6188ffae7ad7bfc1abaee633c55732..785076530feed194e611c75e4edbe830d684decc 100644 (file)
@@ -226,6 +226,20 @@ void Root::makeHTTPRequest(HTTP::Request *req)
     d->httpPendingRequests.push_back(req);
 }
 
+void Root::cancelHTTPRequest(HTTP::Request *req, const std::string &reason)
+{
+    if (d->http) {
+        d->http->cancelRequest(req, reason);
+    }
+
+    std::deque<HTTP::Request_ptr>::iterator it = std::find(d->httpPendingRequests.begin(),
+                                                           d->httpPendingRequests.end(),
+                                                           req);
+    if (it != d->httpPendingRequests.end()) {
+        d->httpPendingRequests.erase(it);
+    }
+}
+
 Root::Root(const SGPath& aPath, const std::string& aVersion) :
     d(new RootPrivate)
 {
index eadfac8847dc0f58b874c8deeb358f7186b83cc4..69ac4804cbfa57d91db16051d2e51b850abfd45c 100644 (file)
@@ -81,6 +81,11 @@ public:
      * set yet.
      */
     void makeHTTPRequest(HTTP::Request* req);
+
+    /**
+     * Cancel an HTTP request.
+     */
+    void cancelHTTPRequest(HTTP::Request* req, const std::string& reason);
     
     /**
      * The catalog XML/property version in use. This is used to make incomaptible