]> git.mxchange.org Git - simgear.git/blob - simgear/io/test_HTTP.cxx
Update HTTP code to support HTTP/1.0 responses, eg metarproxy
[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) : 
50         HTTP::Request(url),
51         complete(false)
52     {
53         
54     }
55     
56     std::map<string, string> headers;
57 protected:
58     virtual void responseHeadersComplete()
59     {
60     }
61     
62     virtual void responseComplete()
63     {
64         complete = true;
65     }  
66     
67     virtual void failure()
68     {
69         failed = true;
70     }
71     
72     virtual void gotBodyData(const char* s, int n)
73     {
74         bodyData += string(s, n);
75     }
76     
77     virtual void responseHeader(const string& header, const string& value)
78     {
79         headers[header] =  value;
80     }
81 };
82
83 class TestServerChannel : public NetChat
84 {
85 public:  
86     enum State
87     {
88         STATE_IDLE = 0,
89         STATE_HEADERS,
90         STATE_REQUEST_BODY
91     };
92     
93     TestServerChannel()
94     {
95         state = STATE_IDLE;
96         setTerminator("\r\n");
97     }
98     
99     virtual void collectIncomingData(const char* s, int n)
100     {
101         buffer += string(s, n);
102     }
103     
104     virtual void foundTerminator(void)
105     {
106         if (state == STATE_IDLE) {
107             state = STATE_HEADERS;
108             string_list line = strutils::split(buffer, NULL, 3);
109             if (line.size() < 3) {
110                 cerr << "malformed request:" << buffer << endl;
111                 exit(-1);
112             }
113             
114             method = line[0];
115             path = line[1];
116             httpVersion = line[2];
117             requestHeaders.clear();
118             buffer.clear();
119         } else if (state == STATE_HEADERS) {
120             string s = strutils::simplify(buffer);
121             if (s.empty()) {
122                 buffer.clear();
123                 receivedRequestHeaders();
124                 return;
125             }
126             
127             int colonPos = buffer.find(':');
128             if (colonPos < 0) {
129                 cerr << "malformed HTTP response header:" << buffer << endl;
130                 buffer.clear();
131                 return;
132             }
133
134             string key = strutils::simplify(buffer.substr(0, colonPos));
135             string lkey = boost::to_lower_copy(key);
136             string value = strutils::strip(buffer.substr(colonPos + 1));
137             requestHeaders[lkey] = value;
138             buffer.clear();
139         } else if (state == STATE_REQUEST_BODY) {
140             
141         }
142     }  
143     
144     void receivedRequestHeaders()
145     {
146         state = STATE_IDLE;
147         if (path == "/test1") {
148             string contentStr(BODY1);
149             stringstream d;
150             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
151             d << "Content-Length:" << contentStr.size() << "\r\n";
152             d << "\r\n"; // final CRLF to terminate the headers
153             d << contentStr;
154             push(d.str().c_str());
155         } else if (path == "/test2") {
156             sendBody2();
157         } else if (path == "/testchunked") {
158             stringstream d;
159             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
160             d << "Transfer-Encoding:chunked\r\n";
161             d << "\r\n";
162             d << "8\r\n"; // first chunk
163             d << "ABCDEFGH\r\n";
164             d << "6\r\n"; // second chunk
165             d << "ABCDEF\r\n";
166             d << "10\r\n"; // third chunk
167             d << "ABCDSTUVABCDSTUV\r\n";
168             d << "0\r\n"; // start of trailer
169             d << "X-Foobar: wibble\r\n"; // trailer data
170             d << "\r\n";
171             push(d.str().c_str());
172         } else if (path == "http://www.google.com/test2") {
173             // proxy test
174             if (requestHeaders["host"] != "www.google.com") {
175                 sendErrorResponse(400, true, "bad destination");
176             }
177             
178             if (requestHeaders["proxy-authorization"] != string()) {
179                 sendErrorResponse(401, false, "bad auth"); // shouldn't supply auth
180             }
181             
182             sendBody2();
183         } else if (path == "http://www.google.com/test3") {
184             // proxy test
185             if (requestHeaders["host"] != "www.google.com") {
186                 sendErrorResponse(400, true, "bad destination");
187             }
188
189             if (requestHeaders["proxy-authorization"] != "ABCDEF") {
190                 sendErrorResponse(401, false, "bad auth"); // forbidden
191             }
192
193             sendBody2();
194         } else if (path == "/test_1_0") {
195             string contentStr(BODY1);
196             stringstream d;
197             d << "HTTP/1.0 " << 200 << " " << reasonForCode(200) << "\r\n";
198             d << "\r\n"; // final CRLF to terminate the headers
199             d << contentStr;
200             push(d.str().c_str());
201             closeWhenDone();
202         } else if (path == "/test_close") {
203             string contentStr(BODY1);
204             stringstream d;
205             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
206             d << "Connection: close\r\n";
207             d << "\r\n"; // final CRLF to terminate the headers
208             d << contentStr;
209             push(d.str().c_str());
210             closeWhenDone();
211         } else {
212             sendErrorResponse(404, true, "");
213         }
214     }
215     
216     void sendBody2()
217     {
218         stringstream d;
219         d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
220         d << "Content-Length:" << body2Size << "\r\n";
221         d << "\r\n"; // final CRLF to terminate the headers
222         push(d.str().c_str());
223         bufferSend(body2, body2Size);
224     }
225     
226     void sendErrorResponse(int code, bool close, string content)
227     {
228         cerr << "sending error " << code << " for " << path << endl;
229         stringstream headerData;
230         headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n";
231         headerData << "Content-Length:" << content.size() << "\r\n";
232         headerData << "\r\n"; // final CRLF to terminate the headers
233         push(headerData.str().c_str());
234         push(content.c_str());
235         
236         if (close) {
237             closeWhenDone();
238         }
239     }
240     
241     string reasonForCode(int code) 
242     {
243         switch (code) {
244             case 200: return "OK";
245             case 404: return "not found";
246             default: return "unknown code";
247         }
248     }
249     
250     State state;
251     string buffer;
252     string method;
253     string path;
254     string httpVersion;
255     std::map<string, string> requestHeaders;
256 };
257
258 class TestServer : public NetChannel
259 {
260 public:   
261     TestServer()
262     {
263         open();
264         bind(NULL, 2000); // localhost, any port
265         listen(5);
266     }
267     
268     virtual ~TestServer()
269     {    
270     }
271     
272     virtual bool writable (void) { return false ; }
273
274     virtual void handleAccept (void)
275     {
276         simgear::IPAddress addr ;
277         int handle = accept ( &addr ) ;
278         //cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
279         TestServerChannel* chan = new TestServerChannel();
280         chan->setHandle(handle);
281     }
282 };
283
284 void waitForComplete(TestRequest* tr)
285 {
286     SGTimeStamp start(SGTimeStamp::now());
287     while (start.elapsedMSec() <  1000) {
288         NetChannel::poll(10);
289         if (tr->complete) {
290             return;
291         }
292     }
293     
294     cerr << "timed out" << endl;
295 }
296
297 void waitForFailed(TestRequest* tr)
298 {
299     SGTimeStamp start(SGTimeStamp::now());
300     while (start.elapsedMSec() <  1000) {
301         NetChannel::poll(10);
302         if (tr->failed) {
303             return;
304         }
305     }
306     
307     cerr << "timed out waiting for failure" << endl;
308 }
309
310 int main(int argc, char* argv[])
311 {
312     TestServer s;
313     
314     HTTP::Client cl;
315
316 // test URL parsing
317     TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
318     COMPARE(tr1->scheme(), "http");
319     COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
320     COMPARE(tr1->host(), "localhost.woo.zar");
321     COMPARE(tr1->port(), 2000);
322     COMPARE(tr1->path(), "/test1");
323     
324     TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
325     COMPARE(tr2->scheme(), "http");
326     COMPARE(tr2->hostAndPort(), "192.168.1.1");
327     COMPARE(tr2->host(), "192.168.1.1");
328     COMPARE(tr2->port(), 80);
329     COMPARE(tr2->path(), "/test1/dir/thing/file.png");
330     
331 // basic get request
332     {
333         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
334         HTTP::Request_ptr own(tr);
335         cl.makeRequest(tr);
336
337         waitForComplete(tr);
338         COMPARE(tr->responseCode(), 200);
339         COMPARE(tr->responseReason(), string("OK"));
340         COMPARE(tr->responseLength(), strlen(BODY1));
341         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
342         COMPARE(tr->bodyData, string(BODY1));
343     }
344
345 // larger get request
346     for (unsigned int i=0; i<body2Size; ++i) {
347         body2[i] = (i << 4) | (i >> 2);
348     }
349     
350     {
351         TestRequest* tr = new TestRequest("http://localhost:2000/test2");
352         HTTP::Request_ptr own(tr);
353         cl.makeRequest(tr);
354         waitForComplete(tr);
355         COMPARE(tr->responseCode(), 200);
356         COMPARE(tr->responseBytesReceived(), body2Size);
357         COMPARE(tr->bodyData, string(body2, body2Size));
358     }
359     
360     {
361         TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
362         HTTP::Request_ptr own(tr);
363         cl.makeRequest(tr);
364
365         waitForComplete(tr);
366         COMPARE(tr->responseCode(), 200);
367         COMPARE(tr->responseReason(), string("OK"));
368         COMPARE(tr->responseBytesReceived(), 30);
369         COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
370     // check trailers made it too
371         COMPARE(tr->headers["x-foobar"], string("wibble"));
372     }
373     
374 // test 404
375     {
376         TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
377         HTTP::Request_ptr own(tr);
378         cl.makeRequest(tr);
379         waitForComplete(tr);
380         COMPARE(tr->responseCode(), 404);
381         COMPARE(tr->responseReason(), string("not found"));
382         COMPARE(tr->responseLength(), 0);
383     }
384
385     cout << "done1" << endl;
386 // test HTTP/1.0
387     {
388         TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
389         HTTP::Request_ptr own(tr);
390         cl.makeRequest(tr);
391         waitForComplete(tr);
392         COMPARE(tr->responseCode(), 200);
393         COMPARE(tr->responseLength(), strlen(BODY1));
394         COMPARE(tr->bodyData, string(BODY1));
395     }
396
397     cout << "done2" << endl;
398 // test HTTP/1.1 Connection::close
399     {
400         TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
401         HTTP::Request_ptr own(tr);
402         cl.makeRequest(tr);
403         waitForComplete(tr);
404         COMPARE(tr->responseCode(), 200);
405         COMPARE(tr->responseLength(), strlen(BODY1));
406         COMPARE(tr->bodyData, string(BODY1));
407     }
408     cout << "done3" << endl;
409 // test connectToHost failure
410 /*
411     {
412         TestRequest* tr = new TestRequest("http://not.found/something");
413         HTTP::Request_ptr own(tr);
414         cl.makeRequest(tr);
415         waitForFailed(tr);
416         COMPARE(tr->responseCode(), -1);
417     }
418     */
419 // test proxy
420     {
421         cl.setProxy("localhost", 2000);
422         TestRequest* tr = new TestRequest("http://www.google.com/test2");
423         HTTP::Request_ptr own(tr);
424         cl.makeRequest(tr);
425         waitForComplete(tr);
426         COMPARE(tr->responseCode(), 200);
427         COMPARE(tr->responseLength(), body2Size);
428         COMPARE(tr->bodyData, string(body2, body2Size));
429     }
430     
431     {
432         cl.setProxy("localhost", 2000, "ABCDEF");
433         TestRequest* tr = new TestRequest("http://www.google.com/test3");
434         HTTP::Request_ptr own(tr);
435         cl.makeRequest(tr);
436         waitForComplete(tr);
437         COMPARE(tr->responseCode(), 200);
438         COMPARE(tr->responseBytesReceived(), body2Size);
439         COMPARE(tr->bodyData, string(body2, body2Size));
440     }
441     
442     cout << "all tests passed ok" << endl;
443     return EXIT_SUCCESS;
444 }