#include <simgear/misc/strutils.hxx>
#include <simgear/compiler.h>
#include <simgear/debug/logstream.hxx>
+#include <simgear/timing/timestamp.hxx>
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
#include "version.h"
namespace HTTP
{
+extern const int DEFAULT_HTTP_PORT = 80;
class Connection : public NetChat
{
setTerminator("\r\n");
}
- void connectToHost(const string& host)
+ void connectToHost(const string& host, short port)
{
open();
-
- int colonPos = host.find(':');
- if (colonPos > 0) {
- string h = host.substr(0, colonPos);
- int port = strutils::to_int(host.substr(colonPos + 1));
- connect(h.c_str(), port);
- } else {
- connect(host.c_str(), 80 /* default port */);
- }
+ connect(host.c_str(), port);
}
void queueRequest(const Request_ptr& r)
stringstream headerData;
string path = r->path();
if (!client->proxyHost().empty()) {
- path = "http://" + r->host() + path;
+ path = "http://" + r->hostAndPort() + path;
}
- int requestTime = 0;
headerData << r->method() << " " << path << " HTTP/1.1 " << client->userAgent() << "\r\n";
- headerData << "Host: " << r->host() << "\r\n";
- headerData << "X-Time: " << requestTime << "\r\n";
+ headerData << "Host: " << r->hostAndPort() << "\r\n";
if (!client->proxyAuth().empty()) {
headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
Request_ptr next = queuedRequests.front();
queuedRequests.pop_front();
startRequest(next);
+ } else {
+ idleTime.stamp();
}
break;
}
}
+ bool hasIdleTimeout() const
+ {
+ if (state != STATE_IDLE) {
+ return false;
+ }
+
+ return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
+ }
private:
void processHeader()
{
ConnectionState state;
std::string buffer;
int bodyTransferSize;
+ SGTimeStamp idleTime;
std::list<Request_ptr> queuedRequests;
};
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
}
+void Client::update()
+{
+ ConnectionDict::iterator it = _connections.begin();
+ for (; it != _connections.end(); ) {
+ if (it->second->hasIdleTimeout()) {
+ // connection has been idle for a while, clean it up
+ ConnectionDict::iterator del = it++;
+ delete del->second;
+ _connections.erase(del);
+ } else {
+ ++it;
+ }
+ } // of connecion iteration
+}
+
void Client::makeRequest(const Request_ptr& r)
{
- string host = r->host();
+ string host = r->hostAndPort();
if (!_proxy.empty()) {
host = _proxy;
}
if (_connections.find(host) == _connections.end()) {
Connection* con = new Connection(this);
- con->connectToHost(host);
+ con->connectToHost(r->host(), r->port());
_connections[host] = con;
}
public:
Client();
+ void update();
+
void makeRequest(const Request_ptr& r);
void setUserAgent(const std::string& ua);
std::string _proxyAuth;
// connections by host
- std::map<std::string, Connection*> _connections;
+ typedef std::map<std::string, Connection*> ConnectionDict;
+ ConnectionDict _connections;
};
} // of namespace HTTP
namespace HTTP
{
+extern const int DEFAULT_HTTP_PORT;
+
Request::Request(const string& url, const string method) :
_method(method),
_url(url)
}
+void Request::setUrl(const string& url)
+{
+ _url = url;
+}
+
string_list Request::requestHeaders() const
{
string_list r;
}
string Request::host() const
+{
+ string hp(hostAndPort());
+ int colonPos = hp.find(':');
+ if (colonPos >= 0) {
+ return hp.substr(0, colonPos); // trim off the colon and port
+ } else {
+ return hp; // no port specifier
+ }
+}
+
+unsigned short Request::port() const
+{
+ string hp(hostAndPort());
+ int colonPos = hp.find(':');
+ if (colonPos >= 0) {
+ return (unsigned short) strutils::to_int(hp.substr(colonPos + 1));
+ } else {
+ return DEFAULT_HTTP_PORT;
+ }
+}
+
+string Request::hostAndPort() const
{
string u(url());
int schemeEnd = u.find("://");
return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
}
-int Request::contentLength() const
+unsigned int Request::contentLength() const
{
HeaderDict::const_iterator it = _responseHeaders.find("content-length");
if (it == _responseHeaders.end()) {
return 0;
}
- return strutils::to_int(it->second);
+ return (unsigned int) strutils::to_int(it->second);
}
} // of namespace HTTP
public:
virtual ~Request();
+ virtual void setUrl(const std::string& url);
+
virtual std::string method() const
{ return _method; }
virtual std::string url() const
virtual std::string scheme() const;
virtual std::string path() const;
- virtual std::string host() const; // host, including port
+ virtual std::string host() const;
+ virtual std::string hostAndPort() const;
+ virtual unsigned short port() const;
virtual string_list requestHeaders() const;
virtual std::string header(const std::string& name) const;
virtual std::string resposeReason() const
{ return _responseReason; }
- virtual int contentLength() const;
+ virtual unsigned int contentLength() const;
protected:
friend class Connection;
const char* BODY1 = "The quick brown fox jumps over a lazy dog.";
-const int body2Size = 8 * 1024;
+const unsigned int body2Size = 8 * 1024;
char body2[body2Size];
#define COMPARE(a, b) \
d << contentStr;
push(d.str().c_str());
} else if (path == "/test2") {
- stringstream d;
- d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
- d << "Content-Length:" << body2Size << "\r\n";
- d << "\r\n"; // final CRLF to terminate the headers
- push(d.str().c_str());
- bufferSend(body2, body2Size);
- cout << "sent body2" << endl;
+ sendBody2();
+ } else if (path == "http://www.google.com/test2") {
+ // proxy test
+ if (requestHeaders["host"] != "www.google.com") {
+ sendErrorResponse(400);
+ }
+
+ if (requestHeaders["proxy-authorization"] != string()) {
+ sendErrorResponse(401); // shouldn't supply auth
+ }
+
+ sendBody2();
+ } else if (path == "http://www.google.com/test3") {
+ // proxy test
+ if (requestHeaders["host"] != "www.google.com") {
+ sendErrorResponse(400);
+ }
+
+ if (requestHeaders["proxy-authorization"] != "ABCDEF") {
+ sendErrorResponse(401); // forbidden
+ }
+
+ sendBody2();
} else {
sendErrorResponse(404);
}
}
+ void sendBody2()
+ {
+ stringstream d;
+ d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
+ d << "Content-Length:" << body2Size << "\r\n";
+ d << "\r\n"; // final CRLF to terminate the headers
+ push(d.str().c_str());
+ bufferSend(body2, body2Size);
+ }
+
void sendErrorResponse(int code)
{
cerr << "sending error " << code << " for " << path << endl;
HTTP::Client cl;
// test URL parsing
- TestRequest* tr1 = new TestRequest("http://localhost:2000/test1?foo=bar");
+ TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
COMPARE(tr1->scheme(), "http");
- COMPARE(tr1->host(), "localhost:2000");
+ COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
+ COMPARE(tr1->host(), "localhost.woo.zar");
+ COMPARE(tr1->port(), 2000);
COMPARE(tr1->path(), "/test1");
+ TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
+ COMPARE(tr2->scheme(), "http");
+ COMPARE(tr2->hostAndPort(), "192.168.1.1");
+ COMPARE(tr2->host(), "192.168.1.1");
+ COMPARE(tr2->port(), 80);
+ COMPARE(tr2->path(), "/test1/dir/thing/file.png");
+
// basic get request
{
TestRequest* tr = new TestRequest("http://localhost:2000/test1");
}
// larger get request
- for (int i=0; i<body2Size; ++i) {
+ for (unsigned int i=0; i<body2Size; ++i) {
body2[i] = (i << 4) | (i >> 2);
}
COMPARE(tr->contentLength(), 0);
}
+// test proxy
+ {
+ cl.setProxy("localhost:2000");
+ TestRequest* tr = new TestRequest("http://www.google.com/test2");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+ waitForComplete(tr);
+ COMPARE(tr->responseCode(), 200);
+ COMPARE(tr->contentLength(), body2Size);
+ COMPARE(tr->bodyData, string(body2, body2Size));
+ }
+
+ {
+ cl.setProxy("localhost:2000", "ABCDEF");
+ TestRequest* tr = new TestRequest("http://www.google.com/test3");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+ waitForComplete(tr);
+ COMPARE(tr->responseCode(), 200);
+ COMPARE(tr->contentLength(), body2Size);
+ COMPARE(tr->bodyData, string(body2, body2Size));
+ }
+
cout << "all tests passed ok" << endl;
return EXIT_SUCCESS;
}