]> git.mxchange.org Git - simgear.git/blob - simgear/io/test_HTTP.cxx
Fix signed vs unsigned compiler warnings
[simgear.git] / simgear / io / test_HTTP.cxx
1 #include <cstdlib>
2
3 #include <iostream>
4 #include <map>
5 #include <sstream>
6
7 #include <boost/algorithm/string/case_conv.hpp>
8
9 #include "HTTPClient.hxx"
10 #include "HTTPRequest.hxx"
11
12 #include <simgear/io/sg_netChat.hxx>
13 #include <simgear/misc/strutils.hxx>
14 #include <simgear/timing/timestamp.hxx>
15
16 using std::cout;
17 using std::cerr;
18 using std::endl;
19 using std::string;
20 using std::stringstream;
21
22 using namespace simgear;
23
24 const char* BODY1 = "The quick brown fox jumps over a lazy dog.";
25
26 const unsigned int body2Size = 8 * 1024;
27 char body2[body2Size];
28
29 #define COMPARE(a, b) \
30     if ((a) != (b))  { \
31         cerr << "failed:" << #a << " != " << #b << endl; \
32         cerr << "\tgot:" << a << endl; \
33         exit(1); \
34     }
35
36 #define VERIFY(a) \
37     if (!(a))  { \
38         cerr << "failed:" << #a << endl; \
39         exit(1); \
40     }
41     
42 class TestRequest : public HTTP::Request
43 {
44 public:
45     bool complete;
46     bool failed;
47     string bodyData;
48     
49     TestRequest(const std::string& url, const std::string method = "GET") : 
50         HTTP::Request(url, method),
51         complete(false)
52     {
53     }
54     
55     std::map<string, string> sendHeaders;
56     std::map<string, string> headers;
57 protected:
58     string_list requestHeaders() const
59     {
60         string_list r;
61         std::map<string, string>::const_iterator it;
62         for (it = sendHeaders.begin(); it != sendHeaders.end(); ++it) {
63             r.push_back(it->first);
64         }
65         return r;
66     }
67     
68     string header(const string& name) const
69     {
70         std::map<string, string>::const_iterator it = sendHeaders.find(name);
71         if (it == sendHeaders.end()) {
72             return string();
73         }
74         
75         return it->second;
76     }
77     
78     virtual void responseHeadersComplete()
79     {
80     }
81     
82     virtual void responseComplete()
83     {
84         complete = true;
85     }  
86     
87     virtual void failure()
88     {
89         failed = true;
90     }
91     
92     virtual void gotBodyData(const char* s, int n)
93     {
94         bodyData += string(s, n);
95     }
96     
97     virtual void responseHeader(const string& header, const string& value)
98     {
99         headers[header] =  value;
100     }
101 };
102
103 class TestServerChannel : public NetChat
104 {
105 public:  
106     enum State
107     {
108         STATE_IDLE = 0,
109         STATE_HEADERS,
110         STATE_REQUEST_BODY
111     };
112     
113     TestServerChannel()
114     {
115         state = STATE_IDLE;
116         setTerminator("\r\n");
117     }
118     
119     virtual void collectIncomingData(const char* s, int n)
120     {
121         buffer += string(s, n);
122     }
123     
124     virtual void foundTerminator(void)
125     {
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;
131                 exit(-1);
132             }
133             
134             method = line[0];
135             path = line[1];
136             
137             string::size_type queryPos = path.find('?');
138             if (queryPos != string::npos) {
139                 parseArgs(path.substr(queryPos + 1));
140                 path = path.substr(0, queryPos);
141             }
142             
143             httpVersion = line[2];
144             requestHeaders.clear();
145             buffer.clear();
146         } else if (state == STATE_HEADERS) {
147             string s = strutils::simplify(buffer);
148             if (s.empty()) {
149                 buffer.clear();
150                 receivedRequestHeaders();
151                 return;
152             }
153             
154             string::size_type colonPos = buffer.find(':');
155             if (colonPos == string::npos) {
156                 cerr << "malformed HTTP response header:" << buffer << endl;
157                 buffer.clear();
158                 return;
159             }
160
161             string key = strutils::simplify(buffer.substr(0, colonPos));
162             string value = strutils::strip(buffer.substr(colonPos + 1));
163             requestHeaders[key] = value;
164             buffer.clear();
165         } else if (state == STATE_REQUEST_BODY) {
166             cerr << "done getting requst body";
167             receivedBody();
168             setTerminator("\r\n");
169         }
170     }  
171     
172     void parseArgs(const string& argData)
173     {
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;
179                 continue;
180             }
181
182             string key = argv[a].substr(0, eqPos);
183             string value = argv[a].substr(eqPos + 1);
184             args[key] = value;
185         }
186     }
187     
188     void receivedRequestHeaders()
189     {
190         state = STATE_IDLE;
191         
192         if (path == "/test1") {
193             string contentStr(BODY1);
194             stringstream d;
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
198             d << contentStr;
199             push(d.str().c_str());
200         } else if (path == "/test_zero_length_content") {
201             string contentStr;
202             stringstream d;
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
206             d << contentStr;
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"));
211             
212             string contentStr(BODY1);
213             stringstream d;
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
217             d << contentStr;
218             push(d.str().c_str());
219         } else if (path == "/test2") {
220             sendBody2();
221         } else if (path == "/testchunked") {
222             stringstream d;
223             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
224             d << "Transfer-Encoding:chunked\r\n";
225             d << "\r\n";
226             d << "8\r\n"; // first chunk
227             d << "ABCDEFGH\r\n";
228             d << "6\r\n"; // second chunk
229             d << "ABCDEF\r\n";
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
234             d << "\r\n";
235             push(d.str().c_str());
236         } else if (path == "http://www.google.com/test2") {
237             // proxy test
238             if (requestHeaders["Host"] != "www.google.com") {
239                 sendErrorResponse(400, true, "bad destination");
240             }
241             
242             if (requestHeaders["Proxy-Authorization"] != string()) {
243                 sendErrorResponse(401, false, "bad auth"); // shouldn't supply auth
244             }
245             
246             sendBody2();
247         } else if (path == "http://www.google.com/test3") {
248             // proxy test
249             if (requestHeaders["Host"] != "www.google.com") {
250                 sendErrorResponse(400, true, "bad destination");
251             }
252
253             if (requestHeaders["Proxy-Authorization"] != "ABCDEF") {
254                 sendErrorResponse(401, false, "bad auth"); // forbidden
255             }
256
257             sendBody2();
258         } else if (strutils::starts_with(path, "/test_1_0")) {
259             string contentStr(BODY1);
260             stringstream d;
261             d << "HTTP/1.0 " << 200 << " " << reasonForCode(200) << "\r\n";
262             d << "\r\n"; // final CRLF to terminate the headers
263             d << contentStr;
264             push(d.str().c_str());
265             closeWhenDone();
266         } else if (path == "/test_close") {
267             string contentStr(BODY1);
268             stringstream d;
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
272             d << contentStr;
273             push(d.str().c_str());
274             closeWhenDone();
275         } else if (path == "/test_args") {
276             if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
277                 sendErrorResponse(400, true, "bad arguments");
278                 return;
279             }
280
281             string contentStr(BODY1);
282             stringstream d;
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
286             d << contentStr;
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");
292                  return;
293             }
294             
295             requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
296             setByteCount(requestContentLength);
297             state = STATE_REQUEST_BODY;
298         } else {
299             sendErrorResponse(404, false, "");
300         }
301     }
302     
303     void receivedBody()
304     {
305         state = STATE_IDLE;
306         if (method == "POST") {
307             parseArgs(buffer);
308         }
309         
310         if (path == "/test_post") {
311             if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
312                 sendErrorResponse(400, true, "bad arguments");
313                 return;
314             }
315             
316             stringstream d;
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());
320             
321             cerr << "sent 204 response ok" << endl;
322         }
323     }
324     
325     void sendBody2()
326     {
327         stringstream d;
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);
333     }
334     
335     void sendErrorResponse(int code, bool close, string content)
336     {
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());
344         
345         if (close) {
346             closeWhenDone();
347         }
348     }
349     
350     string reasonForCode(int code) 
351     {
352         switch (code) {
353             case 200: return "OK";
354             case 204: return "no content";
355             case 404: return "not found";
356             default: return "unknown code";
357         }
358     }
359     
360     State state;
361     string buffer;
362     string method;
363     string path;
364     string httpVersion;
365     std::map<string, string> requestHeaders;
366     std::map<string, string> args;
367     int requestContentLength;
368 };
369
370 class TestServer : public NetChannel
371 {
372 public:   
373     TestServer()
374     {
375         open();
376         bind(NULL, 2000); // localhost, any port
377         listen(5);
378     }
379     
380     virtual ~TestServer()
381     {    
382     }
383     
384     virtual bool writable (void) { return false ; }
385
386     virtual void handleAccept (void)
387     {
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);
393     }
394 };
395
396 void waitForComplete(HTTP::Client* cl, TestRequest* tr)
397 {
398     SGTimeStamp start(SGTimeStamp::now());
399     while (start.elapsedMSec() <  1000) {
400         cl->update();
401         if (tr->complete) {
402             return;
403         }
404         SGTimeStamp::sleepForMSec(1);
405     }
406     
407     cerr << "timed out" << endl;
408 }
409
410 void waitForFailed(HTTP::Client* cl, TestRequest* tr)
411 {
412     SGTimeStamp start(SGTimeStamp::now());
413     while (start.elapsedMSec() <  1000) {
414         cl->update();
415         if (tr->failed) {
416             return;
417         }
418         SGTimeStamp::sleepForMSec(1);
419     }
420     
421     cerr << "timed out waiting for failure" << endl;
422 }
423
424 int main(int argc, char* argv[])
425 {
426     TestServer s;
427     
428     HTTP::Client cl;
429
430 // test URL parsing
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");
437     
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");
444     
445 // basic get request
446     {
447         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
448         HTTP::Request_ptr own(tr);
449         cl.makeRequest(tr);
450
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));
457     }
458     
459     {
460         TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
461         HTTP::Request_ptr own(tr);
462         cl.makeRequest(tr);
463         waitForComplete(&cl, tr);
464         COMPARE(tr->responseCode(), 200);
465     }
466     
467     cerr << "done args" << endl;
468     
469     {
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";
474         cl.makeRequest(tr);
475
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));
482     }
483     
484 // larger get request
485     for (unsigned int i=0; i<body2Size; ++i) {
486         body2[i] = (i << 4) | (i >> 2);
487     }
488     
489     {
490         TestRequest* tr = new TestRequest("http://localhost:2000/test2");
491         HTTP::Request_ptr own(tr);
492         cl.makeRequest(tr);
493         waitForComplete(&cl, tr);
494         COMPARE(tr->responseCode(), 200);
495         COMPARE(tr->responseBytesReceived(), body2Size);
496         COMPARE(tr->bodyData, string(body2, body2Size));
497     }
498     
499     cerr << "testing chunked" << endl;
500     {
501         TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
502         HTTP::Request_ptr own(tr);
503         cl.makeRequest(tr);
504
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"));
512     }
513     
514 // test 404
515     {
516         TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
517         HTTP::Request_ptr own(tr);
518         cl.makeRequest(tr);
519         waitForComplete(&cl, tr);
520         COMPARE(tr->responseCode(), 404);
521         COMPARE(tr->responseReason(), string("not found"));
522         COMPARE(tr->responseLength(), 0);
523     }
524
525     cout << "done 404 test" << endl;
526
527     {
528         TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
529         HTTP::Request_ptr own(tr);
530         cl.makeRequest(tr);
531         waitForComplete(&cl, tr);
532         COMPARE(tr->responseCode(), 200);
533     }
534
535     cout << "done1" << endl;
536 // test HTTP/1.0
537     {
538         TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
539         HTTP::Request_ptr own(tr);
540         cl.makeRequest(tr);
541         waitForComplete(&cl, tr);
542         COMPARE(tr->responseCode(), 200);
543         COMPARE(tr->responseLength(), strlen(BODY1));
544         COMPARE(tr->bodyData, string(BODY1));
545     }
546
547     cout << "done2" << endl;
548 // test HTTP/1.1 Connection::close
549     {
550         TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
551         HTTP::Request_ptr own(tr);
552         cl.makeRequest(tr);
553         waitForComplete(&cl, tr);
554         COMPARE(tr->responseCode(), 200);
555         COMPARE(tr->responseLength(), strlen(BODY1));
556         COMPARE(tr->bodyData, string(BODY1));
557     }
558     cout << "done3" << endl;
559 // test connectToHost failure
560 /*
561     {
562         TestRequest* tr = new TestRequest("http://not.found/something");
563         HTTP::Request_ptr own(tr);
564         cl.makeRequest(tr);
565         waitForFailed(tr);
566         COMPARE(tr->responseCode(), -1);
567     }
568     */
569 // test proxy
570     {
571         cl.setProxy("localhost", 2000);
572         TestRequest* tr = new TestRequest("http://www.google.com/test2");
573         HTTP::Request_ptr own(tr);
574         cl.makeRequest(tr);
575         waitForComplete(&cl, tr);
576         COMPARE(tr->responseCode(), 200);
577         COMPARE(tr->responseLength(), body2Size);
578         COMPARE(tr->bodyData, string(body2, body2Size));
579     }
580     
581     {
582         cl.setProxy("localhost", 2000, "ABCDEF");
583         TestRequest* tr = new TestRequest("http://www.google.com/test3");
584         HTTP::Request_ptr own(tr);
585         cl.makeRequest(tr);
586         waitForComplete(&cl, tr);
587         COMPARE(tr->responseCode(), 200);
588         COMPARE(tr->responseBytesReceived(), body2Size);
589         COMPARE(tr->bodyData, string(body2, body2Size));
590     }
591     
592 // pipelining
593     {
594         cl.setProxy("", 80);
595         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
596         HTTP::Request_ptr own(tr);
597         cl.makeRequest(tr);
598         
599         
600         TestRequest* tr2 = new TestRequest("http://localhost:2000/test1");
601         HTTP::Request_ptr own2(tr2);
602         cl.makeRequest(tr2);
603         
604         TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
605         HTTP::Request_ptr own3(tr3);
606         cl.makeRequest(tr3);
607         
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));
614     }
615     
616 // multiple requests with an HTTP 1.0 server
617     {
618         cout << "http 1.0 multiple requests" << endl;
619         
620         cl.setProxy("", 80);
621         TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
622         HTTP::Request_ptr own(tr);
623         cl.makeRequest(tr);
624         
625         TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
626         HTTP::Request_ptr own2(tr2);
627         cl.makeRequest(tr2);
628         
629         TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
630         HTTP::Request_ptr own3(tr3);
631         cl.makeRequest(tr3);
632         
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));
639     }
640     
641 // POST
642     {
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);
646         cl.makeRequest(tr);
647         waitForComplete(&cl, tr);
648         COMPARE(tr->responseCode(), 204);
649     }
650     
651     // test_zero_length_content
652     {
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);
656         cl.makeRequest(tr);
657         waitForComplete(&cl, tr);
658         COMPARE(tr->responseCode(), 200);
659         COMPARE(tr->bodyData, string());
660         COMPARE(tr->responseBytesReceived(), 0);
661     }
662     
663     
664     cout << "all tests passed ok" << endl;
665     return EXIT_SUCCESS;
666 }