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),
63 std::map<string, string> sendHeaders;
64 std::map<string, string> headers;
66 string_list requestHeaders() const
69 std::map<string, string>::const_iterator it;
70 for (it = sendHeaders.begin(); it != sendHeaders.end(); ++it) {
71 r.push_back(it->first);
76 string header(const string& name) const
78 std::map<string, string>::const_iterator it = sendHeaders.find(name);
79 if (it == sendHeaders.end()) {
86 virtual void responseHeadersComplete()
90 virtual void responseComplete()
95 virtual void failure()
100 virtual void gotBodyData(const char* s, int n)
102 //std::cout << "got body data:'" << string(s, n) << "'" <<std::endl;
103 bodyData += string(s, n);
106 virtual void responseHeader(const string& header, const string& value)
108 headers[header] = value;
112 class TestServerChannel : public NetChat
126 setTerminator("\r\n");
130 virtual void collectIncomingData(const char* s, int n)
132 buffer += string(s, n);
135 virtual void foundTerminator(void)
137 if (state == STATE_IDLE) {
138 state = STATE_HEADERS;
139 string_list line = strutils::split(buffer, NULL, 3);
140 if (line.size() < 3) {
141 cerr << "malformed request:" << buffer << endl;
148 string::size_type queryPos = path.find('?');
149 if (queryPos != string::npos) {
150 parseArgs(path.substr(queryPos + 1));
151 path = path.substr(0, queryPos);
154 httpVersion = line[2];
155 requestHeaders.clear();
157 } else if (state == STATE_HEADERS) {
158 string s = strutils::simplify(buffer);
161 receivedRequestHeaders();
165 string::size_type colonPos = buffer.find(':');
166 if (colonPos == string::npos) {
167 cerr << "malformed HTTP response header:" << buffer << endl;
172 string key = strutils::simplify(buffer.substr(0, colonPos));
173 string value = strutils::strip(buffer.substr(colonPos + 1));
174 requestHeaders[key] = value;
176 } else if (state == STATE_REQUEST_BODY) {
178 setTerminator("\r\n");
179 } else if (state == STATE_CLOSING) {
184 void parseArgs(const string& argData)
186 string_list argv = strutils::split(argData, "&");
187 for (unsigned int a=0; a<argv.size(); ++a) {
188 string::size_type eqPos = argv[a].find('=');
189 if (eqPos == string::npos) {
190 cerr << "malformed HTTP argument:" << argv[a] << endl;
194 string key = argv[a].substr(0, eqPos);
195 string value = argv[a].substr(eqPos + 1);
200 void receivedRequestHeaders()
204 if (path == "/test1") {
205 string contentStr(BODY1);
207 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
208 d << "Content-Length:" << contentStr.size() << "\r\n";
209 d << "\r\n"; // final CRLF to terminate the headers
211 push(d.str().c_str());
212 } else if (path == "/testLorem") {
213 string contentStr(BODY3);
215 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
216 d << "Content-Length:" << contentStr.size() << "\r\n";
217 d << "\r\n"; // final CRLF to terminate the headers
219 push(d.str().c_str());
220 } else if (path == "/test_zero_length_content") {
223 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
224 d << "Content-Length:" << contentStr.size() << "\r\n";
225 d << "\r\n"; // final CRLF to terminate the headers
227 push(d.str().c_str());
228 } else if (path == "/test_headers") {
229 COMPARE(requestHeaders["X-Foo"], string("Bar"));
230 COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
232 string contentStr(BODY1);
234 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
235 d << "Content-Length:" << contentStr.size() << "\r\n";
236 d << "\r\n"; // final CRLF to terminate the headers
238 push(d.str().c_str());
239 } else if (path == "/test2") {
241 } else if (path == "/testchunked") {
243 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
244 d << "Transfer-Encoding:chunked\r\n";
246 d << "8\r\n"; // first chunk
248 d << "6\r\n"; // second chunk
250 d << "10\r\n"; // third chunk
251 d << "ABCDSTUVABCDSTUV\r\n";
252 d << "0\r\n"; // start of trailer
253 d << "X-Foobar: wibble\r\n"; // trailer data
255 push(d.str().c_str());
256 } else if (path == "http://www.google.com/test2") {
258 if (requestHeaders["Host"] != "www.google.com") {
259 sendErrorResponse(400, true, "bad destination");
262 if (requestHeaders["Proxy-Authorization"] != string()) {
263 sendErrorResponse(401, false, "bad auth"); // shouldn't supply auth
267 } else if (path == "http://www.google.com/test3") {
269 if (requestHeaders["Host"] != "www.google.com") {
270 sendErrorResponse(400, true, "bad destination");
273 if (requestHeaders["Proxy-Authorization"] != "ABCDEF") {
274 sendErrorResponse(401, false, "bad auth"); // forbidden
278 } else if (strutils::starts_with(path, "/test_1_0")) {
279 string contentStr(BODY1);
280 if (strutils::ends_with(path, "/B")) {
284 d << "HTTP/1.0 " << 200 << " " << reasonForCode(200) << "\r\n";
285 d << "\r\n"; // final CRLF to terminate the headers
287 push(d.str().c_str());
289 } else if (path == "/test_close") {
290 string contentStr(BODY1);
292 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
293 d << "Connection: close\r\n";
294 d << "\r\n"; // final CRLF to terminate the headers
296 push(d.str().c_str());
298 } else if (path == "/test_args") {
299 if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
300 sendErrorResponse(400, true, "bad arguments");
304 string contentStr(BODY1);
306 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
307 d << "Content-Length:" << contentStr.size() << "\r\n";
308 d << "\r\n"; // final CRLF to terminate the headers
310 push(d.str().c_str());
311 } else if (path == "/test_post") {
312 if (requestHeaders["Content-Type"] != "application/x-www-form-urlencoded") {
313 cerr << "bad content type: '" << requestHeaders["Content-Type"] << "'" << endl;
314 sendErrorResponse(400, true, "bad content type");
318 requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
319 setByteCount(requestContentLength);
320 state = STATE_REQUEST_BODY;
322 sendErrorResponse(404, false, "");
326 void closeAfterSending()
328 state = STATE_CLOSING;
335 if (method == "POST") {
339 if (path == "/test_post") {
340 if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
341 sendErrorResponse(400, true, "bad arguments");
346 d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
347 d << "\r\n"; // final CRLF to terminate the headers
348 push(d.str().c_str());
350 cerr << "sent 204 response ok" << endl;
357 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
358 d << "Content-Length:" << body2Size << "\r\n";
359 d << "\r\n"; // final CRLF to terminate the headers
360 push(d.str().c_str());
361 bufferSend(body2, body2Size);
364 void sendErrorResponse(int code, bool close, string content)
366 cerr << "sending error " << code << " for " << path << endl;
367 stringstream headerData;
368 headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n";
369 headerData << "Content-Length:" << content.size() << "\r\n";
370 headerData << "\r\n"; // final CRLF to terminate the headers
371 push(headerData.str().c_str());
372 push(content.c_str());
379 string reasonForCode(int code)
382 case 200: return "OK";
383 case 204: return "no content";
384 case 404: return "not found";
385 default: return "unknown code";
394 std::map<string, string> requestHeaders;
395 std::map<string, string> args;
396 int requestContentLength;
399 class TestServer : public NetChannel
405 bind(NULL, 2000); // localhost, any port
409 virtual ~TestServer()
413 virtual bool writable (void) { return false ; }
415 virtual void handleAccept (void)
417 simgear::IPAddress addr ;
418 int handle = accept ( &addr ) ;
419 //cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
420 TestServerChannel* chan = new TestServerChannel();
421 chan->setHandle(handle);
425 void waitForComplete(HTTP::Client* cl, TestRequest* tr)
427 SGTimeStamp start(SGTimeStamp::now());
428 while (start.elapsedMSec() < 1000) {
433 SGTimeStamp::sleepForMSec(1);
436 cerr << "timed out" << endl;
439 void waitForFailed(HTTP::Client* cl, TestRequest* tr)
441 SGTimeStamp start(SGTimeStamp::now());
442 while (start.elapsedMSec() < 1000) {
447 SGTimeStamp::sleepForMSec(1);
450 cerr << "timed out waiting for failure" << endl;
453 int main(int argc, char* argv[])
460 TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
461 COMPARE(tr1->scheme(), "http");
462 COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
463 COMPARE(tr1->host(), "localhost.woo.zar");
464 COMPARE(tr1->port(), 2000);
465 COMPARE(tr1->path(), "/test1");
467 TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
468 COMPARE(tr2->scheme(), "http");
469 COMPARE(tr2->hostAndPort(), "192.168.1.1");
470 COMPARE(tr2->host(), "192.168.1.1");
471 COMPARE(tr2->port(), 80);
472 COMPARE(tr2->path(), "/test1/dir/thing/file.png");
476 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
477 HTTP::Request_ptr own(tr);
480 waitForComplete(&cl, tr);
481 COMPARE(tr->responseCode(), 200);
482 COMPARE(tr->responseReason(), string("OK"));
483 COMPARE(tr->responseLength(), strlen(BODY1));
484 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
485 COMPARE(tr->bodyData, string(BODY1));
489 TestRequest* tr = new TestRequest("http://localhost:2000/testLorem");
490 HTTP::Request_ptr own(tr);
493 waitForComplete(&cl, tr);
494 COMPARE(tr->responseCode(), 200);
495 COMPARE(tr->responseReason(), string("OK"));
496 COMPARE(tr->responseLength(), strlen(BODY3));
497 COMPARE(tr->responseBytesReceived(), strlen(BODY3));
498 COMPARE(tr->bodyData, string(BODY3));
502 TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
503 HTTP::Request_ptr own(tr);
505 waitForComplete(&cl, tr);
506 COMPARE(tr->responseCode(), 200);
509 cerr << "done args" << endl;
512 TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
513 HTTP::Request_ptr own(tr);
514 tr->sendHeaders["X-Foo"] = "Bar";
515 tr->sendHeaders["X-AnotherHeader"] = "A longer value";
518 waitForComplete(&cl, tr);
519 COMPARE(tr->responseCode(), 200);
520 COMPARE(tr->responseReason(), string("OK"));
521 COMPARE(tr->responseLength(), strlen(BODY1));
522 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
523 COMPARE(tr->bodyData, string(BODY1));
526 // larger get request
527 for (unsigned int i=0; i<body2Size; ++i) {
528 body2[i] = (i << 4) | (i >> 2);
532 TestRequest* tr = new TestRequest("http://localhost:2000/test2");
533 HTTP::Request_ptr own(tr);
535 waitForComplete(&cl, tr);
536 COMPARE(tr->responseCode(), 200);
537 COMPARE(tr->responseBytesReceived(), body2Size);
538 COMPARE(tr->bodyData, string(body2, body2Size));
541 cerr << "testing chunked" << endl;
543 TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
544 HTTP::Request_ptr own(tr);
547 waitForComplete(&cl, tr);
548 COMPARE(tr->responseCode(), 200);
549 COMPARE(tr->responseReason(), string("OK"));
550 COMPARE(tr->responseBytesReceived(), 30);
551 COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
552 // check trailers made it too
553 COMPARE(tr->headers["x-foobar"], string("wibble"));
558 TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
559 HTTP::Request_ptr own(tr);
561 waitForComplete(&cl, tr);
562 COMPARE(tr->responseCode(), 404);
563 COMPARE(tr->responseReason(), string("not found"));
564 COMPARE(tr->responseLength(), 0);
567 cout << "done 404 test" << endl;
570 TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
571 HTTP::Request_ptr own(tr);
573 waitForComplete(&cl, tr);
574 COMPARE(tr->responseCode(), 200);
577 cout << "done1" << endl;
580 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
581 HTTP::Request_ptr own(tr);
583 waitForComplete(&cl, tr);
584 COMPARE(tr->responseCode(), 200);
585 COMPARE(tr->responseLength(), strlen(BODY1));
586 COMPARE(tr->bodyData, string(BODY1));
589 cout << "done2" << endl;
590 // test HTTP/1.1 Connection::close
592 TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
593 HTTP::Request_ptr own(tr);
595 waitForComplete(&cl, tr);
596 COMPARE(tr->responseCode(), 200);
597 COMPARE(tr->responseLength(), strlen(BODY1));
598 COMPARE(tr->bodyData, string(BODY1));
600 cout << "done3" << endl;
601 // test connectToHost failure
604 TestRequest* tr = new TestRequest("http://not.found/something");
605 HTTP::Request_ptr own(tr);
608 COMPARE(tr->responseCode(), -1);
613 cl.setProxy("localhost", 2000);
614 TestRequest* tr = new TestRequest("http://www.google.com/test2");
615 HTTP::Request_ptr own(tr);
617 waitForComplete(&cl, tr);
618 COMPARE(tr->responseCode(), 200);
619 COMPARE(tr->responseLength(), body2Size);
620 COMPARE(tr->bodyData, string(body2, body2Size));
624 cl.setProxy("localhost", 2000, "ABCDEF");
625 TestRequest* tr = new TestRequest("http://www.google.com/test3");
626 HTTP::Request_ptr own(tr);
628 waitForComplete(&cl, tr);
629 COMPARE(tr->responseCode(), 200);
630 COMPARE(tr->responseBytesReceived(), body2Size);
631 COMPARE(tr->bodyData, string(body2, body2Size));
635 cout << "testing HTTP 1.1 pipelineing" << endl;
639 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
640 HTTP::Request_ptr own(tr);
644 TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
645 HTTP::Request_ptr own2(tr2);
648 TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
649 HTTP::Request_ptr own3(tr3);
652 waitForComplete(&cl, tr3);
653 VERIFY(tr->complete);
654 VERIFY(tr2->complete);
655 COMPARE(tr->bodyData, string(BODY1));
657 COMPARE(tr2->responseLength(), strlen(BODY3));
658 COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
659 COMPARE(tr2->bodyData, string(BODY3));
661 COMPARE(tr3->bodyData, string(BODY1));
664 // multiple requests with an HTTP 1.0 server
666 cout << "http 1.0 multiple requests" << endl;
669 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
670 HTTP::Request_ptr own(tr);
673 TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
674 HTTP::Request_ptr own2(tr2);
677 TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
678 HTTP::Request_ptr own3(tr3);
681 waitForComplete(&cl, tr3);
682 VERIFY(tr->complete);
683 VERIFY(tr2->complete);
685 COMPARE(tr->responseLength(), strlen(BODY1));
686 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
687 COMPARE(tr->bodyData, string(BODY1));
689 COMPARE(tr2->responseLength(), strlen(BODY3));
690 COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
691 COMPARE(tr2->bodyData, string(BODY3));
692 COMPARE(tr3->bodyData, string(BODY1));
697 cout << "POST" << endl;
698 TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
699 HTTP::Request_ptr own(tr);
701 waitForComplete(&cl, tr);
702 COMPARE(tr->responseCode(), 204);
705 // test_zero_length_content
707 cout << "zero-length-content-response" << endl;
708 TestRequest* tr = new TestRequest("http://localhost:2000/test_zero_length_content");
709 HTTP::Request_ptr own(tr);
711 waitForComplete(&cl, tr);
712 COMPARE(tr->responseCode(), 200);
713 COMPARE(tr->bodyData, string());
714 COMPARE(tr->responseBytesReceived(), 0);
718 cout << "all tests passed ok" << endl;