7 #include <boost/algorithm/string/case_conv.hpp>
9 #include "HTTPClient.hxx"
10 #include "HTTPRequest.hxx"
12 #include <simgear/io/sg_netChat.hxx>
13 #include <simgear/misc/strutils.hxx>
14 #include <simgear/timing/timestamp.hxx>
20 using std::stringstream;
22 using namespace simgear;
24 const char* BODY1 = "The quick brown fox jumps over a lazy dog.";
25 const char* BODY3 = "Cras ut neque nulla. Duis ut velit neque, sit amet "
26 "pharetra risus. In est ligula, lacinia vitae congue in, sollicitudin at "
27 "libero. Mauris pharetra pretium elit, nec placerat dui semper et. Maecenas "
28 "magna magna, placerat sed luctus ac, commodo et ligula. Mauris at purus et "
29 "nisl molestie auctor placerat at quam. Donec sapien magna, venenatis sed "
30 "iaculis id, fringilla vel arcu. Duis sed neque nisi. Cras a arcu sit amet "
31 "risus ultrices varius. Integer sagittis euismod dui id varius. Cras vel "
32 "justo gravida metus.";
34 const unsigned int body2Size = 8 * 1024;
35 char body2[body2Size];
37 #define COMPARE(a, b) \
39 cerr << "failed:" << #a << " != " << #b << endl; \
40 cerr << "\tgot:'" << a << "'" << endl; \
46 cerr << "failed:" << #a << endl; \
50 class TestRequest : public HTTP::Request
57 TestRequest(const std::string& url, const std::string method = "GET") :
58 HTTP::Request(url, method),
64 std::map<string, string> headers;
77 virtual void gotBodyData(const char* s, int n)
79 //std::cout << "got body data:'" << string(s, n) << "'" <<std::endl;
80 bodyData += string(s, n);
83 virtual void responseHeader(const string& header, const string& value)
85 headers[header] = value;
89 class TestServerChannel : public NetChat
103 setTerminator("\r\n");
107 virtual void collectIncomingData(const char* s, int n)
109 buffer += string(s, n);
112 virtual void foundTerminator(void)
114 if (state == STATE_IDLE) {
115 state = STATE_HEADERS;
116 string_list line = strutils::split(buffer, NULL, 3);
117 if (line.size() < 3) {
118 cerr << "malformed request:" << buffer << endl;
125 string::size_type queryPos = path.find('?');
126 if (queryPos != string::npos) {
127 parseArgs(path.substr(queryPos + 1));
128 path = path.substr(0, queryPos);
131 httpVersion = line[2];
132 requestHeaders.clear();
134 } else if (state == STATE_HEADERS) {
135 string s = strutils::simplify(buffer);
138 receivedRequestHeaders();
142 string::size_type colonPos = buffer.find(':');
143 if (colonPos == string::npos) {
144 cerr << "malformed HTTP response header:" << buffer << endl;
149 string key = strutils::simplify(buffer.substr(0, colonPos));
150 string value = strutils::strip(buffer.substr(colonPos + 1));
151 requestHeaders[key] = value;
153 } else if (state == STATE_REQUEST_BODY) {
155 setTerminator("\r\n");
156 } else if (state == STATE_CLOSING) {
161 void parseArgs(const string& argData)
163 string_list argv = strutils::split(argData, "&");
164 for (unsigned int a=0; a<argv.size(); ++a) {
165 string::size_type eqPos = argv[a].find('=');
166 if (eqPos == string::npos) {
167 cerr << "malformed HTTP argument:" << argv[a] << endl;
171 string key = argv[a].substr(0, eqPos);
172 string value = argv[a].substr(eqPos + 1);
177 void receivedRequestHeaders()
181 if (path == "/test1") {
182 string contentStr(BODY1);
184 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
185 d << "Content-Length:" << contentStr.size() << "\r\n";
186 d << "\r\n"; // final CRLF to terminate the headers
188 push(d.str().c_str());
189 } else if (path == "/testLorem") {
190 string contentStr(BODY3);
192 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
193 d << "Content-Length:" << contentStr.size() << "\r\n";
194 d << "\r\n"; // final CRLF to terminate the headers
196 push(d.str().c_str());
197 } else if (path == "/test_zero_length_content") {
200 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
201 d << "Content-Length:" << contentStr.size() << "\r\n";
202 d << "\r\n"; // final CRLF to terminate the headers
204 push(d.str().c_str());
205 } else if (path == "/test_headers") {
206 COMPARE(requestHeaders["X-Foo"], string("Bar"));
207 COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
209 string contentStr(BODY1);
211 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
212 d << "Content-Length:" << contentStr.size() << "\r\n";
213 d << "\r\n"; // final CRLF to terminate the headers
215 push(d.str().c_str());
216 } else if (path == "/test2") {
218 } else if (path == "/testchunked") {
220 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
221 d << "Transfer-Encoding:chunked\r\n";
223 d << "8\r\n"; // first chunk
225 d << "6\r\n"; // second chunk
227 d << "10\r\n"; // third chunk
228 d << "ABCDSTUVABCDSTUV\r\n";
229 d << "0\r\n"; // start of trailer
230 d << "X-Foobar: wibble\r\n"; // trailer data
232 push(d.str().c_str());
233 } else if (path == "http://www.google.com/test2") {
235 if (requestHeaders["Host"] != "www.google.com") {
236 sendErrorResponse(400, true, "bad destination");
239 if (requestHeaders["Proxy-Authorization"] != string()) {
240 sendErrorResponse(401, false, "bad auth"); // shouldn't supply auth
244 } else if (path == "http://www.google.com/test3") {
246 if (requestHeaders["Host"] != "www.google.com") {
247 sendErrorResponse(400, true, "bad destination");
250 if (requestHeaders["Proxy-Authorization"] != "ABCDEF") {
251 sendErrorResponse(401, false, "bad auth"); // forbidden
255 } else if (strutils::starts_with(path, "/test_1_0")) {
256 string contentStr(BODY1);
257 if (strutils::ends_with(path, "/B")) {
261 d << "HTTP/1.0 " << 200 << " " << reasonForCode(200) << "\r\n";
262 d << "\r\n"; // final CRLF to terminate the headers
264 push(d.str().c_str());
266 } else if (path == "/test_close") {
267 string contentStr(BODY1);
269 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
270 d << "Connection: close\r\n";
271 d << "\r\n"; // final CRLF to terminate the headers
273 push(d.str().c_str());
275 } else if (path == "/test_args") {
276 if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
277 sendErrorResponse(400, true, "bad arguments");
281 string contentStr(BODY1);
283 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
284 d << "Content-Length:" << contentStr.size() << "\r\n";
285 d << "\r\n"; // final CRLF to terminate the headers
287 push(d.str().c_str());
288 } else if (path == "/test_post") {
289 if (requestHeaders["Content-Type"] != "application/x-www-form-urlencoded") {
290 cerr << "bad content type: '" << requestHeaders["Content-Type"] << "'" << endl;
291 sendErrorResponse(400, true, "bad content type");
295 requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
296 setByteCount(requestContentLength);
297 state = STATE_REQUEST_BODY;
299 sendErrorResponse(404, false, "");
303 void closeAfterSending()
305 state = STATE_CLOSING;
312 if (method == "POST") {
316 if (path == "/test_post") {
317 if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
318 sendErrorResponse(400, true, "bad arguments");
323 d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
324 d << "\r\n"; // final CRLF to terminate the headers
325 push(d.str().c_str());
327 cerr << "sent 204 response ok" << endl;
334 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
335 d << "Content-Length:" << body2Size << "\r\n";
336 d << "\r\n"; // final CRLF to terminate the headers
337 push(d.str().c_str());
338 bufferSend(body2, body2Size);
341 void sendErrorResponse(int code, bool close, string content)
343 cerr << "sending error " << code << " for " << path << endl;
344 stringstream headerData;
345 headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n";
346 headerData << "Content-Length:" << content.size() << "\r\n";
347 headerData << "\r\n"; // final CRLF to terminate the headers
348 push(headerData.str().c_str());
349 push(content.c_str());
356 string reasonForCode(int code)
359 case 200: return "OK";
360 case 204: return "no content";
361 case 404: return "not found";
362 default: return "unknown code";
371 std::map<string, string> requestHeaders;
372 std::map<string, string> args;
373 int requestContentLength;
376 class TestServer : public NetChannel
378 simgear::NetChannelPoller _poller;
382 Socket::initSockets();
385 bind(NULL, 2000); // localhost, any port
388 _poller.addChannel(this);
391 virtual ~TestServer()
395 virtual bool writable (void) { return false ; }
397 virtual void handleAccept (void)
399 simgear::IPAddress addr ;
400 int handle = accept ( &addr ) ;
401 //cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
402 TestServerChannel* chan = new TestServerChannel();
403 chan->setHandle(handle);
405 _poller.addChannel(chan);
414 TestServer testServer;
416 void waitForComplete(HTTP::Client* cl, TestRequest* tr)
418 SGTimeStamp start(SGTimeStamp::now());
419 while (start.elapsedMSec() < 10000) {
426 SGTimeStamp::sleepForMSec(15);
429 cerr << "timed out" << endl;
432 void waitForFailed(HTTP::Client* cl, TestRequest* tr)
434 SGTimeStamp start(SGTimeStamp::now());
435 while (start.elapsedMSec() < 10000) {
442 SGTimeStamp::sleepForMSec(15);
445 cerr << "timed out waiting for failure" << endl;
448 int main(int argc, char* argv[])
452 // force all requests to use the same connection for this test
453 cl.setMaxConnections(1);
456 TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
457 COMPARE(tr1->scheme(), "http");
458 COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
459 COMPARE(tr1->host(), "localhost.woo.zar");
460 COMPARE(tr1->port(), 2000);
461 COMPARE(tr1->path(), "/test1");
463 TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
464 COMPARE(tr2->scheme(), "http");
465 COMPARE(tr2->hostAndPort(), "192.168.1.1");
466 COMPARE(tr2->host(), "192.168.1.1");
467 COMPARE(tr2->port(), 80);
468 COMPARE(tr2->path(), "/test1/dir/thing/file.png");
472 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
473 HTTP::Request_ptr own(tr);
476 waitForComplete(&cl, tr);
477 COMPARE(tr->responseCode(), 200);
478 COMPARE(tr->responseReason(), string("OK"));
479 COMPARE(tr->responseLength(), strlen(BODY1));
480 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
481 COMPARE(tr->bodyData, string(BODY1));
485 TestRequest* tr = new TestRequest("http://localhost:2000/testLorem");
486 HTTP::Request_ptr own(tr);
489 waitForComplete(&cl, tr);
490 COMPARE(tr->responseCode(), 200);
491 COMPARE(tr->responseReason(), string("OK"));
492 COMPARE(tr->responseLength(), strlen(BODY3));
493 COMPARE(tr->responseBytesReceived(), strlen(BODY3));
494 COMPARE(tr->bodyData, string(BODY3));
498 TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
499 HTTP::Request_ptr own(tr);
501 waitForComplete(&cl, tr);
502 COMPARE(tr->responseCode(), 200);
505 cerr << "done args" << endl;
508 TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
509 HTTP::Request_ptr own(tr);
510 tr->requestHeader("X-Foo") = "Bar";
511 tr->requestHeader("X-AnotherHeader") = "A longer value";
514 waitForComplete(&cl, tr);
515 COMPARE(tr->responseCode(), 200);
516 COMPARE(tr->responseReason(), string("OK"));
517 COMPARE(tr->responseLength(), strlen(BODY1));
518 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
519 COMPARE(tr->bodyData, string(BODY1));
522 // larger get request
523 for (unsigned int i=0; i<body2Size; ++i) {
524 body2[i] = (i << 4) | (i >> 2);
528 TestRequest* tr = new TestRequest("http://localhost:2000/test2");
529 HTTP::Request_ptr own(tr);
531 waitForComplete(&cl, tr);
532 COMPARE(tr->responseCode(), 200);
533 COMPARE(tr->responseBytesReceived(), body2Size);
534 COMPARE(tr->bodyData, string(body2, body2Size));
537 cerr << "testing chunked" << endl;
539 TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
540 HTTP::Request_ptr own(tr);
543 waitForComplete(&cl, tr);
544 COMPARE(tr->responseCode(), 200);
545 COMPARE(tr->responseReason(), string("OK"));
546 COMPARE(tr->responseBytesReceived(), 30);
547 COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
548 // check trailers made it too
549 COMPARE(tr->headers["x-foobar"], string("wibble"));
554 TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
555 HTTP::Request_ptr own(tr);
557 waitForComplete(&cl, tr);
558 COMPARE(tr->responseCode(), 404);
559 COMPARE(tr->responseReason(), string("not found"));
560 COMPARE(tr->responseLength(), 0);
563 cout << "done 404 test" << endl;
566 TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
567 HTTP::Request_ptr own(tr);
569 waitForComplete(&cl, tr);
570 COMPARE(tr->responseCode(), 200);
573 cout << "done1" << endl;
576 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
577 HTTP::Request_ptr own(tr);
579 waitForComplete(&cl, tr);
580 COMPARE(tr->responseCode(), 200);
581 COMPARE(tr->responseLength(), strlen(BODY1));
582 COMPARE(tr->bodyData, string(BODY1));
585 cout << "done2" << endl;
586 // test HTTP/1.1 Connection::close
588 TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
589 HTTP::Request_ptr own(tr);
591 waitForComplete(&cl, tr);
592 COMPARE(tr->responseCode(), 200);
593 COMPARE(tr->responseLength(), strlen(BODY1));
594 COMPARE(tr->bodyData, string(BODY1));
596 cout << "done3" << endl;
597 // test connectToHost failure
600 TestRequest* tr = new TestRequest("http://not.found/something");
601 HTTP::Request_ptr own(tr);
604 COMPARE(tr->responseCode(), -1);
609 cl.setProxy("localhost", 2000);
610 TestRequest* tr = new TestRequest("http://www.google.com/test2");
611 HTTP::Request_ptr own(tr);
613 waitForComplete(&cl, tr);
614 COMPARE(tr->responseCode(), 200);
615 COMPARE(tr->responseLength(), body2Size);
616 COMPARE(tr->bodyData, string(body2, body2Size));
620 cl.setProxy("localhost", 2000, "ABCDEF");
621 TestRequest* tr = new TestRequest("http://www.google.com/test3");
622 HTTP::Request_ptr own(tr);
624 waitForComplete(&cl, tr);
625 COMPARE(tr->responseCode(), 200);
626 COMPARE(tr->responseBytesReceived(), body2Size);
627 COMPARE(tr->bodyData, string(body2, body2Size));
631 cout << "testing HTTP 1.1 pipelineing" << endl;
636 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
637 HTTP::Request_ptr own(tr);
641 TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
642 HTTP::Request_ptr own2(tr2);
645 TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
646 HTTP::Request_ptr own3(tr3);
649 waitForComplete(&cl, tr3);
650 VERIFY(tr->complete);
651 VERIFY(tr2->complete);
652 COMPARE(tr->bodyData, string(BODY1));
654 COMPARE(tr2->responseLength(), strlen(BODY3));
655 COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
656 COMPARE(tr2->bodyData, string(BODY3));
658 COMPARE(tr3->bodyData, string(BODY1));
661 // multiple requests with an HTTP 1.0 server
663 cout << "http 1.0 multiple requests" << endl;
666 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
667 HTTP::Request_ptr own(tr);
670 TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
671 HTTP::Request_ptr own2(tr2);
674 TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
675 HTTP::Request_ptr own3(tr3);
678 waitForComplete(&cl, tr3);
679 VERIFY(tr->complete);
680 VERIFY(tr2->complete);
682 COMPARE(tr->responseLength(), strlen(BODY1));
683 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
684 COMPARE(tr->bodyData, string(BODY1));
686 COMPARE(tr2->responseLength(), strlen(BODY3));
687 COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
688 COMPARE(tr2->bodyData, string(BODY3));
689 COMPARE(tr3->bodyData, string(BODY1));
694 cout << "POST" << endl;
695 TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
696 tr->setBodyData("", "application/x-www-form-urlencoded");
698 HTTP::Request_ptr own(tr);
700 waitForComplete(&cl, tr);
701 COMPARE(tr->responseCode(), 204);
704 // test_zero_length_content
706 cout << "zero-length-content-response" << endl;
707 TestRequest* tr = new TestRequest("http://localhost:2000/test_zero_length_content");
708 HTTP::Request_ptr own(tr);
710 waitForComplete(&cl, tr);
711 COMPARE(tr->responseCode(), 200);
712 COMPARE(tr->bodyData, string());
713 COMPARE(tr->responseBytesReceived(), 0);
717 cout << "all tests passed ok" << endl;