Tweaks to HTTP code, in preparation for using it for metar - especially, test code for proxies

This commit is contained in:
James Turner
2011-07-30 10:43:49 +01:00
parent 38ac7b41ad
commit 19119cc4ae
5 changed files with 144 additions and 32 deletions

View File

@@ -11,6 +11,7 @@
#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"
@@ -30,6 +31,7 @@ namespace simgear
namespace HTTP
{
extern const int DEFAULT_HTTP_PORT = 80;
class Connection : public NetChat
{
@@ -41,18 +43,10 @@ public:
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)
@@ -73,13 +67,11 @@ public:
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";
@@ -128,12 +120,22 @@ public:
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()
{
@@ -193,6 +195,7 @@ private:
ConnectionState state;
std::string buffer;
int bodyTransferSize;
SGTimeStamp idleTime;
std::list<Request_ptr> queuedRequests;
};
@@ -202,16 +205,31 @@ Client::Client()
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;
}

View File

@@ -18,6 +18,8 @@ class Client
public:
Client();
void update();
void makeRequest(const Request_ptr& r);
void setUserAgent(const std::string& ua);
@@ -41,7 +43,8 @@ private:
std::string _proxyAuth;
// connections by host
std::map<std::string, Connection*> _connections;
typedef std::map<std::string, Connection*> ConnectionDict;
ConnectionDict _connections;
};
} // of namespace HTTP

View File

@@ -19,6 +19,8 @@ namespace simgear
namespace HTTP
{
extern const int DEFAULT_HTTP_PORT;
Request::Request(const string& url, const string method) :
_method(method),
_url(url)
@@ -31,6 +33,11 @@ Request::~Request()
}
void Request::setUrl(const string& url)
{
_url = url;
}
string_list Request::requestHeaders() const
{
string_list r;
@@ -103,6 +110,28 @@ string Request::path() const
}
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("://");
@@ -118,14 +147,14 @@ string Request::host() const
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

View File

@@ -18,6 +18,8 @@ class Request : public SGReferenced
public:
virtual ~Request();
virtual void setUrl(const std::string& url);
virtual std::string method() const
{ return _method; }
virtual std::string url() const
@@ -25,7 +27,9 @@ public:
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;
@@ -36,7 +40,7 @@ public:
virtual std::string resposeReason() const
{ return _responseReason; }
virtual int contentLength() const;
virtual unsigned int contentLength() const;
protected:
friend class Connection;

View File

@@ -23,7 +23,7 @@ using namespace simgear;
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) \
@@ -142,18 +142,44 @@ public:
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;
@@ -227,11 +253,20 @@ int main(int argc, char* argv[])
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");
@@ -245,7 +280,7 @@ int main(int argc, char* argv[])
}
// larger get request
for (int i=0; i<body2Size; ++i) {
for (unsigned int i=0; i<body2Size; ++i) {
body2[i] = (i << 4) | (i >> 2);
}
@@ -269,6 +304,29 @@ int main(int argc, char* argv[])
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;
}