8 #include <boost/algorithm/string/case_conv.hpp>
10 #include <simgear/simgear_config.h>
12 #include "HTTPClient.hxx"
13 #include "HTTPRequest.hxx"
15 #include "test_HTTP.hxx"
17 #include <simgear/misc/strutils.hxx>
18 #include <simgear/timing/timestamp.hxx>
19 #include <simgear/debug/logstream.hxx>
21 #include <curl/multi.h>
27 using std::stringstream;
29 using namespace simgear;
31 const char* BODY1 = "The quick brown fox jumps over a lazy dog.";
32 const char* BODY3 = "Cras ut neque nulla. Duis ut velit neque, sit amet "
33 "pharetra risus. In est ligula, lacinia vitae congue in, sollicitudin at "
34 "libero. Mauris pharetra pretium elit, nec placerat dui semper et. Maecenas "
35 "magna magna, placerat sed luctus ac, commodo et ligula. Mauris at purus et "
36 "nisl molestie auctor placerat at quam. Donec sapien magna, venenatis sed "
37 "iaculis id, fringilla vel arcu. Duis sed neque nisi. Cras a arcu sit amet "
38 "risus ultrices varius. Integer sagittis euismod dui id varius. Cras vel "
39 "justo gravida metus.";
41 const unsigned int body2Size = 8 * 1024;
42 char body2[body2Size];
44 #define COMPARE(a, b) \
46 cerr << "failed:" << #a << " != " << #b << endl; \
47 cerr << "\tgot:'" << a << "'" << endl; \
53 cerr << "failed:" << #a << endl; \
57 class TestRequest : public HTTP::Request
64 TestRequest(const std::string& url, const std::string method = "GET") :
65 HTTP::Request(url, method),
72 std::map<string, string> headers;
85 virtual void gotBodyData(const char* s, int n)
87 //std::cout << "got body data:'" << string(s, n) << "'" <<std::endl;
88 bodyData += string(s, n);
91 virtual void responseHeader(const string& header, const string& value)
93 Request::responseHeader(header, value);
94 headers[header] = value;
98 class HTTPTestChannel : public TestServerChannel
102 virtual void processRequestHeaders()
104 if (path == "/test1") {
105 string contentStr(BODY1);
107 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
108 d << "Content-Length:" << contentStr.size() << "\r\n";
109 d << "\r\n"; // final CRLF to terminate the headers
111 push(d.str().c_str());
112 } else if (path == "/testLorem") {
113 string contentStr(BODY3);
115 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
116 d << "Content-Length:" << contentStr.size() << "\r\n";
117 d << "\r\n"; // final CRLF to terminate the headers
119 push(d.str().c_str());
120 } else if (path == "/test_zero_length_content") {
123 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
124 d << "Content-Length:" << contentStr.size() << "\r\n";
125 d << "\r\n"; // final CRLF to terminate the headers
127 push(d.str().c_str());
128 } else if (path == "/test_headers") {
129 COMPARE(requestHeaders["X-Foo"], string("Bar"));
130 COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
132 string contentStr(BODY1);
134 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
135 d << "Content-Length:" << contentStr.size() << "\r\n";
136 d << "\r\n"; // final CRLF to terminate the headers
138 push(d.str().c_str());
139 } else if (path == "/test2") {
141 } else if (path == "/testchunked") {
143 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
144 d << "Transfer-Encoding:chunked\r\n";
146 d << "8\r\n"; // first chunk
148 d << "6\r\n"; // second chunk
150 d << "10\r\n"; // third chunk
151 d << "ABCDSTUVABCDSTUV\r\n";
152 d << "0\r\n"; // start of trailer
153 d << "X-Foobar: wibble\r\n"; // trailer data
155 push(d.str().c_str());
156 } else if (path == "http://www.google.com/test2") {
158 if (requestHeaders["Host"] != "www.google.com") {
159 sendErrorResponse(400, true, "bad destination");
162 if (requestHeaders["Proxy-Authorization"] != string()) {
163 sendErrorResponse(401, false, "bad auth, not empty"); // shouldn't supply auth
167 } else if (path == "http://www.google.com/test3") {
169 if (requestHeaders["Host"] != "www.google.com") {
170 sendErrorResponse(400, true, "bad destination");
173 string credentials = requestHeaders["Proxy-Authorization"];
174 if (credentials.substr(0, 5) != "Basic") {
175 // request basic auth
177 d << "HTTP/1.1 " << 407 << " " << reasonForCode(407) << "\r\n";
178 d << "WWW-Authenticate: Basic real=\"simgear\"\r\n";
179 d << "\r\n"; // final CRLF to terminate the headers
180 push(d.str().c_str());
184 std::vector<unsigned char> userAndPass;
185 strutils::decodeBase64(credentials.substr(6), userAndPass);
186 std::string decodedUserPass((char*) userAndPass.data(), userAndPass.size());
188 if (decodedUserPass != "johndoe:swordfish") {
189 std::map<string, string>::const_iterator it;
190 for (it = requestHeaders.begin(); it != requestHeaders.end(); ++it) {
191 cerr << "header:" << it->first << " = " << it->second << endl;
194 sendErrorResponse(401, false, "bad auth, not as set"); // forbidden
198 } else if (strutils::starts_with(path, "/test_1_0")) {
199 string contentStr(BODY1);
200 if (strutils::ends_with(path, "/B")) {
204 d << "HTTP/1.0 " << 200 << " " << reasonForCode(200) << "\r\n";
205 d << "\r\n"; // final CRLF to terminate the headers
207 push(d.str().c_str());
209 } else if (path == "/test_close") {
210 string contentStr(BODY1);
212 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
213 d << "Connection: close\r\n";
214 d << "\r\n"; // final CRLF to terminate the headers
216 push(d.str().c_str());
218 } else if (path == "/test_abrupt_close") {
219 // simulate server doing socket close before sending any
220 // response - this used to cause a TerraSync failure since we
221 // would get stuck restarting the request
224 } else if (path == "/test_args") {
225 if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
226 sendErrorResponse(400, true, "bad arguments");
230 string contentStr(BODY1);
232 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
233 d << "Content-Length:" << contentStr.size() << "\r\n";
234 d << "\r\n"; // final CRLF to terminate the headers
236 push(d.str().c_str());
237 } else if (path == "/test_post") {
238 if (requestHeaders["Content-Type"] != "application/x-www-form-urlencoded") {
239 cerr << "bad content type: '" << requestHeaders["Content-Type"] << "'" << endl;
240 sendErrorResponse(400, true, "bad content type");
244 requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
245 setByteCount(requestContentLength);
246 state = STATE_REQUEST_BODY;
247 } else if ((path == "/test_put") || (path == "/test_create")) {
248 if (requestHeaders["Content-Type"] != "x-application/foobar") {
249 cerr << "bad content type: '" << requestHeaders["Content-Type"] << "'" << endl;
250 sendErrorResponse(400, true, "bad content type");
254 requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
255 setByteCount(requestContentLength);
256 state = STATE_REQUEST_BODY;
257 } else if (path == "/test_get_during_send") {
258 // indicate we will send some number of bytes, but only send
260 waitingOnNextRequestToContinue = true;
262 string contentStr(BODY3, 100); // only send first 100 chars
264 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
265 d << "Content-Length:" << strlen(BODY3) << "\r\n";
266 d << "\r\n"; // final CRLF to terminate the headers
268 push(d.str().c_str());
269 } else if (path == "/test_get_during_send_2") {
272 if (waitingOnNextRequestToContinue) {
273 waitingOnNextRequestToContinue = false;
274 // push the rest of the first request
275 d << string(BODY3).substr(100);
279 // push the response to this request
280 string contentStr(BODY1);
281 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
282 d << "Content-Length:" << contentStr.size() << "\r\n";
283 d << "\r\n"; // final CRLF to terminate the headers
285 push(d.str().c_str());
288 TestServerChannel::processRequestHeaders();
292 void processRequestBody()
294 if (path == "/test_post") {
295 if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
296 sendErrorResponse(400, true, "bad arguments");
301 d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
302 d << "\r\n"; // final CRLF to terminate the headers
303 push(d.str().c_str());
304 } else if (path == "/test_put") {
305 std::cerr << "sending PUT response" << std::endl;
307 COMPARE(buffer, BODY3);
309 d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
310 d << "\r\n"; // final CRLF to terminate the headers
311 push(d.str().c_str());
312 } else if (path == "/test_create") {
313 std::cerr << "sending create response" << std::endl;
315 std::string entityStr = "http://localhost:2000/something.txt";
317 COMPARE(buffer, BODY3);
319 d << "HTTP/1.1 " << 201 << " " << reasonForCode(201) << "\r\n";
320 d << "Location:" << entityStr << "\r\n";
321 d << "Content-Length:" << entityStr.size() << "\r\n";
322 d << "\r\n"; // final CRLF to terminate the headers
325 push(d.str().c_str());
327 TestServerChannel::processRequestBody();
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 bool waitingOnNextRequestToContinue;
344 TestServer<HTTPTestChannel> testServer;
346 void waitForComplete(HTTP::Client* cl, TestRequest* tr)
348 SGTimeStamp start(SGTimeStamp::now());
349 while (start.elapsedMSec() < 10000) {
356 SGTimeStamp::sleepForMSec(15);
359 cerr << "timed out" << endl;
362 void waitForFailed(HTTP::Client* cl, TestRequest* tr)
364 SGTimeStamp start(SGTimeStamp::now());
365 while (start.elapsedMSec() < 10000) {
372 SGTimeStamp::sleepForMSec(15);
375 cerr << "timed out waiting for failure" << endl;
378 int main(int argc, char* argv[])
380 sglog().setLogLevels( SG_ALL, SG_INFO );
383 // force all requests to use the same connection for this test
384 cl.setMaxConnections(1);
387 TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
388 COMPARE(tr1->scheme(), "http");
389 COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
390 COMPARE(tr1->host(), "localhost.woo.zar");
391 COMPARE(tr1->port(), 2000);
392 COMPARE(tr1->path(), "/test1");
394 TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
395 COMPARE(tr2->scheme(), "http");
396 COMPARE(tr2->hostAndPort(), "192.168.1.1");
397 COMPARE(tr2->host(), "192.168.1.1");
398 COMPARE(tr2->port(), 80);
399 COMPARE(tr2->path(), "/test1/dir/thing/file.png");
403 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
404 HTTP::Request_ptr own(tr);
407 waitForComplete(&cl, tr);
408 COMPARE(tr->responseCode(), 200);
409 COMPARE(tr->responseReason(), string("OK"));
410 COMPARE(tr->responseLength(), strlen(BODY1));
411 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
412 COMPARE(tr->bodyData, string(BODY1));
416 TestRequest* tr = new TestRequest("http://localhost:2000/testLorem");
417 HTTP::Request_ptr own(tr);
420 waitForComplete(&cl, tr);
421 COMPARE(tr->responseCode(), 200);
422 COMPARE(tr->responseReason(), string("OK"));
423 COMPARE(tr->responseLength(), strlen(BODY3));
424 COMPARE(tr->responseBytesReceived(), strlen(BODY3));
425 COMPARE(tr->bodyData, string(BODY3));
429 TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
430 HTTP::Request_ptr own(tr);
432 waitForComplete(&cl, tr);
433 COMPARE(tr->responseCode(), 200);
437 TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
438 HTTP::Request_ptr own(tr);
439 tr->requestHeader("X-Foo") = "Bar";
440 tr->requestHeader("X-AnotherHeader") = "A longer value";
443 waitForComplete(&cl, tr);
444 COMPARE(tr->responseCode(), 200);
445 COMPARE(tr->responseReason(), string("OK"));
446 COMPARE(tr->responseLength(), strlen(BODY1));
447 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
448 COMPARE(tr->bodyData, string(BODY1));
451 // larger get request
452 for (unsigned int i=0; i<body2Size; ++i) {
453 body2[i] = (i << 4) | (i >> 2);
457 TestRequest* tr = new TestRequest("http://localhost:2000/test2");
458 HTTP::Request_ptr own(tr);
460 waitForComplete(&cl, tr);
461 COMPARE(tr->responseCode(), 200);
462 COMPARE(tr->responseBytesReceived(), body2Size);
463 COMPARE(tr->bodyData, string(body2, body2Size));
466 cerr << "testing chunked transfer encoding" << endl;
468 TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
469 HTTP::Request_ptr own(tr);
472 waitForComplete(&cl, tr);
473 COMPARE(tr->responseCode(), 200);
474 COMPARE(tr->responseReason(), string("OK"));
475 COMPARE(tr->responseBytesReceived(), 30);
476 COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
477 // check trailers made it too
478 COMPARE(tr->headers["x-foobar"], string("wibble"));
483 TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
484 HTTP::Request_ptr own(tr);
486 waitForComplete(&cl, tr);
487 COMPARE(tr->responseCode(), 404);
488 COMPARE(tr->responseReason(), string("not found"));
489 COMPARE(tr->responseLength(), 0);
492 cout << "done 404 test" << endl;
495 TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
496 HTTP::Request_ptr own(tr);
498 waitForComplete(&cl, tr);
499 COMPARE(tr->responseCode(), 200);
502 cout << "done1" << endl;
505 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
506 HTTP::Request_ptr own(tr);
508 waitForComplete(&cl, tr);
509 COMPARE(tr->responseCode(), 200);
510 COMPARE(tr->responseLength(), strlen(BODY1));
511 COMPARE(tr->bodyData, string(BODY1));
514 cout << "done2" << endl;
515 // test HTTP/1.1 Connection::close
517 TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
518 HTTP::Request_ptr own(tr);
520 waitForComplete(&cl, tr);
521 COMPARE(tr->responseCode(), 200);
522 COMPARE(tr->responseLength(), strlen(BODY1));
523 COMPARE(tr->bodyData, string(BODY1));
525 cout << "done3" << endl;
526 // test connectToHost failure
529 TestRequest* tr = new TestRequest("http://not.found/something");
530 HTTP::Request_ptr own(tr);
532 waitForFailed(&cl, tr);
536 const int HOST_NOT_FOUND_CODE = CURLE_COULDNT_RESOLVE_HOST;
537 COMPARE(tr->responseCode(), HOST_NOT_FOUND_CODE);
540 cout << "testing abrupt close" << endl;
541 // test server-side abrupt close
543 TestRequest* tr = new TestRequest("http://localhost:2000/test_abrupt_close");
544 HTTP::Request_ptr own(tr);
546 waitForFailed(&cl, tr);
548 const int SERVER_NO_DATA_CODE = CURLE_GOT_NOTHING;
549 COMPARE(tr->responseCode(), SERVER_NO_DATA_CODE);
552 cout << "testing proxy close" << endl;
555 cl.setProxy("localhost", 2000);
556 TestRequest* tr = new TestRequest("http://www.google.com/test2");
557 HTTP::Request_ptr own(tr);
559 waitForComplete(&cl, tr);
560 COMPARE(tr->responseCode(), 200);
561 COMPARE(tr->responseLength(), body2Size);
562 COMPARE(tr->bodyData, string(body2, body2Size));
566 cl.setProxy("localhost", 2000, "johndoe:swordfish");
567 TestRequest* tr = new TestRequest("http://www.google.com/test3");
568 HTTP::Request_ptr own(tr);
570 waitForComplete(&cl, tr);
571 COMPARE(tr->responseCode(), 200);
572 COMPARE(tr->responseBytesReceived(), body2Size);
573 COMPARE(tr->bodyData, string(body2, body2Size));
577 cout << "testing HTTP 1.1 pipelining" << endl;
580 testServer.disconnectAll();
581 cl.clearAllConnections();
584 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
585 HTTP::Request_ptr own(tr);
589 TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
590 HTTP::Request_ptr own2(tr2);
593 TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
594 HTTP::Request_ptr own3(tr3);
597 waitForComplete(&cl, tr3);
598 VERIFY(tr->complete);
599 VERIFY(tr2->complete);
600 COMPARE(tr->bodyData, string(BODY1));
602 COMPARE(tr2->responseLength(), strlen(BODY3));
603 COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
604 COMPARE(tr2->bodyData, string(BODY3));
606 COMPARE(tr3->bodyData, string(BODY1));
608 COMPARE(testServer.connectCount(), 1);
611 // multiple requests with an HTTP 1.0 server
613 cout << "http 1.0 multiple requests" << endl;
616 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
617 HTTP::Request_ptr own(tr);
620 TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
621 HTTP::Request_ptr own2(tr2);
624 TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
625 HTTP::Request_ptr own3(tr3);
628 waitForComplete(&cl, tr3);
629 VERIFY(tr->complete);
630 VERIFY(tr2->complete);
632 COMPARE(tr->responseLength(), strlen(BODY1));
633 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
634 COMPARE(tr->bodyData, string(BODY1));
636 COMPARE(tr2->responseLength(), strlen(BODY3));
637 COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
638 COMPARE(tr2->bodyData, string(BODY3));
639 COMPARE(tr3->bodyData, string(BODY1));
644 cout << "testing POST" << endl;
645 TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
646 tr->setBodyData("", "application/x-www-form-urlencoded");
648 HTTP::Request_ptr own(tr);
650 waitForComplete(&cl, tr);
651 COMPARE(tr->responseCode(), 204);
656 cout << "testing PUT" << endl;
657 TestRequest* tr = new TestRequest("http://localhost:2000/test_put", "PUT");
658 tr->setBodyData(BODY3, "x-application/foobar");
660 HTTP::Request_ptr own(tr);
662 waitForComplete(&cl, tr);
663 COMPARE(tr->responseCode(), 204);
667 cout << "testing PUT create" << endl;
668 TestRequest* tr = new TestRequest("http://localhost:2000/test_create", "PUT");
669 tr->setBodyData(BODY3, "x-application/foobar");
671 HTTP::Request_ptr own(tr);
673 waitForComplete(&cl, tr);
674 COMPARE(tr->responseCode(), 201);
677 // test_zero_length_content
679 cout << "zero-length-content-response" << endl;
680 TestRequest* tr = new TestRequest("http://localhost:2000/test_zero_length_content");
681 HTTP::Request_ptr own(tr);
683 waitForComplete(&cl, tr);
684 COMPARE(tr->responseCode(), 200);
685 COMPARE(tr->bodyData, string());
686 COMPARE(tr->responseBytesReceived(), 0);
691 cout << "cancel request" << endl;
692 testServer.disconnectAll();
693 cl.clearAllConnections();
696 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
697 HTTP::Request_ptr own(tr);
700 TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
701 HTTP::Request_ptr own2(tr2);
704 TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
705 HTTP::Request_ptr own3(tr3);
708 cl.cancelRequest(tr, "my reason 1");
710 cl.cancelRequest(tr2, "my reason 2");
712 waitForComplete(&cl, tr3);
714 COMPARE(tr->responseCode(), -1);
715 COMPARE(tr2->responseReason(), "my reason 2");
717 COMPARE(tr3->responseLength(), strlen(BODY1));
718 COMPARE(tr3->responseBytesReceived(), strlen(BODY1));
719 COMPARE(tr3->bodyData, string(BODY1));
724 cout << "cancel middle request" << endl;
725 testServer.disconnectAll();
726 cl.clearAllConnections();
729 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
730 HTTP::Request_ptr own(tr);
733 TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
734 HTTP::Request_ptr own2(tr2);
737 TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
738 HTTP::Request_ptr own3(tr3);
741 cl.cancelRequest(tr2, "middle request");
743 waitForComplete(&cl, tr3);
745 COMPARE(tr->responseCode(), 200);
746 COMPARE(tr->responseLength(), strlen(BODY1));
747 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
748 COMPARE(tr->bodyData, string(BODY1));
750 COMPARE(tr2->responseCode(), -1);
752 COMPARE(tr3->responseLength(), strlen(BODY1));
753 COMPARE(tr3->responseBytesReceived(), strlen(BODY1));
754 COMPARE(tr3->bodyData, string(BODY1));
758 cout << "get-during-response-send" << endl;
759 cl.clearAllConnections();
760 //test_get_during_send
762 TestRequest* tr = new TestRequest("http://localhost:2000/test_get_during_send");
763 HTTP::Request_ptr own(tr);
767 for (int i=0; i<10; ++i) {
768 SGTimeStamp::sleepForMSec(1);
774 TestRequest* tr2 = new TestRequest("http://localhost:2000/test_get_during_send_2");
775 HTTP::Request_ptr own2(tr2);
778 waitForComplete(&cl, tr2);
779 COMPARE(tr->responseCode(), 200);
780 COMPARE(tr->bodyData, string(BODY3));
781 COMPARE(tr->responseBytesReceived(), strlen(BODY3));
782 COMPARE(tr2->responseCode(), 200);
783 COMPARE(tr2->bodyData, string(BODY1));
784 COMPARE(tr2->responseBytesReceived(), strlen(BODY1));
787 cout << "all tests passed ok" << endl;