From 19119cc4ae078dfb648f624cb5cff18ad86186a5 Mon Sep 17 00:00:00 2001 From: James Turner Date: Sat, 30 Jul 2011 10:43:49 +0100 Subject: [PATCH] Tweaks to HTTP code, in preparation for using it for metar - especially, test code for proxies --- simgear/io/HTTPClient.cxx | 50 ++++++++++++++++-------- simgear/io/HTTPClient.hxx | 5 ++- simgear/io/HTTPRequest.cxx | 33 +++++++++++++++- simgear/io/HTTPRequest.hxx | 8 +++- simgear/io/test_HTTP.cxx | 80 ++++++++++++++++++++++++++++++++------ 5 files changed, 144 insertions(+), 32 deletions(-) diff --git a/simgear/io/HTTPClient.cxx b/simgear/io/HTTPClient.cxx index 4c06fba3..8c023ab3 100644 --- a/simgear/io/HTTPClient.cxx +++ b/simgear/io/HTTPClient.cxx @@ -11,6 +11,7 @@ #include #include #include +#include #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 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; } diff --git a/simgear/io/HTTPClient.hxx b/simgear/io/HTTPClient.hxx index 0e04cdc9..7e7a69b7 100644 --- a/simgear/io/HTTPClient.hxx +++ b/simgear/io/HTTPClient.hxx @@ -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 _connections; + typedef std::map ConnectionDict; + ConnectionDict _connections; }; } // of namespace HTTP diff --git a/simgear/io/HTTPRequest.cxx b/simgear/io/HTTPRequest.cxx index e38f7aae..50d059e6 100644 --- a/simgear/io/HTTPRequest.cxx +++ b/simgear/io/HTTPRequest.cxx @@ -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 diff --git a/simgear/io/HTTPRequest.hxx b/simgear/io/HTTPRequest.hxx index 4afb1c75..7cf498cd 100644 --- a/simgear/io/HTTPRequest.hxx +++ b/simgear/io/HTTPRequest.hxx @@ -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; diff --git a/simgear/io/test_HTTP.cxx b/simgear/io/test_HTTP.cxx index 3d536498..1dce8d29 100644 --- a/simgear/io/test_HTTP.cxx +++ b/simgear/io/test_HTTP.cxx @@ -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> 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; } -- 2.39.5