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);
534 const int HOST_NOT_FOUND_CODE = CURLE_COULDNT_RESOLVE_HOST;
535 COMPARE(tr->responseCode(), HOST_NOT_FOUND_CODE);
538 cout << "testing abrupt close" << endl;
539 // test server-side abrupt close
541 TestRequest* tr = new TestRequest("http://localhost:2000/test_abrupt_close");
542 HTTP::Request_ptr own(tr);
544 waitForFailed(&cl, tr);
546 const int SERVER_NO_DATA_CODE = CURLE_GOT_NOTHING;
547 COMPARE(tr->responseCode(), SERVER_NO_DATA_CODE);
550 cout << "testing proxy close" << endl;
553 cl.setProxy("localhost", 2000);
554 TestRequest* tr = new TestRequest("http://www.google.com/test2");
555 HTTP::Request_ptr own(tr);
557 waitForComplete(&cl, tr);
558 COMPARE(tr->responseCode(), 200);
559 COMPARE(tr->responseLength(), body2Size);
560 COMPARE(tr->bodyData, string(body2, body2Size));
564 cl.setProxy("localhost", 2000, "johndoe:swordfish");
565 TestRequest* tr = new TestRequest("http://www.google.com/test3");
566 HTTP::Request_ptr own(tr);
568 waitForComplete(&cl, tr);
569 COMPARE(tr->responseCode(), 200);
570 COMPARE(tr->responseBytesReceived(), body2Size);
571 COMPARE(tr->bodyData, string(body2, body2Size));
575 cout << "testing HTTP 1.1 pipelining" << endl;
578 testServer.resetConnectCount();
579 cl.clearAllConnections();
582 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
583 HTTP::Request_ptr own(tr);
587 TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
588 HTTP::Request_ptr own2(tr2);
591 TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
592 HTTP::Request_ptr own3(tr3);
595 waitForComplete(&cl, tr3);
596 VERIFY(tr->complete);
597 VERIFY(tr2->complete);
598 COMPARE(tr->bodyData, string(BODY1));
600 COMPARE(tr2->responseLength(), strlen(BODY3));
601 COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
602 COMPARE(tr2->bodyData, string(BODY3));
604 COMPARE(tr3->bodyData, string(BODY1));
606 COMPARE(testServer.connectCount(), 1);
609 // multiple requests with an HTTP 1.0 server
611 cout << "http 1.0 multiple requests" << endl;
614 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
615 HTTP::Request_ptr own(tr);
618 TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
619 HTTP::Request_ptr own2(tr2);
622 TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
623 HTTP::Request_ptr own3(tr3);
626 waitForComplete(&cl, tr3);
627 VERIFY(tr->complete);
628 VERIFY(tr2->complete);
630 COMPARE(tr->responseLength(), strlen(BODY1));
631 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
632 COMPARE(tr->bodyData, string(BODY1));
634 COMPARE(tr2->responseLength(), strlen(BODY3));
635 COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
636 COMPARE(tr2->bodyData, string(BODY3));
637 COMPARE(tr3->bodyData, string(BODY1));
642 cout << "testing POST" << endl;
643 TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
644 tr->setBodyData("", "application/x-www-form-urlencoded");
646 HTTP::Request_ptr own(tr);
648 waitForComplete(&cl, tr);
649 COMPARE(tr->responseCode(), 204);
654 cout << "testing PUT" << endl;
655 TestRequest* tr = new TestRequest("http://localhost:2000/test_put", "PUT");
656 tr->setBodyData(BODY3, "x-application/foobar");
658 HTTP::Request_ptr own(tr);
660 waitForComplete(&cl, tr);
661 COMPARE(tr->responseCode(), 204);
665 cout << "testing PUT create" << endl;
666 TestRequest* tr = new TestRequest("http://localhost:2000/test_create", "PUT");
667 tr->setBodyData(BODY3, "x-application/foobar");
669 HTTP::Request_ptr own(tr);
671 waitForComplete(&cl, tr);
672 COMPARE(tr->responseCode(), 201);
675 // test_zero_length_content
677 cout << "zero-length-content-response" << endl;
678 TestRequest* tr = new TestRequest("http://localhost:2000/test_zero_length_content");
679 HTTP::Request_ptr own(tr);
681 waitForComplete(&cl, tr);
682 COMPARE(tr->responseCode(), 200);
683 COMPARE(tr->bodyData, string());
684 COMPARE(tr->responseBytesReceived(), 0);
689 cout << "cancel request" << endl;
690 testServer.resetConnectCount();
691 cl.clearAllConnections();
694 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
695 HTTP::Request_ptr own(tr);
698 TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
699 HTTP::Request_ptr own2(tr2);
702 TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
703 HTTP::Request_ptr own3(tr3);
706 cl.cancelRequest(tr, "my reason 1");
708 cl.cancelRequest(tr2, "my reason 2");
710 waitForComplete(&cl, tr3);
712 COMPARE(tr->responseCode(), -1);
713 COMPARE(tr2->responseReason(), "my reason 2");
715 COMPARE(tr3->responseLength(), strlen(BODY1));
716 COMPARE(tr3->responseBytesReceived(), strlen(BODY1));
717 COMPARE(tr3->bodyData, string(BODY1));
722 cout << "cancel middle request" << endl;
723 testServer.resetConnectCount();
724 cl.clearAllConnections();
727 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
728 HTTP::Request_ptr own(tr);
731 TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
732 HTTP::Request_ptr own2(tr2);
735 TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
736 HTTP::Request_ptr own3(tr3);
739 cl.cancelRequest(tr2, "middle request");
741 waitForComplete(&cl, tr3);
743 COMPARE(tr->responseCode(), 200);
744 COMPARE(tr->responseLength(), strlen(BODY1));
745 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
746 COMPARE(tr->bodyData, string(BODY1));
748 COMPARE(tr2->responseCode(), -1);
750 COMPARE(tr3->responseLength(), strlen(BODY1));
751 COMPARE(tr3->responseBytesReceived(), strlen(BODY1));
752 COMPARE(tr3->bodyData, string(BODY1));
756 cout << "get-during-response-send" << endl;
757 cl.clearAllConnections();
758 //test_get_during_send
760 TestRequest* tr = new TestRequest("http://localhost:2000/test_get_during_send");
761 HTTP::Request_ptr own(tr);
765 for (int i=0; i<10; ++i) {
766 SGTimeStamp::sleepForMSec(1);
772 TestRequest* tr2 = new TestRequest("http://localhost:2000/test_get_during_send_2");
773 HTTP::Request_ptr own2(tr2);
776 waitForComplete(&cl, tr2);
777 COMPARE(tr->responseCode(), 200);
778 COMPARE(tr->bodyData, string(BODY3));
779 COMPARE(tr->responseBytesReceived(), strlen(BODY3));
780 COMPARE(tr2->responseCode(), 200);
781 COMPARE(tr2->bodyData, string(BODY1));
782 COMPARE(tr2->responseBytesReceived(), strlen(BODY1));
785 cout << "all tests passed ok" << endl;