]> git.mxchange.org Git - simgear.git/blobdiff - simgear/io/HTTPClient.cxx
Fix windows build
[simgear.git] / simgear / io / HTTPClient.cxx
index 3ea5f02a127b8890c0e99ad748e7ec3d1817271a..fb1b812faaeb4d91dcbb5c9a4e0b4f1cfa0c33e5 100644 (file)
@@ -25,6 +25,9 @@ using std::string;
 using std::stringstream;
 using std::vector;
 
+//#include <iostream>
+//using namespace std;
+
 namespace simgear
 {
 
@@ -38,30 +41,24 @@ class Connection : public NetChat
 public:
     Connection(Client* pr) :
         client(pr),
-        state(STATE_IDLE)
+        state(STATE_CLOSED),
+        port(DEFAULT_HTTP_PORT)
     {
-        setTerminator("\r\n");
+        
     }
     
-    bool connectToHost(const string& host, short port)
+    void setServer(const string& h, short p)
     {
-        if (!open()) {
-            SG_LOG(SG_ALL, SG_WARN, "HTTP::Connection: connectToHost: open() failed");
-            return false;
-        }
-        
-        if (connect(host.c_str(), port) != 0) {
-            return false;
-        }
-        
-        return true;
+        host = h;
+        port = p;
     }
     
     // socket-level errors
     virtual void handleError(int error)
-    {
+    {        
         NetChat::handleError(error);
         if (activeRequest) {
+            SG_LOG(SG_IO, SG_INFO, "HTTP socket error");
             activeRequest->setFailure(error, "socket error");
             activeRequest = NULL;
         }
@@ -69,6 +66,20 @@ public:
         state = STATE_SOCKET_ERROR;
     }
     
+    virtual void handleClose()
+    {
+        NetChat::handleClose();
+        
+        if ((state == STATE_GETTING_BODY) && activeRequest) {
+        // force state here, so responseComplete can avoid closing the 
+        // socket again
+            state =  STATE_CLOSED;
+            responseComplete();
+        } else {
+            state = STATE_CLOSED;
+        }
+    }
+    
     void queueRequest(const Request_ptr& r)
     {
         if (!activeRequest) {
@@ -80,10 +91,19 @@ public:
     
     void startRequest(const Request_ptr& r)
     {
+        if (state == STATE_CLOSED) {
+            if (!connectToHost()) {
+                return;
+            }
+            
+            state = STATE_IDLE;
+        }
+                
         activeRequest = r;
-        state = STATE_IDLE;
-        bodyTransferSize = 0;
+        state = STATE_SENT_REQUEST;
+        bodyTransferSize = -1;
         chunkedTransfer = false;
+        setTerminator("\r\n");
         
         stringstream headerData;
         string path = r->path();
@@ -106,7 +126,12 @@ public:
 
     // TODO - add request body support for PUT, etc operations
 
-        push(headerData.str().c_str());
+        bool ok = push(headerData.str().c_str());
+        if (!ok) {
+            SG_LOG(SG_IO, SG_WARN, "HTTP writing to socket failed");
+            state = STATE_SOCKET_ERROR;
+            return;
+        }        
     }
     
     virtual void collectIncomingData(const char* s, int n)
@@ -121,7 +146,7 @@ public:
     virtual void foundTerminator(void)
     {
         switch (state) {
-        case STATE_IDLE:
+        case STATE_SENT_REQUEST:
             activeRequest->responseStart(buffer);
             state = STATE_GETTING_HEADERS;
             buffer.clear();
@@ -145,6 +170,7 @@ public:
             state = STATE_GETTING_CHUNKED;
             break;
             
+
         case STATE_GETTING_TRAILER:
             processTrailer();
             buffer.clear();
@@ -168,7 +194,37 @@ public:
     {
         return (state == STATE_SOCKET_ERROR);
     }
+    
+    bool shouldStartNext() const
+    {
+        return !activeRequest && !queuedRequests.empty() && 
+            ((state == STATE_CLOSED) || (state == STATE_IDLE));
+    }
+    
+    void startNext()
+    {
+        Request_ptr next = queuedRequests.front();
+        queuedRequests.pop_front();
+        startRequest(next);
+    }
 private:
+    bool connectToHost()
+    {
+        SG_LOG(SG_IO, SG_INFO, "HTTP connecting to " << host << ":" << port);
+        
+        if (!open()) {
+            SG_LOG(SG_ALL, SG_WARN, "HTTP::Connection: connectToHost: open() failed");
+            return false;
+        }
+        
+        if (connect(host.c_str(), port) != 0) {
+            return false;
+        }
+        
+        return true;
+    }
+    
+    
     void processHeader()
     {
         string h = strutils::simplify(buffer);
@@ -177,12 +233,11 @@ private:
             
             if (chunkedTransfer) {
                 state = STATE_GETTING_CHUNKED;
-            } else if (bodyTransferSize > 0) {
-                state = STATE_GETTING_BODY;
-                setByteCount(bodyTransferSize);
             } else {
-                responseComplete();
+                setByteCount(bodyTransferSize); // may be -1, that's fine
+                state = STATE_GETTING_BODY;
             }
+            
             return;
         }
         
@@ -272,15 +327,27 @@ private:
     {
         activeRequest->responseComplete();
         client->requestFinished(this);
+        //cout << "response complete: " << activeRequest->url() << endl;
+        
+        bool doClose = activeRequest->closeAfterComplete();
         activeRequest = NULL;
+        if (state == STATE_GETTING_BODY) {
+            state = STATE_IDLE;
+            if (doClose) {
+            // this will bring us into handleClose() above, which updates
+            // state to STATE_CLOSED
+                close();
+            }
+        }
         
-        state = STATE_IDLE;
         setTerminator("\r\n");
         
-        if (!queuedRequests.empty()) {
-            Request_ptr next = queuedRequests.front();
-            queuedRequests.pop_front();
-            startRequest(next);
+    // if we have more requests, and we're idle, can start the next
+    // request immediately. Note we cannot do this if we're in STATE_CLOSED,
+    // since NetChannel::close cleans up state after calling handleClose;
+    // instead we pick up waiting requests in update()
+        if (!queuedRequests.empty() && (state == STATE_IDLE)) {
+            startNext();
         } else {
             idleTime.stamp();
         }
@@ -288,17 +355,21 @@ private:
     
     enum ConnectionState {
         STATE_IDLE = 0,
+        STATE_SENT_REQUEST,
         STATE_GETTING_HEADERS,
         STATE_GETTING_BODY,
         STATE_GETTING_CHUNKED,
         STATE_GETTING_CHUNKED_BYTES,
         STATE_GETTING_TRAILER,
-        STATE_SOCKET_ERROR
+        STATE_SOCKET_ERROR,
+        STATE_CLOSED             ///< connection should be closed now
     };
     
     Client* client;
     Request_ptr activeRequest;
     ConnectionState state;
+    string host;
+    short port;
     std::string buffer;
     int bodyTransferSize;
     SGTimeStamp idleTime;
@@ -315,15 +386,21 @@ Client::Client()
 void Client::update()
 {
     NetChannel::poll();
-    
+        
     ConnectionDict::iterator it = _connections.begin();
     for (; it != _connections.end(); ) {
         if (it->second->hasIdleTimeout() || it->second->hasError()) {
         // connection has been idle for a while, clean it up
+        // (or has an error condition, again clean it up)
+            SG_LOG(SG_IO, SG_INFO, "cleaning up " << it->second);
             ConnectionDict::iterator del = it++;
             delete del->second;
             _connections.erase(del);
         } else {
+            if (it->second->shouldStartNext()) {
+                it->second->startNext();
+            }
+            
             ++it;
         }
     } // of connecion iteration
@@ -344,18 +421,7 @@ void Client::makeRequest(const Request_ptr& r)
     
     if (_connections.find(connectionId) == _connections.end()) {
         Connection* con = new Connection(this);
-        bool ok = con->connectToHost(host, port);
-        if (!ok) {
-        // since NetChannel connect is non-blocking, this failure
-        // path is unlikely, but still checked for.
-            SG_LOG(SG_IO, SG_WARN, "unable to connect to host:" 
-                << host << " (port:" << port << ")");
-            delete con;
-            
-            r->setFailure(-1, "unable to connect to host");
-            return;
-        }
-        
+        con->setServer(host, port);
         _connections[connectionId] = con;
     }