]> git.mxchange.org Git - simgear.git/blob - simgear/io/test_HTTP.cxx
Further HTTP improvements, correct proxy support, spec compliance, support for chunke...
[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() < 4) {
110                 cerr << "malformed request:" << buffer << endl;
111                 exit(-1);
112             }
113             
114             method = line[0];
115             path = line[1];
116             httpVersion = line[2];
117             userAgent = line[3];
118             requestHeaders.clear();
119             buffer.clear();
120         } else if (state == STATE_HEADERS) {
121             string s = strutils::simplify(buffer);
122             if (s.empty()) {
123                 buffer.clear();
124                 receivedRequestHeaders();
125                 return;
126             }
127             
128             int colonPos = buffer.find(':');
129             if (colonPos < 0) {
130                 cerr << "malformed HTTP response header:" << buffer << endl;
131                 buffer.clear();
132                 return;
133             }
134
135             string key = strutils::simplify(buffer.substr(0, colonPos));
136             string lkey = boost::to_lower_copy(key);
137             string value = strutils::strip(buffer.substr(colonPos + 1));
138             requestHeaders[lkey] = value;
139             buffer.clear();
140         } else if (state == STATE_REQUEST_BODY) {
141             
142         }
143     }  
144     
145     void receivedRequestHeaders()
146     {
147         state = STATE_IDLE;
148         if (path == "/test1") {
149             string contentStr(BODY1);
150             stringstream d;
151             d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
152             d << "Content-Length:" << contentStr.size() << "\r\n";
153             d << "\r\n"; // final CRLF to terminate the headers
154             d << contentStr;
155             push(d.str().c_str());
156         } else if (path == "/test2") {
157             sendBody2();
158         } else if (path == "/testchunked") {
159             stringstream d;
160             d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
161             d << "Transfer-Encoding:chunked\r\n";
162             d << "\r\n";
163             d << "8\r\n"; // first chunk
164             d << "ABCDEFGH\r\n";
165             d << "6\r\n"; // second chunk
166             d << "ABCDEF\r\n";
167             d << "10\r\n"; // third chunk
168             d << "ABCDSTUVABCDSTUV\r\n";
169             d << "0\r\n"; // start of trailer
170             d << "X-Foobar: wibble\r\n"; // trailer data
171             d << "\r\n";
172             push(d.str().c_str());
173         } else if (path == "http://www.google.com/test2") {
174             // proxy test
175             if (requestHeaders["host"] != "www.google.com") {
176                 sendErrorResponse(400);
177             }
178             
179             if (requestHeaders["proxy-authorization"] != string()) {
180                 sendErrorResponse(401); // shouldn't supply auth
181             }
182             
183             sendBody2();
184         } else if (path == "http://www.google.com/test3") {
185             // proxy test
186             if (requestHeaders["host"] != "www.google.com") {
187                 sendErrorResponse(400);
188             }
189
190             if (requestHeaders["proxy-authorization"] != "ABCDEF") {
191                 sendErrorResponse(401); // forbidden
192             }
193
194             sendBody2();
195         } else {
196             sendErrorResponse(404);
197         }
198     }
199     
200     void sendBody2()
201     {
202         stringstream d;
203         d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
204         d << "Content-Length:" << body2Size << "\r\n";
205         d << "\r\n"; // final CRLF to terminate the headers
206         push(d.str().c_str());
207         bufferSend(body2, body2Size);
208     }
209     
210     void sendErrorResponse(int code)
211     {
212         cerr << "sending error " << code << " for " << path << endl;
213         stringstream headerData;
214         headerData << "HTTP1.1 " << code << " " << reasonForCode(code) << "\r\n";
215         headerData << "\r\n"; // final CRLF to terminate the headers
216         push(headerData.str().c_str());
217     }
218     
219     string reasonForCode(int code) 
220     {
221         switch (code) {
222             case 200: return "OK";
223             case 404: return "not found";
224             default: return "unknown code";
225         }
226     }
227     
228     State state;
229     string buffer;
230     string method;
231     string path;
232     string httpVersion;
233     string userAgent;
234     std::map<string, string> requestHeaders;
235 };
236
237 class TestServer : public NetChannel
238 {
239 public:   
240     TestServer()
241     {
242         open();
243         bind(NULL, 2000); // localhost, any port
244         listen(5);
245     }
246     
247     virtual ~TestServer()
248     {    
249     }
250     
251     virtual bool writable (void) { return false ; }
252
253     virtual void handleAccept (void)
254     {
255         simgear::IPAddress addr ;
256         int handle = accept ( &addr ) ;
257         cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
258         TestServerChannel* chan = new TestServerChannel();
259         chan->setHandle(handle);
260     }
261 };
262
263 void waitForComplete(TestRequest* tr)
264 {
265     SGTimeStamp start(SGTimeStamp::now());
266     while (start.elapsedMSec() <  1000) {
267         NetChannel::poll(10);
268         if (tr->complete) {
269             return;
270         }
271     }
272     
273     cerr << "timed out" << endl;
274 }
275
276 void waitForFailed(TestRequest* tr)
277 {
278     SGTimeStamp start(SGTimeStamp::now());
279     while (start.elapsedMSec() <  1000) {
280         NetChannel::poll(10);
281         if (tr->failed) {
282             return;
283         }
284     }
285     
286     cerr << "timed out waiting for failure" << endl;
287 }
288
289 int main(int argc, char* argv[])
290 {
291     TestServer s;
292     
293     HTTP::Client cl;
294
295 // test URL parsing
296     TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
297     COMPARE(tr1->scheme(), "http");
298     COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
299     COMPARE(tr1->host(), "localhost.woo.zar");
300     COMPARE(tr1->port(), 2000);
301     COMPARE(tr1->path(), "/test1");
302     
303     TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
304     COMPARE(tr2->scheme(), "http");
305     COMPARE(tr2->hostAndPort(), "192.168.1.1");
306     COMPARE(tr2->host(), "192.168.1.1");
307     COMPARE(tr2->port(), 80);
308     COMPARE(tr2->path(), "/test1/dir/thing/file.png");
309     
310 // basic get request
311     {
312         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
313         HTTP::Request_ptr own(tr);
314         cl.makeRequest(tr);
315
316         waitForComplete(tr);
317         COMPARE(tr->responseCode(), 200);
318         COMPARE(tr->responseReason(), string("OK"));
319         COMPARE(tr->responseLength(), strlen(BODY1));
320         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
321         COMPARE(tr->bodyData, string(BODY1));
322     }
323
324 // larger get request
325     for (unsigned int i=0; i<body2Size; ++i) {
326         body2[i] = (i << 4) | (i >> 2);
327     }
328     
329     {
330         TestRequest* tr = new TestRequest("http://localhost:2000/test2");
331         HTTP::Request_ptr own(tr);
332         cl.makeRequest(tr);
333         waitForComplete(tr);
334         COMPARE(tr->responseCode(), 200);
335         COMPARE(tr->responseBytesReceived(), body2Size);
336         COMPARE(tr->bodyData, string(body2, body2Size));
337     }
338     
339     {
340         TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
341         HTTP::Request_ptr own(tr);
342         cl.makeRequest(tr);
343
344         waitForComplete(tr);
345         COMPARE(tr->responseCode(), 200);
346         COMPARE(tr->responseReason(), string("OK"));
347         COMPARE(tr->responseBytesReceived(), 30);
348         COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
349     // check trailers made it too
350         COMPARE(tr->headers["x-foobar"], string("wibble"));
351     }
352     
353 // test 404
354     {
355         TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
356         HTTP::Request_ptr own(tr);
357         cl.makeRequest(tr);
358         waitForComplete(tr);
359         COMPARE(tr->responseCode(), 404);
360         COMPARE(tr->responseReason(), string("not found"));
361         COMPARE(tr->responseLength(), 0);
362     }
363
364 // test connectToHost failure
365 /*
366     {
367         TestRequest* tr = new TestRequest("http://not.found/something");
368         HTTP::Request_ptr own(tr);
369         cl.makeRequest(tr);
370         waitForFailed(tr);
371         COMPARE(tr->responseCode(), -1);
372     }
373     */
374 // test proxy
375     {
376         cl.setProxy("localhost", 2000);
377         TestRequest* tr = new TestRequest("http://www.google.com/test2");
378         HTTP::Request_ptr own(tr);
379         cl.makeRequest(tr);
380         waitForComplete(tr);
381         COMPARE(tr->responseCode(), 200);
382         COMPARE(tr->responseLength(), body2Size);
383         COMPARE(tr->bodyData, string(body2, body2Size));
384     }
385     
386     {
387         cl.setProxy("localhost", 2000, "ABCDEF");
388         TestRequest* tr = new TestRequest("http://www.google.com/test3");
389         HTTP::Request_ptr own(tr);
390         cl.makeRequest(tr);
391         waitForComplete(tr);
392         COMPARE(tr->responseCode(), 200);
393         COMPARE(tr->responseBytesReceived(), body2Size);
394         COMPARE(tr->bodyData, string(body2, body2Size));
395     }
396     
397     cout << "all tests passed ok" << endl;
398     return EXIT_SUCCESS;
399 }