class Connection : public NetChat
{
public:
- Connection(Client* pr) :
+ Connection(Client* pr, const std::string& conId) :
client(pr),
state(STATE_CLOSED),
- port(DEFAULT_HTTP_PORT)
+ port(DEFAULT_HTTP_PORT),
+ connectionId(conId)
{
}
virtual void handleError(int error)
{
const char* errStr = strerror(error);
+ SG_LOG(SG_IO, SG_WARN, "HTTP Connection handleError:" << error << " ("
+ << errStr << ")");
+
+ debugDumpRequests();
+
if (!activeRequest)
{
// connection level failure, eg name lookup or routing
state = STATE_SOCKET_ERROR;
}
+ void handleTimeout()
+ {
+ handleError(ETIMEDOUT);
+ }
+
virtual void handleClose()
{
NetChat::handleClose();
state = STATE_CLOSED;
responseComplete();
} else {
+ if (state == STATE_WAITING_FOR_RESPONSE) {
+ assert(!sentRequests.empty());
+ sentRequests.front()->setFailure(500, "server closed connection unexpectedly");
+ // no active request, but don't restore the front sent one
+ sentRequests.erase(sentRequests.begin());
+ }
+
if (activeRequest) {
activeRequest->setFailure(500, "server closed connection");
// remove the failed request from sentRequests, so it does
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;
- _contentDecoder.reset();
- }
-
- state = STATE_SOCKET_ERROR;
- }
-
void queueRequest(const Request_ptr& r)
{
queuedRequests.push_back(r);
}
}
- // SG_LOG(SG_IO, SG_INFO, "did start request:" << r->url() <<
- // "\n\t @ " << reinterpret_cast<void*>(r.ptr()) <<
- // "\n\t on connection " << this);
+ SG_LOG(SG_IO, SG_DEBUG, "con:" << connectionId << " did start request:" << r->url());
// successfully sent, remove from queue, and maybe send the next
queuedRequests.pop_front();
sentRequests.push_back(r);
bool hasIdleTimeout() const
{
- if (state != STATE_IDLE) {
+ if ((state != STATE_IDLE) && (state != STATE_CLOSED)) {
return false;
}
assert(sentRequests.empty());
- return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
+ bool isTimedOut = (idleTime.elapsedMSec() > (1000 * 10)); // 10 seconds
+ return isTimedOut;
}
bool hasErrorTimeout() const
{
- if (state == STATE_IDLE) {
+ if ((state == STATE_IDLE) || (state == STATE_CLOSED)) {
return false;
}
- return idleTime.elapsedMSec() > (1000 * 30); // 30 seconds
+ bool isTimedOut = (idleTime.elapsedMSec() > (1000 * 30)); // 30 seconds
+ return isTimedOut;
}
bool hasError() const
{
return !queuedRequests.empty() || !sentRequests.empty();
}
+
+ void debugDumpRequests() const
+ {
+ SG_LOG(SG_IO, SG_DEBUG, "requests for:" << host << ":" << port << " (conId=" << connectionId
+ << "; state=" << state << ")");
+ if (activeRequest) {
+ SG_LOG(SG_IO, SG_DEBUG, "\tactive:" << activeRequest->url());
+ } else {
+ SG_LOG(SG_IO, SG_DEBUG, "\tNo active request");
+ }
+
+ BOOST_FOREACH(Request_ptr req, sentRequests) {
+ SG_LOG(SG_IO, SG_DEBUG, "\tsent:" << req->url());
+ }
+
+ BOOST_FOREACH(Request_ptr req, queuedRequests) {
+ SG_LOG(SG_IO, SG_DEBUG, "\tqueued:" << req->url());
+ }
+ }
private:
bool connectToHost()
{
SG_LOG(SG_IO, SG_DEBUG, "HTTP connecting to " << host << ":" << port);
if (!open()) {
- SG_LOG(SG_ALL, SG_WARN, "HTTP::Connection: connectToHost: open() failed");
+ SG_LOG(SG_IO, SG_WARN, "HTTP::Connection: connectToHost: open() failed");
return false;
}
if (connect(host.c_str(), port) != 0) {
+ SG_LOG(SG_IO, SG_WARN, "HTTP::Connection: connectToHost: connect() failed");
return false;
}
// notify request after we change state, so this connection is idle
// if completion triggers other requests (which is likely)
- // SG_LOG(SG_IO, SG_INFO, "*** responseComplete:" << activeRequest->url());
completedRequest->responseComplete();
client->requestFinished(this);
RequestList sentRequests;
ContentDecoder _contentDecoder;
+ std::string connectionId;
};
#endif // of !ENABLE_CURL
// allocate a new connection object
if (!con) {
- con = new Connection(this);
+ con = new Connection(this, connectionId);
con->setServer(host, port);
d->poller.addChannel(con);
d->connections.insert(d->connections.end(),
return byteSize;
}
+void Client::debugDumpRequests()
+{
+#if defined(ENABLE_CURL)
+
+#else
+ SG_LOG(SG_IO, SG_INFO, "== HTTP connection dump");
+ ConnectionDict::iterator it = d->connections.begin();
+ for (; it != d->connections.end(); ++it) {
+ it->second->debugDumpRequests();
+ }
+ SG_LOG(SG_IO, SG_INFO, "==");
+#endif
+}
+
} // of namespace HTTP
} // of namespace simgear
d << contentStr;
push(d.str().c_str());
closeAfterSending();
+ } else if (path == "/test_abrupt_close") {
+ // simulate server doing socket close before sending any
+ // response - this used to cause a TerraSync failure since we
+ // would get stuck restarting the request
+ closeAfterSending();
+
} else if (path == "/test_args") {
if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
sendErrorResponse(400, true, "bad arguments");
}
cout << "done3" << endl;
// test connectToHost failure
-/*
+
{
TestRequest* tr = new TestRequest("http://not.found/something");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
- waitForFailed(tr);
- COMPARE(tr->responseCode(), -1);
+ waitForFailed(&cl, tr);
+ COMPARE(tr->responseCode(), ENOENT);
+ }
+
+
+ // test server-side abrupt close
+ {
+ TestRequest* tr = new TestRequest("http://localhost:2000/test_abrupt_close");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+ waitForFailed(&cl, tr);
+ COMPARE(tr->responseCode(), 500);
}
- */
+
// test proxy
{
cl.setProxy("localhost", 2000);