using namespace simgear;
const char* BODY1 = "The quick brown fox jumps over a lazy dog.";
+const char* BODY3 = "Cras ut neque nulla. Duis ut velit neque, sit amet "
+"pharetra risus. In est ligula, lacinia vitae congue in, sollicitudin at "
+"libero. Mauris pharetra pretium elit, nec placerat dui semper et. Maecenas "
+"magna magna, placerat sed luctus ac, commodo et ligula. Mauris at purus et "
+"nisl molestie auctor placerat at quam. Donec sapien magna, venenatis sed "
+"iaculis id, fringilla vel arcu. Duis sed neque nisi. Cras a arcu sit amet "
+"risus ultrices varius. Integer sagittis euismod dui id varius. Cras vel "
+"justo gravida metus.";
const unsigned int body2Size = 8 * 1024;
char body2[body2Size];
#define COMPARE(a, b) \
if ((a) != (b)) { \
cerr << "failed:" << #a << " != " << #b << endl; \
- cerr << "\tgot:" << a << endl; \
+ cerr << "\tgot:'" << a << "'" << endl; \
exit(1); \
}
bool complete;
bool failed;
string bodyData;
-
- TestRequest(const std::string& url) :
- HTTP::Request(url),
+
+ TestRequest(const std::string& url, const std::string method = "GET") :
+ HTTP::Request(url, method),
complete(false)
{
-
+
}
std::map<string, string> headers;
protected:
- virtual void responseHeadersComplete()
- {
- }
- virtual void responseComplete()
+ virtual void onDone()
{
complete = true;
}
- virtual void failure()
+ virtual void onFail()
{
failed = true;
}
virtual void gotBodyData(const char* s, int n)
{
+ //std::cout << "got body data:'" << string(s, n) << "'" <<std::endl;
bodyData += string(s, n);
}
{
STATE_IDLE = 0,
STATE_HEADERS,
+ STATE_CLOSING,
STATE_REQUEST_BODY
};
{
state = STATE_IDLE;
setTerminator("\r\n");
+
}
virtual void collectIncomingData(const char* s, int n)
method = line[0];
path = line[1];
+
+ string::size_type queryPos = path.find('?');
+ if (queryPos != string::npos) {
+ parseArgs(path.substr(queryPos + 1));
+ path = path.substr(0, queryPos);
+ }
+
httpVersion = line[2];
requestHeaders.clear();
buffer.clear();
return;
}
- int colonPos = buffer.find(':');
- if (colonPos < 0) {
+ string::size_type colonPos = buffer.find(':');
+ if (colonPos == string::npos) {
cerr << "malformed HTTP response header:" << buffer << endl;
buffer.clear();
return;
}
string key = strutils::simplify(buffer.substr(0, colonPos));
- string lkey = boost::to_lower_copy(key);
string value = strutils::strip(buffer.substr(colonPos + 1));
- requestHeaders[lkey] = value;
+ requestHeaders[key] = value;
buffer.clear();
} else if (state == STATE_REQUEST_BODY) {
-
+ receivedBody();
+ setTerminator("\r\n");
+ } else if (state == STATE_CLOSING) {
+ // ignore!
}
}
+ void parseArgs(const string& argData)
+ {
+ string_list argv = strutils::split(argData, "&");
+ for (unsigned int a=0; a<argv.size(); ++a) {
+ string::size_type eqPos = argv[a].find('=');
+ if (eqPos == string::npos) {
+ cerr << "malformed HTTP argument:" << argv[a] << endl;
+ continue;
+ }
+
+ string key = argv[a].substr(0, eqPos);
+ string value = argv[a].substr(eqPos + 1);
+ args[key] = value;
+ }
+ }
+
void receivedRequestHeaders()
{
state = STATE_IDLE;
+
if (path == "/test1") {
+ string contentStr(BODY1);
+ stringstream d;
+ d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
+ d << "Content-Length:" << contentStr.size() << "\r\n";
+ d << "\r\n"; // final CRLF to terminate the headers
+ d << contentStr;
+ push(d.str().c_str());
+ } else if (path == "/testLorem") {
+ string contentStr(BODY3);
+ stringstream d;
+ d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
+ d << "Content-Length:" << contentStr.size() << "\r\n";
+ d << "\r\n"; // final CRLF to terminate the headers
+ d << contentStr;
+ push(d.str().c_str());
+ } else if (path == "/test_zero_length_content") {
+ string contentStr;
+ stringstream d;
+ d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
+ d << "Content-Length:" << contentStr.size() << "\r\n";
+ d << "\r\n"; // final CRLF to terminate the headers
+ d << contentStr;
+ push(d.str().c_str());
+ } else if (path == "/test_headers") {
+ COMPARE(requestHeaders["X-Foo"], string("Bar"));
+ COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
+
string contentStr(BODY1);
stringstream d;
d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
push(d.str().c_str());
} else if (path == "http://www.google.com/test2") {
// proxy test
- if (requestHeaders["host"] != "www.google.com") {
+ if (requestHeaders["Host"] != "www.google.com") {
sendErrorResponse(400, true, "bad destination");
}
- if (requestHeaders["proxy-authorization"] != string()) {
+ if (requestHeaders["Proxy-Authorization"] != string()) {
sendErrorResponse(401, false, "bad auth"); // shouldn't supply auth
}
sendBody2();
} else if (path == "http://www.google.com/test3") {
// proxy test
- if (requestHeaders["host"] != "www.google.com") {
+ if (requestHeaders["Host"] != "www.google.com") {
sendErrorResponse(400, true, "bad destination");
}
- if (requestHeaders["proxy-authorization"] != "ABCDEF") {
+ if (requestHeaders["Proxy-Authorization"] != "ABCDEF") {
sendErrorResponse(401, false, "bad auth"); // forbidden
}
sendBody2();
- } else if (path == "/test_1_0") {
+ } else if (strutils::starts_with(path, "/test_1_0")) {
string contentStr(BODY1);
+ if (strutils::ends_with(path, "/B")) {
+ contentStr = BODY3;
+ }
stringstream d;
d << "HTTP/1.0 " << 200 << " " << reasonForCode(200) << "\r\n";
d << "\r\n"; // final CRLF to terminate the headers
d << contentStr;
push(d.str().c_str());
- closeWhenDone();
+ closeAfterSending();
} else if (path == "/test_close") {
string contentStr(BODY1);
stringstream d;
d << "\r\n"; // final CRLF to terminate the headers
d << contentStr;
push(d.str().c_str());
- closeWhenDone();
+ closeAfterSending();
+ } else if (path == "/test_args") {
+ if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
+ sendErrorResponse(400, true, "bad arguments");
+ return;
+ }
+
+ string contentStr(BODY1);
+ stringstream d;
+ d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
+ d << "Content-Length:" << contentStr.size() << "\r\n";
+ d << "\r\n"; // final CRLF to terminate the headers
+ d << contentStr;
+ push(d.str().c_str());
+ } else if (path == "/test_post") {
+ if (requestHeaders["Content-Type"] != "application/x-www-form-urlencoded") {
+ cerr << "bad content type: '" << requestHeaders["Content-Type"] << "'" << endl;
+ sendErrorResponse(400, true, "bad content type");
+ return;
+ }
+
+ requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
+ setByteCount(requestContentLength);
+ state = STATE_REQUEST_BODY;
} else {
- sendErrorResponse(404, true, "");
+ sendErrorResponse(404, false, "");
+ }
+ }
+
+ void closeAfterSending()
+ {
+ state = STATE_CLOSING;
+ closeWhenDone();
+ }
+
+ void receivedBody()
+ {
+ state = STATE_IDLE;
+ if (method == "POST") {
+ parseArgs(buffer);
+ }
+
+ if (path == "/test_post") {
+ if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
+ sendErrorResponse(400, true, "bad arguments");
+ return;
+ }
+
+ stringstream d;
+ d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
+ d << "\r\n"; // final CRLF to terminate the headers
+ push(d.str().c_str());
+
+ cerr << "sent 204 response ok" << endl;
}
}
{
switch (code) {
case 200: return "OK";
+ case 204: return "no content";
case 404: return "not found";
default: return "unknown code";
}
string path;
string httpVersion;
std::map<string, string> requestHeaders;
+ std::map<string, string> args;
+ int requestContentLength;
};
class TestServer : public NetChannel
{
+ simgear::NetChannelPoller _poller;
public:
TestServer()
{
open();
bind(NULL, 2000); // localhost, any port
listen(5);
+
+ _poller.addChannel(this);
}
virtual ~TestServer()
//cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
TestServerChannel* chan = new TestServerChannel();
chan->setHandle(handle);
+
+ _poller.addChannel(chan);
+ }
+
+ void poll()
+ {
+ _poller.poll();
}
};
-void waitForComplete(TestRequest* tr)
+TestServer testServer;
+
+void waitForComplete(HTTP::Client* cl, TestRequest* tr)
{
SGTimeStamp start(SGTimeStamp::now());
while (start.elapsedMSec() < 1000) {
- NetChannel::poll(10);
+ cl->update();
+ testServer.poll();
+
if (tr->complete) {
return;
}
+ SGTimeStamp::sleepForMSec(1);
}
cerr << "timed out" << endl;
}
-void waitForFailed(TestRequest* tr)
+void waitForFailed(HTTP::Client* cl, TestRequest* tr)
{
SGTimeStamp start(SGTimeStamp::now());
while (start.elapsedMSec() < 1000) {
- NetChannel::poll(10);
+ cl->update();
+ testServer.poll();
+
if (tr->failed) {
return;
}
+ SGTimeStamp::sleepForMSec(1);
}
cerr << "timed out waiting for failure" << endl;
int main(int argc, char* argv[])
{
- TestServer s;
HTTP::Client cl;
-
+ // force all requests to use the same connection for this test
+ cl.setMaxConnections(1);
+
// test URL parsing
TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
COMPARE(tr1->scheme(), "http");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
- waitForComplete(tr);
+ waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseReason(), string("OK"));
COMPARE(tr->responseLength(), strlen(BODY1));
COMPARE(tr->responseBytesReceived(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
}
+
+ {
+ TestRequest* tr = new TestRequest("http://localhost:2000/testLorem");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+
+ waitForComplete(&cl, tr);
+ COMPARE(tr->responseCode(), 200);
+ COMPARE(tr->responseReason(), string("OK"));
+ COMPARE(tr->responseLength(), strlen(BODY3));
+ COMPARE(tr->responseBytesReceived(), strlen(BODY3));
+ COMPARE(tr->bodyData, string(BODY3));
+ }
+
+ {
+ TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+ waitForComplete(&cl, tr);
+ COMPARE(tr->responseCode(), 200);
+ }
+
+ cerr << "done args" << endl;
+
+ {
+ TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
+ HTTP::Request_ptr own(tr);
+ tr->requestHeader("X-Foo") = "Bar";
+ tr->requestHeader("X-AnotherHeader") = "A longer value";
+ cl.makeRequest(tr);
+ waitForComplete(&cl, tr);
+ COMPARE(tr->responseCode(), 200);
+ COMPARE(tr->responseReason(), string("OK"));
+ COMPARE(tr->responseLength(), strlen(BODY1));
+ COMPARE(tr->responseBytesReceived(), strlen(BODY1));
+ COMPARE(tr->bodyData, string(BODY1));
+ }
+
// larger get request
for (unsigned int i=0; i<body2Size; ++i) {
body2[i] = (i << 4) | (i >> 2);
TestRequest* tr = new TestRequest("http://localhost:2000/test2");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
- waitForComplete(tr);
+ waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseBytesReceived(), body2Size);
COMPARE(tr->bodyData, string(body2, body2Size));
}
+ cerr << "testing chunked" << endl;
{
TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
- waitForComplete(tr);
+ waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseReason(), string("OK"));
COMPARE(tr->responseBytesReceived(), 30);
TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
- waitForComplete(tr);
+ waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 404);
COMPARE(tr->responseReason(), string("not found"));
COMPARE(tr->responseLength(), 0);
}
+ cout << "done 404 test" << endl;
+
+ {
+ TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+ waitForComplete(&cl, tr);
+ COMPARE(tr->responseCode(), 200);
+ }
+
cout << "done1" << endl;
// test HTTP/1.0
{
TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
- waitForComplete(tr);
+ waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseLength(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
- waitForComplete(tr);
+ waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseLength(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
TestRequest* tr = new TestRequest("http://www.google.com/test2");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
- waitForComplete(tr);
+ waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseLength(), body2Size);
COMPARE(tr->bodyData, string(body2, body2Size));
TestRequest* tr = new TestRequest("http://www.google.com/test3");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
- waitForComplete(tr);
+ waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->responseBytesReceived(), body2Size);
COMPARE(tr->bodyData, string(body2, body2Size));
}
+// pipelining
+ cout << "testing HTTP 1.1 pipelineing" << endl;
+
+ {
+
+ cl.setProxy("", 80);
+ TestRequest* tr = new TestRequest("http://localhost:2000/test1");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+
+
+ TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
+ HTTP::Request_ptr own2(tr2);
+ cl.makeRequest(tr2);
+
+ TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
+ HTTP::Request_ptr own3(tr3);
+ cl.makeRequest(tr3);
+
+ waitForComplete(&cl, tr3);
+ VERIFY(tr->complete);
+ VERIFY(tr2->complete);
+ COMPARE(tr->bodyData, string(BODY1));
+
+ COMPARE(tr2->responseLength(), strlen(BODY3));
+ COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
+ COMPARE(tr2->bodyData, string(BODY3));
+
+ COMPARE(tr3->bodyData, string(BODY1));
+ }
+
+// multiple requests with an HTTP 1.0 server
+ {
+ cout << "http 1.0 multiple requests" << endl;
+
+ cl.setProxy("", 80);
+ TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+
+ TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
+ HTTP::Request_ptr own2(tr2);
+ cl.makeRequest(tr2);
+
+ TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
+ HTTP::Request_ptr own3(tr3);
+ cl.makeRequest(tr3);
+
+ waitForComplete(&cl, tr3);
+ VERIFY(tr->complete);
+ VERIFY(tr2->complete);
+
+ COMPARE(tr->responseLength(), strlen(BODY1));
+ COMPARE(tr->responseBytesReceived(), strlen(BODY1));
+ COMPARE(tr->bodyData, string(BODY1));
+
+ COMPARE(tr2->responseLength(), strlen(BODY3));
+ COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
+ COMPARE(tr2->bodyData, string(BODY3));
+ COMPARE(tr3->bodyData, string(BODY1));
+ }
+
+// POST
+ {
+ cout << "POST" << endl;
+ TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
+ tr->setBodyData("", "application/x-www-form-urlencoded");
+
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+ waitForComplete(&cl, tr);
+ COMPARE(tr->responseCode(), 204);
+ }
+
+ // test_zero_length_content
+ {
+ cout << "zero-length-content-response" << endl;
+ TestRequest* tr = new TestRequest("http://localhost:2000/test_zero_length_content");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+ waitForComplete(&cl, tr);
+ COMPARE(tr->responseCode(), 200);
+ COMPARE(tr->bodyData, string());
+ COMPARE(tr->responseBytesReceived(), 0);
+ }
+
+
cout << "all tests passed ok" << endl;
return EXIT_SUCCESS;
}