]> git.mxchange.org Git - simgear.git/blob - simgear/io/test_HTTP.cxx
Revert "Support non-blocking address lookups, and switch to getaddrinfo over gethostb...
[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 #include <simgear/misc/sg_sleep.hxx>
16
17 using std::cout;
18 using std::cerr;
19 using std::endl;
20 using std::string;
21 using std::stringstream;
22
23 using namespace simgear;
24
25 const char* BODY1 = "The quick brown fox jumps over a lazy dog.";
26
27 const unsigned int body2Size = 8 * 1024;
28 char body2[body2Size];
29
30 #define COMPARE(a, b) \
31     if ((a) != (b))  { \
32         cerr << "failed:" << #a << " != " << #b << endl; \
33         cerr << "\tgot:" << a << endl; \
34         exit(1); \
35     }
36
37 #define VERIFY(a) \
38     if (!(a))  { \
39         cerr << "failed:" << #a << endl; \
40         exit(1); \
41     }
42     
43 class TestRequest : public HTTP::Request
44 {
45 public:
46     bool complete;
47     bool failed;
48     string bodyData;
49     
50     TestRequest(const std::string& url) : 
51         HTTP::Request(url),
52         complete(false)
53     {
54     }
55     
56     std::map<string, string> sendHeaders;
57     std::map<string, string> headers;
58 protected:
59     string_list requestHeaders() const
60     {
61         string_list r;
62         std::map<string, string>::const_iterator it;
63         for (it = sendHeaders.begin(); it != sendHeaders.end(); ++it) {
64             r.push_back(it->first);
65         }
66         return r;
67     }
68     
69     string header(const string& name) const
70     {
71         std::map<string, string>::const_iterator it = sendHeaders.find(name);
72         if (it == sendHeaders.end()) {
73             return string();
74         }
75         
76         return it->second;
77     }
78     
79     virtual void responseHeadersComplete()
80     {
81     }
82     
83     virtual void responseComplete()
84     {
85         complete = true;
86     }  
87     
88     virtual void failure()
89     {
90         failed = true;
91     }
92     
93     virtual void gotBodyData(const char* s, int n)
94     {
95         bodyData += string(s, n);
96     }
97     
98     virtual void responseHeader(const string& header, const string& value)
99     {
100         headers[header] =  value;
101     }
102 };
103
104 class TestServerChannel : public NetChat
105 {
106 public:  
107     enum State
108     {
109         STATE_IDLE = 0,
110         STATE_HEADERS,
111         STATE_REQUEST_BODY
112     };
113     
114     TestServerChannel()
115     {
116         state = STATE_IDLE;
117         setTerminator("\r\n");
118     }
119     
120     virtual void collectIncomingData(const char* s, int n)
121     {
122         buffer += string(s, n);
123     }
124     
125     virtual void foundTerminator(void)
126     {
127         if (state == STATE_IDLE) {
128             state = STATE_HEADERS;
129             string_list line = strutils::split(buffer, NULL, 3);
130             if (line.size() < 3) {
131                 cerr << "malformed request:" << buffer << endl;
132                 exit(-1);
133             }
134             
135             method = line[0];
136             path = line[1];
137             httpVersion = line[2];
138             requestHeaders.clear();
139             buffer.clear();
140         } else if (state == STATE_HEADERS) {
141             string s = strutils::simplify(buffer);
142             if (s.empty()) {
143                 buffer.clear();
144                 receivedRequestHeaders();
145                 return;
146             }
147             
148             int colonPos = buffer.find(':');
149             if (colonPos < 0) {
150                 cerr << "malformed HTTP response header:" << buffer << endl;
151                 buffer.clear();
152                 return;
153             }
154
155             string key = strutils::simplify(buffer.substr(0, colonPos));
156             string value = strutils::strip(buffer.substr(colonPos + 1));
157             requestHeaders[key] = value;
158             buffer.clear();
159         } else if (state == STATE_REQUEST_BODY) {
160             
161         }
162     }  
163     
164     void receivedRequestHeaders()
165     {
166         state = STATE_IDLE;
167         
168         if (path == "/test1") {
169             string contentStr(BODY1);
170             stringstream d;
171             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
172             d << "Content-Length:" << contentStr.size() << "\r\n";
173             d << "\r\n"; // final CRLF to terminate the headers
174             d << contentStr;
175             push(d.str().c_str());
176         } else if (path == "/test_headers") {
177             COMPARE(requestHeaders["X-Foo"], string("Bar"));
178             COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
179             
180             string contentStr(BODY1);
181             stringstream d;
182             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
183             d << "Content-Length:" << contentStr.size() << "\r\n";
184             d << "\r\n"; // final CRLF to terminate the headers
185             d << contentStr;
186             push(d.str().c_str());
187         } else if (path == "/test2") {
188             sendBody2();
189         } else if (path == "/testchunked") {
190             stringstream d;
191             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
192             d << "Transfer-Encoding:chunked\r\n";
193             d << "\r\n";
194             d << "8\r\n"; // first chunk
195             d << "ABCDEFGH\r\n";
196             d << "6\r\n"; // second chunk
197             d << "ABCDEF\r\n";
198             d << "10\r\n"; // third chunk
199             d << "ABCDSTUVABCDSTUV\r\n";
200             d << "0\r\n"; // start of trailer
201             d << "X-Foobar: wibble\r\n"; // trailer data
202             d << "\r\n";
203             push(d.str().c_str());
204         } else if (path == "http://www.google.com/test2") {
205             // proxy test
206             if (requestHeaders["Host"] != "www.google.com") {
207                 sendErrorResponse(400, true, "bad destination");
208             }
209             
210             if (requestHeaders["Proxy-Authorization"] != string()) {
211                 sendErrorResponse(401, false, "bad auth"); // shouldn't supply auth
212             }
213             
214             sendBody2();
215         } else if (path == "http://www.google.com/test3") {
216             // proxy test
217             if (requestHeaders["Host"] != "www.google.com") {
218                 sendErrorResponse(400, true, "bad destination");
219             }
220
221             if (requestHeaders["Proxy-Authorization"] != "ABCDEF") {
222                 sendErrorResponse(401, false, "bad auth"); // forbidden
223             }
224
225             sendBody2();
226         } else if (strutils::starts_with(path, "/test_1_0")) {
227             string contentStr(BODY1);
228             stringstream d;
229             d << "HTTP/1.0 " << 200 << " " << reasonForCode(200) << "\r\n";
230             d << "\r\n"; // final CRLF to terminate the headers
231             d << contentStr;
232             push(d.str().c_str());
233             closeWhenDone();
234         } else if (path == "/test_close") {
235             string contentStr(BODY1);
236             stringstream d;
237             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
238             d << "Connection: close\r\n";
239             d << "\r\n"; // final CRLF to terminate the headers
240             d << contentStr;
241             push(d.str().c_str());
242             closeWhenDone();
243         } else {
244             sendErrorResponse(404, true, "");
245         }
246     }
247     
248     void sendBody2()
249     {
250         stringstream d;
251         d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
252         d << "Content-Length:" << body2Size << "\r\n";
253         d << "\r\n"; // final CRLF to terminate the headers
254         push(d.str().c_str());
255         bufferSend(body2, body2Size);
256     }
257     
258     void sendErrorResponse(int code, bool close, string content)
259     {
260         cerr << "sending error " << code << " for " << path << endl;
261         stringstream headerData;
262         headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n";
263         headerData << "Content-Length:" << content.size() << "\r\n";
264         headerData << "\r\n"; // final CRLF to terminate the headers
265         push(headerData.str().c_str());
266         push(content.c_str());
267         
268         if (close) {
269             closeWhenDone();
270         }
271     }
272     
273     string reasonForCode(int code) 
274     {
275         switch (code) {
276             case 200: return "OK";
277             case 404: return "not found";
278             default: return "unknown code";
279         }
280     }
281     
282     State state;
283     string buffer;
284     string method;
285     string path;
286     string httpVersion;
287     std::map<string, string> requestHeaders;
288 };
289
290 class TestServer : public NetChannel
291 {
292 public:   
293     TestServer()
294     {
295         open();
296         bind(NULL, 2000); // localhost, any port
297         listen(5);
298     }
299     
300     virtual ~TestServer()
301     {    
302     }
303     
304     virtual bool writable (void) { return false ; }
305
306     virtual void handleAccept (void)
307     {
308         simgear::IPAddress addr ;
309         int handle = accept ( &addr ) ;
310         //cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
311         TestServerChannel* chan = new TestServerChannel();
312         chan->setHandle(handle);
313     }
314 };
315
316 void waitForComplete(HTTP::Client* cl, TestRequest* tr)
317 {
318     SGTimeStamp start(SGTimeStamp::now());
319     while (start.elapsedMSec() <  1000) {
320         cl->update();
321         if (tr->complete) {
322             return;
323         }
324         sleepForMSec(1);
325     }
326     
327     cerr << "timed out" << endl;
328 }
329
330 void waitForFailed(HTTP::Client* cl, TestRequest* tr)
331 {
332     SGTimeStamp start(SGTimeStamp::now());
333     while (start.elapsedMSec() <  1000) {
334         cl->update();
335         if (tr->failed) {
336             return;
337         }
338         sleepForMSec(1);
339     }
340     
341     cerr << "timed out waiting for failure" << endl;
342 }
343
344 int main(int argc, char* argv[])
345 {
346     TestServer s;
347     
348     HTTP::Client cl;
349
350 // test URL parsing
351     TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
352     COMPARE(tr1->scheme(), "http");
353     COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
354     COMPARE(tr1->host(), "localhost.woo.zar");
355     COMPARE(tr1->port(), 2000);
356     COMPARE(tr1->path(), "/test1");
357     
358     TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
359     COMPARE(tr2->scheme(), "http");
360     COMPARE(tr2->hostAndPort(), "192.168.1.1");
361     COMPARE(tr2->host(), "192.168.1.1");
362     COMPARE(tr2->port(), 80);
363     COMPARE(tr2->path(), "/test1/dir/thing/file.png");
364     
365 // basic get request
366     {
367         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
368         HTTP::Request_ptr own(tr);
369         cl.makeRequest(tr);
370
371         waitForComplete(&cl, tr);
372         COMPARE(tr->responseCode(), 200);
373         COMPARE(tr->responseReason(), string("OK"));
374         COMPARE(tr->responseLength(), strlen(BODY1));
375         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
376         COMPARE(tr->bodyData, string(BODY1));
377     }
378
379     {
380         TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
381         HTTP::Request_ptr own(tr);
382         tr->sendHeaders["X-Foo"] = "Bar";
383         tr->sendHeaders["X-AnotherHeader"] = "A longer value";
384         cl.makeRequest(tr);
385
386         waitForComplete(&cl, tr);
387         COMPARE(tr->responseCode(), 200);
388         COMPARE(tr->responseReason(), string("OK"));
389         COMPARE(tr->responseLength(), strlen(BODY1));
390         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
391         COMPARE(tr->bodyData, string(BODY1));
392     }
393     
394 // larger get request
395     for (unsigned int i=0; i<body2Size; ++i) {
396         body2[i] = (i << 4) | (i >> 2);
397     }
398     
399     {
400         TestRequest* tr = new TestRequest("http://localhost:2000/test2");
401         HTTP::Request_ptr own(tr);
402         cl.makeRequest(tr);
403         waitForComplete(&cl, tr);
404         COMPARE(tr->responseCode(), 200);
405         COMPARE(tr->responseBytesReceived(), body2Size);
406         COMPARE(tr->bodyData, string(body2, body2Size));
407     }
408     
409     {
410         TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
411         HTTP::Request_ptr own(tr);
412         cl.makeRequest(tr);
413
414         waitForComplete(&cl, tr);
415         COMPARE(tr->responseCode(), 200);
416         COMPARE(tr->responseReason(), string("OK"));
417         COMPARE(tr->responseBytesReceived(), 30);
418         COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
419     // check trailers made it too
420         COMPARE(tr->headers["x-foobar"], string("wibble"));
421     }
422     
423 // test 404
424     {
425         TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
426         HTTP::Request_ptr own(tr);
427         cl.makeRequest(tr);
428         waitForComplete(&cl, tr);
429         COMPARE(tr->responseCode(), 404);
430         COMPARE(tr->responseReason(), string("not found"));
431         COMPARE(tr->responseLength(), 0);
432     }
433
434     cout << "done1" << endl;
435 // test HTTP/1.0
436     {
437         TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
438         HTTP::Request_ptr own(tr);
439         cl.makeRequest(tr);
440         waitForComplete(&cl, tr);
441         COMPARE(tr->responseCode(), 200);
442         COMPARE(tr->responseLength(), strlen(BODY1));
443         COMPARE(tr->bodyData, string(BODY1));
444     }
445
446     cout << "done2" << endl;
447 // test HTTP/1.1 Connection::close
448     {
449         TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
450         HTTP::Request_ptr own(tr);
451         cl.makeRequest(tr);
452         waitForComplete(&cl, tr);
453         COMPARE(tr->responseCode(), 200);
454         COMPARE(tr->responseLength(), strlen(BODY1));
455         COMPARE(tr->bodyData, string(BODY1));
456     }
457     cout << "done3" << endl;
458 // test connectToHost failure
459 /*
460     {
461         TestRequest* tr = new TestRequest("http://not.found/something");
462         HTTP::Request_ptr own(tr);
463         cl.makeRequest(tr);
464         waitForFailed(tr);
465         COMPARE(tr->responseCode(), -1);
466     }
467     */
468 // test proxy
469     {
470         cl.setProxy("localhost", 2000);
471         TestRequest* tr = new TestRequest("http://www.google.com/test2");
472         HTTP::Request_ptr own(tr);
473         cl.makeRequest(tr);
474         waitForComplete(&cl, tr);
475         COMPARE(tr->responseCode(), 200);
476         COMPARE(tr->responseLength(), body2Size);
477         COMPARE(tr->bodyData, string(body2, body2Size));
478     }
479     
480     {
481         cl.setProxy("localhost", 2000, "ABCDEF");
482         TestRequest* tr = new TestRequest("http://www.google.com/test3");
483         HTTP::Request_ptr own(tr);
484         cl.makeRequest(tr);
485         waitForComplete(&cl, tr);
486         COMPARE(tr->responseCode(), 200);
487         COMPARE(tr->responseBytesReceived(), body2Size);
488         COMPARE(tr->bodyData, string(body2, body2Size));
489     }
490     
491 // pipelining
492     {
493         cl.setProxy("", 80);
494         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
495         HTTP::Request_ptr own(tr);
496         cl.makeRequest(tr);
497         
498         
499         TestRequest* tr2 = new TestRequest("http://localhost:2000/test1");
500         HTTP::Request_ptr own2(tr2);
501         cl.makeRequest(tr2);
502         
503         TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
504         HTTP::Request_ptr own3(tr3);
505         cl.makeRequest(tr3);
506         
507         waitForComplete(&cl, tr3);
508         VERIFY(tr->complete);
509         VERIFY(tr2->complete);
510         COMPARE(tr->bodyData, string(BODY1));
511         COMPARE(tr2->bodyData, string(BODY1));
512         COMPARE(tr3->bodyData, string(BODY1));
513     }
514     
515 // multiple requests with an HTTP 1.0 server
516     {
517         cout << "http 1.0 multiple requests" << endl;
518         
519         cl.setProxy("", 80);
520         TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
521         HTTP::Request_ptr own(tr);
522         cl.makeRequest(tr);
523         
524         TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
525         HTTP::Request_ptr own2(tr2);
526         cl.makeRequest(tr2);
527         
528         TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
529         HTTP::Request_ptr own3(tr3);
530         cl.makeRequest(tr3);
531         
532         waitForComplete(&cl, tr3);
533         VERIFY(tr->complete);
534         VERIFY(tr2->complete);
535         COMPARE(tr->bodyData, string(BODY1));
536         COMPARE(tr2->bodyData, string(BODY1));
537         COMPARE(tr3->bodyData, string(BODY1));
538     }
539     
540     cout << "all tests passed ok" << endl;
541     return EXIT_SUCCESS;
542 }