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
56 string bodyContentType;
58 TestRequest(const std::string& url, const std::string method = "GET") :
59 HTTP::Request(url, method),
62 bodyContentType = "text/plain";
65 std::map<string, string> sendHeaders;
66 std::map<string, string> headers;
68 string_list requestHeaders() const
71 std::map<string, string>::const_iterator it;
72 for (it = sendHeaders.begin(); it != sendHeaders.end(); ++it) {
73 r.push_back(it->first);
78 string header(const string& name) const
80 std::map<string, string>::const_iterator it = sendHeaders.find(name);
81 if (it == sendHeaders.end()) {
88 virtual void responseHeadersComplete()
92 virtual void responseComplete()
97 virtual void failure()
102 virtual void gotBodyData(const char* s, int n)
104 //std::cout << "got body data:'" << string(s, n) << "'" <<std::endl;
105 bodyData += string(s, n);
108 virtual std::string requestBodyType() const
110 return bodyContentType;
113 virtual void responseHeader(const string& header, const string& value)
115 headers[header] = value;
119 class TestServerChannel : public NetChat
133 setTerminator("\r\n");
137 virtual void collectIncomingData(const char* s, int n)
139 buffer += string(s, n);
142 virtual void foundTerminator(void)
144 if (state == STATE_IDLE) {
145 state = STATE_HEADERS;
146 string_list line = strutils::split(buffer, NULL, 3);
147 if (line.size() < 3) {
148 cerr << "malformed request:" << buffer << endl;
155 string::size_type queryPos = path.find('?');
156 if (queryPos != string::npos) {
157 parseArgs(path.substr(queryPos + 1));
158 path = path.substr(0, queryPos);
161 httpVersion = line[2];
162 requestHeaders.clear();
164 } else if (state == STATE_HEADERS) {
165 string s = strutils::simplify(buffer);
168 receivedRequestHeaders();
172 string::size_type colonPos = buffer.find(':');
173 if (colonPos == string::npos) {
174 cerr << "malformed HTTP response header:" << buffer << endl;
179 string key = strutils::simplify(buffer.substr(0, colonPos));
180 string value = strutils::strip(buffer.substr(colonPos + 1));
181 requestHeaders[key] = value;
183 } else if (state == STATE_REQUEST_BODY) {
185 setTerminator("\r\n");
186 } else if (state == STATE_CLOSING) {
191 void parseArgs(const string& argData)
193 string_list argv = strutils::split(argData, "&");
194 for (unsigned int a=0; a<argv.size(); ++a) {
195 string::size_type eqPos = argv[a].find('=');
196 if (eqPos == string::npos) {
197 cerr << "malformed HTTP argument:" << argv[a] << endl;
201 string key = argv[a].substr(0, eqPos);
202 string value = argv[a].substr(eqPos + 1);
207 void receivedRequestHeaders()
211 if (path == "/test1") {
212 string contentStr(BODY1);
214 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
215 d << "Content-Length:" << contentStr.size() << "\r\n";
216 d << "\r\n"; // final CRLF to terminate the headers
218 push(d.str().c_str());
219 } else if (path == "/testLorem") {
220 string contentStr(BODY3);
222 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
223 d << "Content-Length:" << contentStr.size() << "\r\n";
224 d << "\r\n"; // final CRLF to terminate the headers
226 push(d.str().c_str());
227 } else if (path == "/test_zero_length_content") {
230 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
231 d << "Content-Length:" << contentStr.size() << "\r\n";
232 d << "\r\n"; // final CRLF to terminate the headers
234 push(d.str().c_str());
235 } else if (path == "/test_headers") {
236 COMPARE(requestHeaders["X-Foo"], string("Bar"));
237 COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
239 string contentStr(BODY1);
241 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
242 d << "Content-Length:" << contentStr.size() << "\r\n";
243 d << "\r\n"; // final CRLF to terminate the headers
245 push(d.str().c_str());
246 } else if (path == "/test2") {
248 } else if (path == "/testchunked") {
250 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
251 d << "Transfer-Encoding:chunked\r\n";
253 d << "8\r\n"; // first chunk
255 d << "6\r\n"; // second chunk
257 d << "10\r\n"; // third chunk
258 d << "ABCDSTUVABCDSTUV\r\n";
259 d << "0\r\n"; // start of trailer
260 d << "X-Foobar: wibble\r\n"; // trailer data
262 push(d.str().c_str());
263 } else if (path == "http://www.google.com/test2") {
265 if (requestHeaders["Host"] != "www.google.com") {
266 sendErrorResponse(400, true, "bad destination");
269 if (requestHeaders["Proxy-Authorization"] != string()) {
270 sendErrorResponse(401, false, "bad auth"); // shouldn't supply auth
274 } else if (path == "http://www.google.com/test3") {
276 if (requestHeaders["Host"] != "www.google.com") {
277 sendErrorResponse(400, true, "bad destination");
280 if (requestHeaders["Proxy-Authorization"] != "ABCDEF") {
281 sendErrorResponse(401, false, "bad auth"); // forbidden
285 } else if (strutils::starts_with(path, "/test_1_0")) {
286 string contentStr(BODY1);
287 if (strutils::ends_with(path, "/B")) {
291 d << "HTTP/1.0 " << 200 << " " << reasonForCode(200) << "\r\n";
292 d << "\r\n"; // final CRLF to terminate the headers
294 push(d.str().c_str());
296 } else if (path == "/test_close") {
297 string contentStr(BODY1);
299 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
300 d << "Connection: close\r\n";
301 d << "\r\n"; // final CRLF to terminate the headers
303 push(d.str().c_str());
305 } else if (path == "/test_args") {
306 if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
307 sendErrorResponse(400, true, "bad arguments");
311 string contentStr(BODY1);
313 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
314 d << "Content-Length:" << contentStr.size() << "\r\n";
315 d << "\r\n"; // final CRLF to terminate the headers
317 push(d.str().c_str());
318 } else if (path == "/test_post") {
319 if (requestHeaders["Content-Type"] != "application/x-www-form-urlencoded") {
320 cerr << "bad content type: '" << requestHeaders["Content-Type"] << "'" << endl;
321 sendErrorResponse(400, true, "bad content type");
325 requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
326 setByteCount(requestContentLength);
327 state = STATE_REQUEST_BODY;
329 sendErrorResponse(404, false, "");
333 void closeAfterSending()
335 state = STATE_CLOSING;
342 if (method == "POST") {
346 if (path == "/test_post") {
347 if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
348 sendErrorResponse(400, true, "bad arguments");
353 d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
354 d << "\r\n"; // final CRLF to terminate the headers
355 push(d.str().c_str());
357 cerr << "sent 204 response ok" << endl;
364 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
365 d << "Content-Length:" << body2Size << "\r\n";
366 d << "\r\n"; // final CRLF to terminate the headers
367 push(d.str().c_str());
368 bufferSend(body2, body2Size);
371 void sendErrorResponse(int code, bool close, string content)
373 cerr << "sending error " << code << " for " << path << endl;
374 stringstream headerData;
375 headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n";
376 headerData << "Content-Length:" << content.size() << "\r\n";
377 headerData << "\r\n"; // final CRLF to terminate the headers
378 push(headerData.str().c_str());
379 push(content.c_str());
386 string reasonForCode(int code)
389 case 200: return "OK";
390 case 204: return "no content";
391 case 404: return "not found";
392 default: return "unknown code";
401 std::map<string, string> requestHeaders;
402 std::map<string, string> args;
403 int requestContentLength;
406 class TestServer : public NetChannel
412 bind(NULL, 2000); // localhost, any port
416 virtual ~TestServer()
420 virtual bool writable (void) { return false ; }
422 virtual void handleAccept (void)
424 simgear::IPAddress addr ;
425 int handle = accept ( &addr ) ;
426 //cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
427 TestServerChannel* chan = new TestServerChannel();
428 chan->setHandle(handle);
432 void waitForComplete(HTTP::Client* cl, TestRequest* tr)
434 SGTimeStamp start(SGTimeStamp::now());
435 while (start.elapsedMSec() < 1000) {
440 SGTimeStamp::sleepForMSec(1);
443 cerr << "timed out" << endl;
446 void waitForFailed(HTTP::Client* cl, TestRequest* tr)
448 SGTimeStamp start(SGTimeStamp::now());
449 while (start.elapsedMSec() < 1000) {
454 SGTimeStamp::sleepForMSec(1);
457 cerr << "timed out waiting for failure" << endl;
460 int main(int argc, char* argv[])
467 TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
468 COMPARE(tr1->scheme(), "http");
469 COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
470 COMPARE(tr1->host(), "localhost.woo.zar");
471 COMPARE(tr1->port(), 2000);
472 COMPARE(tr1->path(), "/test1");
474 TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
475 COMPARE(tr2->scheme(), "http");
476 COMPARE(tr2->hostAndPort(), "192.168.1.1");
477 COMPARE(tr2->host(), "192.168.1.1");
478 COMPARE(tr2->port(), 80);
479 COMPARE(tr2->path(), "/test1/dir/thing/file.png");
483 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
484 HTTP::Request_ptr own(tr);
487 waitForComplete(&cl, tr);
488 COMPARE(tr->responseCode(), 200);
489 COMPARE(tr->responseReason(), string("OK"));
490 COMPARE(tr->responseLength(), strlen(BODY1));
491 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
492 COMPARE(tr->bodyData, string(BODY1));
496 TestRequest* tr = new TestRequest("http://localhost:2000/testLorem");
497 HTTP::Request_ptr own(tr);
500 waitForComplete(&cl, tr);
501 COMPARE(tr->responseCode(), 200);
502 COMPARE(tr->responseReason(), string("OK"));
503 COMPARE(tr->responseLength(), strlen(BODY3));
504 COMPARE(tr->responseBytesReceived(), strlen(BODY3));
505 COMPARE(tr->bodyData, string(BODY3));
509 TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
510 HTTP::Request_ptr own(tr);
512 waitForComplete(&cl, tr);
513 COMPARE(tr->responseCode(), 200);
516 cerr << "done args" << endl;
519 TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
520 HTTP::Request_ptr own(tr);
521 tr->sendHeaders["X-Foo"] = "Bar";
522 tr->sendHeaders["X-AnotherHeader"] = "A longer value";
525 waitForComplete(&cl, tr);
526 COMPARE(tr->responseCode(), 200);
527 COMPARE(tr->responseReason(), string("OK"));
528 COMPARE(tr->responseLength(), strlen(BODY1));
529 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
530 COMPARE(tr->bodyData, string(BODY1));
533 // larger get request
534 for (unsigned int i=0; i<body2Size; ++i) {
535 body2[i] = (i << 4) | (i >> 2);
539 TestRequest* tr = new TestRequest("http://localhost:2000/test2");
540 HTTP::Request_ptr own(tr);
542 waitForComplete(&cl, tr);
543 COMPARE(tr->responseCode(), 200);
544 COMPARE(tr->responseBytesReceived(), body2Size);
545 COMPARE(tr->bodyData, string(body2, body2Size));
548 cerr << "testing chunked" << endl;
550 TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
551 HTTP::Request_ptr own(tr);
554 waitForComplete(&cl, tr);
555 COMPARE(tr->responseCode(), 200);
556 COMPARE(tr->responseReason(), string("OK"));
557 COMPARE(tr->responseBytesReceived(), 30);
558 COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
559 // check trailers made it too
560 COMPARE(tr->headers["x-foobar"], string("wibble"));
565 TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
566 HTTP::Request_ptr own(tr);
568 waitForComplete(&cl, tr);
569 COMPARE(tr->responseCode(), 404);
570 COMPARE(tr->responseReason(), string("not found"));
571 COMPARE(tr->responseLength(), 0);
574 cout << "done 404 test" << endl;
577 TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
578 HTTP::Request_ptr own(tr);
580 waitForComplete(&cl, tr);
581 COMPARE(tr->responseCode(), 200);
584 cout << "done1" << endl;
587 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
588 HTTP::Request_ptr own(tr);
590 waitForComplete(&cl, tr);
591 COMPARE(tr->responseCode(), 200);
592 COMPARE(tr->responseLength(), strlen(BODY1));
593 COMPARE(tr->bodyData, string(BODY1));
596 cout << "done2" << endl;
597 // test HTTP/1.1 Connection::close
599 TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
600 HTTP::Request_ptr own(tr);
602 waitForComplete(&cl, tr);
603 COMPARE(tr->responseCode(), 200);
604 COMPARE(tr->responseLength(), strlen(BODY1));
605 COMPARE(tr->bodyData, string(BODY1));
607 cout << "done3" << endl;
608 // test connectToHost failure
611 TestRequest* tr = new TestRequest("http://not.found/something");
612 HTTP::Request_ptr own(tr);
615 COMPARE(tr->responseCode(), -1);
620 cl.setProxy("localhost", 2000);
621 TestRequest* tr = new TestRequest("http://www.google.com/test2");
622 HTTP::Request_ptr own(tr);
624 waitForComplete(&cl, tr);
625 COMPARE(tr->responseCode(), 200);
626 COMPARE(tr->responseLength(), body2Size);
627 COMPARE(tr->bodyData, string(body2, body2Size));
631 cl.setProxy("localhost", 2000, "ABCDEF");
632 TestRequest* tr = new TestRequest("http://www.google.com/test3");
633 HTTP::Request_ptr own(tr);
635 waitForComplete(&cl, tr);
636 COMPARE(tr->responseCode(), 200);
637 COMPARE(tr->responseBytesReceived(), body2Size);
638 COMPARE(tr->bodyData, string(body2, body2Size));
642 cout << "testing HTTP 1.1 pipelineing" << endl;
646 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
647 HTTP::Request_ptr own(tr);
651 TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
652 HTTP::Request_ptr own2(tr2);
655 TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
656 HTTP::Request_ptr own3(tr3);
659 waitForComplete(&cl, tr3);
660 VERIFY(tr->complete);
661 VERIFY(tr2->complete);
662 COMPARE(tr->bodyData, string(BODY1));
664 COMPARE(tr2->responseLength(), strlen(BODY3));
665 COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
666 COMPARE(tr2->bodyData, string(BODY3));
668 COMPARE(tr3->bodyData, string(BODY1));
671 // multiple requests with an HTTP 1.0 server
673 cout << "http 1.0 multiple requests" << endl;
676 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
677 HTTP::Request_ptr own(tr);
680 TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
681 HTTP::Request_ptr own2(tr2);
684 TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
685 HTTP::Request_ptr own3(tr3);
688 waitForComplete(&cl, tr3);
689 VERIFY(tr->complete);
690 VERIFY(tr2->complete);
692 COMPARE(tr->responseLength(), strlen(BODY1));
693 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
694 COMPARE(tr->bodyData, string(BODY1));
696 COMPARE(tr2->responseLength(), strlen(BODY3));
697 COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
698 COMPARE(tr2->bodyData, string(BODY3));
699 COMPARE(tr3->bodyData, string(BODY1));
704 cout << "POST" << endl;
705 TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
706 tr->bodyContentType = "application/x-www-form-urlencoded";
708 HTTP::Request_ptr own(tr);
710 waitForComplete(&cl, tr);
711 COMPARE(tr->responseCode(), 204);
714 // test_zero_length_content
716 cout << "zero-length-content-response" << endl;
717 TestRequest* tr = new TestRequest("http://localhost:2000/test_zero_length_content");
718 HTTP::Request_ptr own(tr);
720 waitForComplete(&cl, tr);
721 COMPARE(tr->responseCode(), 200);
722 COMPARE(tr->bodyData, string());
723 COMPARE(tr->responseBytesReceived(), 0);
727 cout << "all tests passed ok" << endl;