HTTP bugfix + enhancement.

Track 'waiting for response' state explicitly, so we don't
falsely report IDLE when waiting for a response.

Expose bytes-per-second download rate as metric.
This commit is contained in:
James Turner 2013-09-27 18:20:03 +01:00
parent 25cae61211
commit 17418039e1
3 changed files with 83 additions and 20 deletions

View File

@ -97,6 +97,10 @@ public:
// connections by host (potentially more than one) // connections by host (potentially more than one)
ConnectionDict connections; ConnectionDict connections;
SGTimeStamp timeTransferSample;
unsigned int bytesTransferred;
unsigned int lastTransferRate;
}; };
class Connection : public NetChat class Connection : public NetChat
@ -198,6 +202,18 @@ public:
sentRequests.clear(); sentRequests.clear();
} }
void handleTimeout()
{
NetChat::handleError(ETIMEDOUT);
if (activeRequest) {
SG_LOG(SG_IO, SG_DEBUG, "HTTP socket timeout");
activeRequest->setFailure(ETIMEDOUT, "socket timeout");
activeRequest = NULL;
}
state = STATE_SOCKET_ERROR;
}
void queueRequest(const Request_ptr& r) void queueRequest(const Request_ptr& r)
{ {
queuedRequests.push_back(r); queuedRequests.push_back(r);
@ -207,8 +223,10 @@ public:
void beginResponse() void beginResponse()
{ {
assert(!sentRequests.empty()); assert(!sentRequests.empty());
assert(state == STATE_WAITING_FOR_RESPONSE);
activeRequest = sentRequests.front(); activeRequest = sentRequests.front();
activeRequest->responseStart(buffer); activeRequest->responseStart(buffer);
state = STATE_GETTING_HEADERS; state = STATE_GETTING_HEADERS;
buffer.clear(); buffer.clear();
@ -313,12 +331,13 @@ public:
} }
} }
SG_LOG(SG_IO, SG_DEBUG, "did start request:" << r->url() << // SG_LOG(SG_IO, SG_INFO, "did start request:" << r->url() <<
"\n\t @ " << reinterpret_cast<void*>(r.ptr()) << // "\n\t @ " << reinterpret_cast<void*>(r.ptr()) <<
"\n\t on connection " << this); // "\n\t on connection " << this);
// successfully sent, remove from queue, and maybe send the next // successfully sent, remove from queue, and maybe send the next
queuedRequests.pop_front(); queuedRequests.pop_front();
sentRequests.push_back(r); sentRequests.push_back(r);
state = STATE_WAITING_FOR_RESPONSE;
// pipelining, let's maybe send the next request right away // pipelining, let's maybe send the next request right away
tryStartNextRequest(); tryStartNextRequest();
@ -327,6 +346,8 @@ public:
virtual void collectIncomingData(const char* s, int n) virtual void collectIncomingData(const char* s, int n)
{ {
idleTime.stamp(); idleTime.stamp();
client->receivedBytes(static_cast<unsigned int>(n));
if ((state == STATE_GETTING_BODY) || (state == STATE_GETTING_CHUNKED_BYTES)) { if ((state == STATE_GETTING_BODY) || (state == STATE_GETTING_CHUNKED_BYTES)) {
if (contentGZip || contentDeflate) { if (contentGZip || contentDeflate) {
expandCompressedData(s, n); expandCompressedData(s, n);
@ -458,7 +479,7 @@ public:
{ {
idleTime.stamp(); idleTime.stamp();
switch (state) { switch (state) {
case STATE_IDLE: case STATE_WAITING_FOR_RESPONSE:
beginResponse(); beginResponse();
break; break;
@ -487,6 +508,9 @@ public:
buffer.clear(); buffer.clear();
break; break;
case STATE_IDLE:
SG_LOG(SG_IO, SG_WARN, "HTTP got data in IDLE state, bad server?");
default: default:
break; break;
} }
@ -498,6 +522,7 @@ public:
return false; return false;
} }
assert(sentRequests.empty());
return idleTime.elapsedMSec() > 1000 * 10; // ten seconds return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
} }
@ -696,13 +721,14 @@ private:
} }
if (state != STATE_CLOSED) { if (state != STATE_CLOSED) {
state = STATE_IDLE; state = sentRequests.empty() ? STATE_IDLE : STATE_WAITING_FOR_RESPONSE;
} }
setTerminator("\r\n"); setTerminator("\r\n");
} }
enum ConnectionState { enum ConnectionState {
STATE_IDLE = 0, STATE_IDLE = 0,
STATE_WAITING_FOR_RESPONSE,
STATE_GETTING_HEADERS, STATE_GETTING_HEADERS,
STATE_GETTING_BODY, STATE_GETTING_BODY,
STATE_GETTING_CHUNKED, STATE_GETTING_CHUNKED,
@ -739,6 +765,10 @@ Client::Client() :
{ {
d->proxyPort = 0; d->proxyPort = 0;
d->maxConnections = 4; d->maxConnections = 4;
d->bytesTransferred = 0;
d->lastTransferRate = 0;
d->timeTransferSample.stamp();
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION)); setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
} }
@ -768,6 +798,11 @@ void Client::update(int waitTimeout)
con->hasErrorTimeout() || con->hasErrorTimeout() ||
(!con->isActive() && waitingRequests)) (!con->isActive() && waitingRequests))
{ {
if (con->hasErrorTimeout()) {
// tell the connection we're timing it out
con->handleTimeout();
}
// connection has been idle for a while, clean it up // connection has been idle for a while, clean it up
// (or if we have requests waiting for a different host, // (or if we have requests waiting for a different host,
// or an error condition // or an error condition
@ -901,6 +936,27 @@ bool Client::hasActiveRequests() const
return false; return false;
} }
void Client::receivedBytes(unsigned int count)
{
d->bytesTransferred += count;
}
unsigned int Client::transferRateBytesPerSec() const
{
unsigned int e = d->timeTransferSample.elapsedMSec();
if (e < 400) {
// if called too frequently, return cahced value, to smooth out
// < 1 sec changes in flow
return d->lastTransferRate;
}
unsigned int ratio = (d->bytesTransferred * 1000) / e;
d->timeTransferSample.stamp();
d->bytesTransferred = 0;
d->lastTransferRate = ratio;
return ratio;
}
} // of namespace HTTP } // of namespace HTTP
} // of namespace simgear } // of namespace simgear

View File

@ -67,9 +67,17 @@ public:
* least one request active or queued. * least one request active or queued.
*/ */
bool hasActiveRequests() const; bool hasActiveRequests() const;
/**
* crude tracking of bytes-per-second transferred over the socket.
* suitable for user feedback and rough profiling, nothing more.
*/
unsigned int transferRateBytesPerSec() const;
private: private:
void requestFinished(Connection* con); void requestFinished(Connection* con);
void receivedBytes(unsigned int count);
friend class Connection; friend class Connection;
friend class Request; friend class Request;

View File

@ -209,7 +209,6 @@ void Request::setFailure(int code, const std::string& reason)
void Request::failed() void Request::failed()
{ {
// no-op in base class
SG_LOG(SG_IO, SG_INFO, "request failed:" << url() << " : " SG_LOG(SG_IO, SG_INFO, "request failed:" << url() << " : "
<< responseCode() << "/" << responseReason()); << responseCode() << "/" << responseReason());
} }