X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=simgear%2Fio%2FHTTPClient.cxx;h=fb1b812faaeb4d91dcbb5c9a4e0b4f1cfa0c33e5;hb=09b0dd2b2d7d934c1d4059cb2cbd3b4fcbb7872f;hp=3ea5f02a127b8890c0e99ad748e7ec3d1817271a;hpb=115531e944f51c3295f69f3ed361bf421515cc58;p=simgear.git diff --git a/simgear/io/HTTPClient.cxx b/simgear/io/HTTPClient.cxx index 3ea5f02a..fb1b812f 100644 --- a/simgear/io/HTTPClient.cxx +++ b/simgear/io/HTTPClient.cxx @@ -25,6 +25,9 @@ using std::string; using std::stringstream; using std::vector; +//#include +//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; }