]> git.mxchange.org Git - simgear.git/blob - simgear/io/test_HTTP.cxx
Whoops, fix tests for user-agent being HTTP/1.1 spec
[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 << "HTTP1.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 << "HTTP1.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);
176             }
177             
178             if (requestHeaders["proxy-authorization"] != string()) {
179                 sendErrorResponse(401); // 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);
187             }
188
189             if (requestHeaders["proxy-authorization"] != "ABCDEF") {
190                 sendErrorResponse(401); // forbidden
191             }
192
193             sendBody2();
194         } else {
195             sendErrorResponse(404);
196         }
197     }
198     
199     void sendBody2()
200     {
201         stringstream d;
202         d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
203         d << "Content-Length:" << body2Size << "\r\n";
204         d << "\r\n"; // final CRLF to terminate the headers
205         push(d.str().c_str());
206         bufferSend(body2, body2Size);
207     }
208     
209     void sendErrorResponse(int code)
210     {
211         cerr << "sending error " << code << " for " << path << endl;
212         stringstream headerData;
213         headerData << "HTTP1.1 " << code << " " << reasonForCode(code) << "\r\n";
214         headerData << "\r\n"; // final CRLF to terminate the headers
215         push(headerData.str().c_str());
216     }
217     
218     string reasonForCode(int code) 
219     {
220         switch (code) {
221             case 200: return "OK";
222             case 404: return "not found";
223             default: return "unknown code";
224         }
225     }
226     
227     State state;
228     string buffer;
229     string method;
230     string path;
231     string httpVersion;
232     std::map<string, string> requestHeaders;
233 };
234
235 class TestServer : public NetChannel
236 {
237 public:   
238     TestServer()
239     {
240         open();
241         bind(NULL, 2000); // localhost, any port
242         listen(5);
243     }
244     
245     virtual ~TestServer()
246     {    
247     }
248     
249     virtual bool writable (void) { return false ; }
250
251     virtual void handleAccept (void)
252     {
253         simgear::IPAddress addr ;
254         int handle = accept ( &addr ) ;
255         cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
256         TestServerChannel* chan = new TestServerChannel();
257         chan->setHandle(handle);
258     }
259 };
260
261 void waitForComplete(TestRequest* tr)
262 {
263     SGTimeStamp start(SGTimeStamp::now());
264     while (start.elapsedMSec() <  1000) {
265         NetChannel::poll(10);
266         if (tr->complete) {
267             return;
268         }
269     }
270     
271     cerr << "timed out" << endl;
272 }
273
274 void waitForFailed(TestRequest* tr)
275 {
276     SGTimeStamp start(SGTimeStamp::now());
277     while (start.elapsedMSec() <  1000) {
278         NetChannel::poll(10);
279         if (tr->failed) {
280             return;
281         }
282     }
283     
284     cerr << "timed out waiting for failure" << endl;
285 }
286
287 int main(int argc, char* argv[])
288 {
289     TestServer s;
290     
291     HTTP::Client cl;
292
293 // test URL parsing
294     TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
295     COMPARE(tr1->scheme(), "http");
296     COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
297     COMPARE(tr1->host(), "localhost.woo.zar");
298     COMPARE(tr1->port(), 2000);
299     COMPARE(tr1->path(), "/test1");
300     
301     TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
302     COMPARE(tr2->scheme(), "http");
303     COMPARE(tr2->hostAndPort(), "192.168.1.1");
304     COMPARE(tr2->host(), "192.168.1.1");
305     COMPARE(tr2->port(), 80);
306     COMPARE(tr2->path(), "/test1/dir/thing/file.png");
307     
308 // basic get request
309     {
310         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
311         HTTP::Request_ptr own(tr);
312         cl.makeRequest(tr);
313
314         waitForComplete(tr);
315         COMPARE(tr->responseCode(), 200);
316         COMPARE(tr->responseReason(), string("OK"));
317         COMPARE(tr->responseLength(), strlen(BODY1));
318         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
319         COMPARE(tr->bodyData, string(BODY1));
320     }
321
322 // larger get request
323     for (unsigned int i=0; i<body2Size; ++i) {
324         body2[i] = (i << 4) | (i >> 2);
325     }
326     
327     {
328         TestRequest* tr = new TestRequest("http://localhost:2000/test2");
329         HTTP::Request_ptr own(tr);
330         cl.makeRequest(tr);
331         waitForComplete(tr);
332         COMPARE(tr->responseCode(), 200);
333         COMPARE(tr->responseBytesReceived(), body2Size);
334         COMPARE(tr->bodyData, string(body2, body2Size));
335     }
336     
337     {
338         TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
339         HTTP::Request_ptr own(tr);
340         cl.makeRequest(tr);
341
342         waitForComplete(tr);
343         COMPARE(tr->responseCode(), 200);
344         COMPARE(tr->responseReason(), string("OK"));
345         COMPARE(tr->responseBytesReceived(), 30);
346         COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
347     // check trailers made it too
348         COMPARE(tr->headers["x-foobar"], string("wibble"));
349     }
350     
351 // test 404
352     {
353         TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
354         HTTP::Request_ptr own(tr);
355         cl.makeRequest(tr);
356         waitForComplete(tr);
357         COMPARE(tr->responseCode(), 404);
358         COMPARE(tr->responseReason(), string("not found"));
359         COMPARE(tr->responseLength(), 0);
360     }
361
362 // test connectToHost failure
363 /*
364     {
365         TestRequest* tr = new TestRequest("http://not.found/something");
366         HTTP::Request_ptr own(tr);
367         cl.makeRequest(tr);
368         waitForFailed(tr);
369         COMPARE(tr->responseCode(), -1);
370     }
371     */
372 // test proxy
373     {
374         cl.setProxy("localhost", 2000);
375         TestRequest* tr = new TestRequest("http://www.google.com/test2");
376         HTTP::Request_ptr own(tr);
377         cl.makeRequest(tr);
378         waitForComplete(tr);
379         COMPARE(tr->responseCode(), 200);
380         COMPARE(tr->responseLength(), body2Size);
381         COMPARE(tr->bodyData, string(body2, body2Size));
382     }
383     
384     {
385         cl.setProxy("localhost", 2000, "ABCDEF");
386         TestRequest* tr = new TestRequest("http://www.google.com/test3");
387         HTTP::Request_ptr own(tr);
388         cl.makeRequest(tr);
389         waitForComplete(tr);
390         COMPARE(tr->responseCode(), 200);
391         COMPARE(tr->responseBytesReceived(), body2Size);
392         COMPARE(tr->bodyData, string(body2, body2Size));
393     }
394     
395     cout << "all tests passed ok" << endl;
396     return EXIT_SUCCESS;
397 }