Tweaks to HTTP code, in preparation for using it for metar - especially, test code for proxies
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user