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.";
26 const unsigned int body2Size = 8 * 1024;
27 char body2[body2Size];
29 #define COMPARE(a, b) \
31 cerr << "failed:" << #a << " != " << #b << endl; \
32 cerr << "\tgot:" << a << endl; \
38 cerr << "failed:" << #a << endl; \
42 class TestRequest : public HTTP::Request
49 TestRequest(const std::string& url, const std::string method = "GET") :
50 HTTP::Request(url, method),
55 std::map<string, string> sendHeaders;
56 std::map<string, string> headers;
58 string_list requestHeaders() const
61 std::map<string, string>::const_iterator it;
62 for (it = sendHeaders.begin(); it != sendHeaders.end(); ++it) {
63 r.push_back(it->first);
68 string header(const string& name) const
70 std::map<string, string>::const_iterator it = sendHeaders.find(name);
71 if (it == sendHeaders.end()) {
78 virtual void responseHeadersComplete()
82 virtual void responseComplete()
87 virtual void failure()
92 virtual void gotBodyData(const char* s, int n)
94 bodyData += string(s, n);
97 virtual void responseHeader(const string& header, const string& value)
99 headers[header] = value;
103 class TestServerChannel : public NetChat
116 setTerminator("\r\n");
119 virtual void collectIncomingData(const char* s, int n)
121 buffer += string(s, n);
124 virtual void foundTerminator(void)
126 if (state == STATE_IDLE) {
127 state = STATE_HEADERS;
128 string_list line = strutils::split(buffer, NULL, 3);
129 if (line.size() < 3) {
130 cerr << "malformed request:" << buffer << endl;
137 string::size_type queryPos = path.find('?');
138 if (queryPos != string::npos) {
139 parseArgs(path.substr(queryPos + 1));
140 path = path.substr(0, queryPos);
143 httpVersion = line[2];
144 requestHeaders.clear();
146 } else if (state == STATE_HEADERS) {
147 string s = strutils::simplify(buffer);
150 receivedRequestHeaders();
154 string::size_type colonPos = buffer.find(':');
155 if (colonPos == string::npos) {
156 cerr << "malformed HTTP response header:" << buffer << endl;
161 string key = strutils::simplify(buffer.substr(0, colonPos));
162 string value = strutils::strip(buffer.substr(colonPos + 1));
163 requestHeaders[key] = value;
165 } else if (state == STATE_REQUEST_BODY) {
166 cerr << "done getting requst body";
168 setTerminator("\r\n");
172 void parseArgs(const string& argData)
174 string_list argv = strutils::split(argData, "&");
175 for (unsigned int a=0; a<argv.size(); ++a) {
176 string::size_type eqPos = argv[a].find('=');
177 if (eqPos == string::npos) {
178 cerr << "malformed HTTP argument:" << argv[a] << endl;
182 string key = argv[a].substr(0, eqPos);
183 string value = argv[a].substr(eqPos + 1);
188 void receivedRequestHeaders()
192 if (path == "/test1") {
193 string contentStr(BODY1);
195 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
196 d << "Content-Length:" << contentStr.size() << "\r\n";
197 d << "\r\n"; // final CRLF to terminate the headers
199 push(d.str().c_str());
200 } else if (path == "/test_zero_length_content") {
203 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
204 d << "Content-Length:" << contentStr.size() << "\r\n";
205 d << "\r\n"; // final CRLF to terminate the headers
207 push(d.str().c_str());
208 } else if (path == "/test_headers") {
209 COMPARE(requestHeaders["X-Foo"], string("Bar"));
210 COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
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 == "/test2") {
221 } else if (path == "/testchunked") {
223 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
224 d << "Transfer-Encoding:chunked\r\n";
226 d << "8\r\n"; // first chunk
228 d << "6\r\n"; // second chunk
230 d << "10\r\n"; // third chunk
231 d << "ABCDSTUVABCDSTUV\r\n";
232 d << "0\r\n"; // start of trailer
233 d << "X-Foobar: wibble\r\n"; // trailer data
235 push(d.str().c_str());
236 } else if (path == "http://www.google.com/test2") {
238 if (requestHeaders["Host"] != "www.google.com") {
239 sendErrorResponse(400, true, "bad destination");
242 if (requestHeaders["Proxy-Authorization"] != string()) {
243 sendErrorResponse(401, false, "bad auth"); // shouldn't supply auth
247 } else if (path == "http://www.google.com/test3") {
249 if (requestHeaders["Host"] != "www.google.com") {
250 sendErrorResponse(400, true, "bad destination");
253 if (requestHeaders["Proxy-Authorization"] != "ABCDEF") {
254 sendErrorResponse(401, false, "bad auth"); // forbidden
258 } else if (strutils::starts_with(path, "/test_1_0")) {
259 string contentStr(BODY1);
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, "");
306 if (method == "POST") {
310 if (path == "/test_post") {
311 if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
312 sendErrorResponse(400, true, "bad arguments");
317 d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
318 d << "\r\n"; // final CRLF to terminate the headers
319 push(d.str().c_str());
321 cerr << "sent 204 response ok" << endl;
328 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
329 d << "Content-Length:" << body2Size << "\r\n";
330 d << "\r\n"; // final CRLF to terminate the headers
331 push(d.str().c_str());
332 bufferSend(body2, body2Size);
335 void sendErrorResponse(int code, bool close, string content)
337 cerr << "sending error " << code << " for " << path << endl;
338 stringstream headerData;
339 headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n";
340 headerData << "Content-Length:" << content.size() << "\r\n";
341 headerData << "\r\n"; // final CRLF to terminate the headers
342 push(headerData.str().c_str());
343 push(content.c_str());
350 string reasonForCode(int code)
353 case 200: return "OK";
354 case 204: return "no content";
355 case 404: return "not found";
356 default: return "unknown code";
365 std::map<string, string> requestHeaders;
366 std::map<string, string> args;
367 int requestContentLength;
370 class TestServer : public NetChannel
376 bind(NULL, 2000); // localhost, any port
380 virtual ~TestServer()
384 virtual bool writable (void) { return false ; }
386 virtual void handleAccept (void)
388 simgear::IPAddress addr ;
389 int handle = accept ( &addr ) ;
390 //cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
391 TestServerChannel* chan = new TestServerChannel();
392 chan->setHandle(handle);
396 void waitForComplete(HTTP::Client* cl, TestRequest* tr)
398 SGTimeStamp start(SGTimeStamp::now());
399 while (start.elapsedMSec() < 1000) {
404 SGTimeStamp::sleepForMSec(1);
407 cerr << "timed out" << endl;
410 void waitForFailed(HTTP::Client* cl, TestRequest* tr)
412 SGTimeStamp start(SGTimeStamp::now());
413 while (start.elapsedMSec() < 1000) {
418 SGTimeStamp::sleepForMSec(1);
421 cerr << "timed out waiting for failure" << endl;
424 int main(int argc, char* argv[])
431 TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
432 COMPARE(tr1->scheme(), "http");
433 COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
434 COMPARE(tr1->host(), "localhost.woo.zar");
435 COMPARE(tr1->port(), 2000);
436 COMPARE(tr1->path(), "/test1");
438 TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
439 COMPARE(tr2->scheme(), "http");
440 COMPARE(tr2->hostAndPort(), "192.168.1.1");
441 COMPARE(tr2->host(), "192.168.1.1");
442 COMPARE(tr2->port(), 80);
443 COMPARE(tr2->path(), "/test1/dir/thing/file.png");
447 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
448 HTTP::Request_ptr own(tr);
451 waitForComplete(&cl, tr);
452 COMPARE(tr->responseCode(), 200);
453 COMPARE(tr->responseReason(), string("OK"));
454 COMPARE(tr->responseLength(), strlen(BODY1));
455 COMPARE(tr->responseBytesReceived(), strlen(BODY1));
456 COMPARE(tr->bodyData, string(BODY1));
460 TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
461 HTTP::Request_ptr own(tr);
463 waitForComplete(&cl, tr);
464 COMPARE(tr->responseCode(), 200);
467 cerr << "done args" << endl;
470 TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
471 HTTP::Request_ptr own(tr);
472 tr->sendHeaders["X-Foo"] = "Bar";
473 tr->sendHeaders["X-AnotherHeader"] = "A longer value";
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));
484 // larger get request
485 for (unsigned int i=0; i<body2Size; ++i) {
486 body2[i] = (i << 4) | (i >> 2);
490 TestRequest* tr = new TestRequest("http://localhost:2000/test2");
491 HTTP::Request_ptr own(tr);
493 waitForComplete(&cl, tr);
494 COMPARE(tr->responseCode(), 200);
495 COMPARE(tr->responseBytesReceived(), body2Size);
496 COMPARE(tr->bodyData, string(body2, body2Size));
499 cerr << "testing chunked" << endl;
501 TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
502 HTTP::Request_ptr own(tr);
505 waitForComplete(&cl, tr);
506 COMPARE(tr->responseCode(), 200);
507 COMPARE(tr->responseReason(), string("OK"));
508 COMPARE(tr->responseBytesReceived(), 30);
509 COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
510 // check trailers made it too
511 COMPARE(tr->headers["x-foobar"], string("wibble"));
516 TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
517 HTTP::Request_ptr own(tr);
519 waitForComplete(&cl, tr);
520 COMPARE(tr->responseCode(), 404);
521 COMPARE(tr->responseReason(), string("not found"));
522 COMPARE(tr->responseLength(), 0);
525 cout << "done 404 test" << endl;
528 TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
529 HTTP::Request_ptr own(tr);
531 waitForComplete(&cl, tr);
532 COMPARE(tr->responseCode(), 200);
535 cout << "done1" << endl;
538 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
539 HTTP::Request_ptr own(tr);
541 waitForComplete(&cl, tr);
542 COMPARE(tr->responseCode(), 200);
543 COMPARE(tr->responseLength(), strlen(BODY1));
544 COMPARE(tr->bodyData, string(BODY1));
547 cout << "done2" << endl;
548 // test HTTP/1.1 Connection::close
550 TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
551 HTTP::Request_ptr own(tr);
553 waitForComplete(&cl, tr);
554 COMPARE(tr->responseCode(), 200);
555 COMPARE(tr->responseLength(), strlen(BODY1));
556 COMPARE(tr->bodyData, string(BODY1));
558 cout << "done3" << endl;
559 // test connectToHost failure
562 TestRequest* tr = new TestRequest("http://not.found/something");
563 HTTP::Request_ptr own(tr);
566 COMPARE(tr->responseCode(), -1);
571 cl.setProxy("localhost", 2000);
572 TestRequest* tr = new TestRequest("http://www.google.com/test2");
573 HTTP::Request_ptr own(tr);
575 waitForComplete(&cl, tr);
576 COMPARE(tr->responseCode(), 200);
577 COMPARE(tr->responseLength(), body2Size);
578 COMPARE(tr->bodyData, string(body2, body2Size));
582 cl.setProxy("localhost", 2000, "ABCDEF");
583 TestRequest* tr = new TestRequest("http://www.google.com/test3");
584 HTTP::Request_ptr own(tr);
586 waitForComplete(&cl, tr);
587 COMPARE(tr->responseCode(), 200);
588 COMPARE(tr->responseBytesReceived(), body2Size);
589 COMPARE(tr->bodyData, string(body2, body2Size));
595 TestRequest* tr = new TestRequest("http://localhost:2000/test1");
596 HTTP::Request_ptr own(tr);
600 TestRequest* tr2 = new TestRequest("http://localhost:2000/test1");
601 HTTP::Request_ptr own2(tr2);
604 TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
605 HTTP::Request_ptr own3(tr3);
608 waitForComplete(&cl, tr3);
609 VERIFY(tr->complete);
610 VERIFY(tr2->complete);
611 COMPARE(tr->bodyData, string(BODY1));
612 COMPARE(tr2->bodyData, string(BODY1));
613 COMPARE(tr3->bodyData, string(BODY1));
616 // multiple requests with an HTTP 1.0 server
618 cout << "http 1.0 multiple requests" << endl;
621 TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
622 HTTP::Request_ptr own(tr);
625 TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
626 HTTP::Request_ptr own2(tr2);
629 TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
630 HTTP::Request_ptr own3(tr3);
633 waitForComplete(&cl, tr3);
634 VERIFY(tr->complete);
635 VERIFY(tr2->complete);
636 COMPARE(tr->bodyData, string(BODY1));
637 COMPARE(tr2->bodyData, string(BODY1));
638 COMPARE(tr3->bodyData, string(BODY1));
643 cout << "POST" << endl;
644 TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
645 HTTP::Request_ptr own(tr);
647 waitForComplete(&cl, tr);
648 COMPARE(tr->responseCode(), 204);
651 // test_zero_length_content
653 cout << "zero-length-content-response" << endl;
654 TestRequest* tr = new TestRequest("http://localhost:2000/test_zero_length_content");
655 HTTP::Request_ptr own(tr);
657 waitForComplete(&cl, tr);
658 COMPARE(tr->responseCode(), 200);
659 COMPARE(tr->bodyData, string());
660 COMPARE(tr->responseBytesReceived(), 0);
664 cout << "all tests passed ok" << endl;