]> git.mxchange.org Git - simgear.git/blob - simgear/io/test_HTTP.cxx
Tiny HTTP client layer on top of NetChat - and CTest support for some SimGear tests.
[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 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     string bodyData;
47     
48     TestRequest(const std::string& url) : 
49         HTTP::Request(url),
50         complete(false)
51     {
52         
53     }
54     
55 protected:
56     virtual void responseHeadersComplete()
57     {
58     }
59     
60     virtual void responseComplete()
61     {
62         complete = true;
63     }  
64     
65     virtual void gotBodyData(const char* s, int n)
66     {
67         bodyData += string(s, n);
68     }
69 };
70
71 class TestServerChannel : public NetChat
72 {
73 public:  
74     enum State
75     {
76         STATE_IDLE = 0,
77         STATE_HEADERS,
78         STATE_REQUEST_BODY
79     };
80     
81     TestServerChannel()
82     {
83         state = STATE_IDLE;
84         setTerminator("\r\n");
85     }
86     
87     virtual void collectIncomingData(const char* s, int n)
88     {
89         buffer += string(s, n);
90     }
91     
92     virtual void foundTerminator(void)
93     {
94         if (state == STATE_IDLE) {
95             state = STATE_HEADERS;
96             string_list line = strutils::split(buffer, NULL, 3);
97             if (line.size() < 4) {
98                 cerr << "malformed request:" << buffer << endl;
99                 exit(-1);
100             }
101             
102             method = line[0];
103             path = line[1];
104             httpVersion = line[2];
105             userAgent = line[3];
106             requestHeaders.clear();
107             buffer.clear();
108         } else if (state == STATE_HEADERS) {
109             string s = strutils::simplify(buffer);
110             if (s.empty()) {
111                 buffer.clear();
112                 receivedRequestHeaders();
113                 return;
114             }
115             
116             int colonPos = buffer.find(':');
117             if (colonPos < 0) {
118                 cerr << "malformed HTTP response header:" << buffer << endl;
119                 buffer.clear();
120                 return;
121             }
122
123             string key = strutils::simplify(buffer.substr(0, colonPos));
124             string lkey = boost::to_lower_copy(key);
125             string value = strutils::strip(buffer.substr(colonPos + 1));
126             requestHeaders[lkey] = value;
127             buffer.clear();
128         } else if (state == STATE_REQUEST_BODY) {
129             
130         }
131     }  
132     
133     void receivedRequestHeaders()
134     {
135         state = STATE_IDLE;
136         if (path == "/test1") {
137             string contentStr(BODY1);
138             stringstream d;
139             d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
140             d << "Content-Length:" << contentStr.size() << "\r\n";
141             d << "\r\n"; // final CRLF to terminate the headers
142             d << contentStr;
143             push(d.str().c_str());
144         } else if (path == "/test2") {
145             stringstream d;
146             d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
147             d << "Content-Length:" << body2Size << "\r\n";
148             d << "\r\n"; // final CRLF to terminate the headers
149             push(d.str().c_str());
150             bufferSend(body2, body2Size);
151             cout << "sent body2" << endl;
152         } else {
153             sendErrorResponse(404);
154         }
155     }
156     
157     void sendErrorResponse(int code)
158     {
159         cerr << "sending error " << code << " for " << path << endl;
160         stringstream headerData;
161         headerData << "HTTP1.1 " << code << " " << reasonForCode(code) << "\r\n";
162         headerData << "\r\n"; // final CRLF to terminate the headers
163         push(headerData.str().c_str());
164     }
165     
166     string reasonForCode(int code) 
167     {
168         switch (code) {
169             case 200: return "OK";
170             case 404: return "not found";
171             default: return "unknown code";
172         }
173     }
174     
175     State state;
176     string buffer;
177     string method;
178     string path;
179     string httpVersion;
180     string userAgent;
181     std::map<string, string> requestHeaders;
182 };
183
184 class TestServer : public NetChannel
185 {
186 public:   
187     TestServer()
188     {
189         open();
190         bind(NULL, 2000); // localhost, any port
191         listen(5);
192     }
193     
194     virtual ~TestServer()
195     {    
196     }
197     
198     virtual bool writable (void) { return false ; }
199
200     virtual void handleAccept (void)
201     {
202         simgear::IPAddress addr ;
203         int handle = accept ( &addr ) ;
204
205         TestServerChannel* chan = new TestServerChannel();
206         chan->setHandle(handle);
207     }
208 };
209
210 void waitForComplete(TestRequest* tr)
211 {
212     SGTimeStamp start(SGTimeStamp::now());
213     while (start.elapsedMSec() <  1000) {
214         NetChannel::poll(10);
215         if (tr->complete) {
216             return;
217         }
218     }
219     
220     cerr << "timed out" << endl;
221 }
222
223 int main(int argc, char* argv[])
224 {
225     TestServer s;
226     
227     HTTP::Client cl;
228
229 // test URL parsing
230     TestRequest* tr1 = new TestRequest("http://localhost:2000/test1?foo=bar");
231     COMPARE(tr1->scheme(), "http");
232     COMPARE(tr1->host(), "localhost:2000");
233     COMPARE(tr1->path(), "/test1");
234     
235 // basic get request
236     {
237         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
238         HTTP::Request_ptr own(tr);
239         cl.makeRequest(tr);
240
241         waitForComplete(tr);
242         COMPARE(tr->responseCode(), 200);
243         COMPARE(tr->contentLength(), strlen(BODY1));
244         COMPARE(tr->bodyData, string(BODY1));
245     }
246
247 // larger get request
248     for (int i=0; i<body2Size; ++i) {
249         body2[i] = (i << 4) | (i >> 2);
250     }
251     
252     {
253         TestRequest* tr = new TestRequest("http://localhost:2000/test2");
254         HTTP::Request_ptr own(tr);
255         cl.makeRequest(tr);
256         waitForComplete(tr);
257         COMPARE(tr->responseCode(), 200);
258         COMPARE(tr->contentLength(), body2Size);
259         COMPARE(tr->bodyData, string(body2, body2Size));
260     }
261     
262 // test 404
263     {
264         TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
265         HTTP::Request_ptr own(tr);
266         cl.makeRequest(tr);
267         waitForComplete(tr);
268         COMPARE(tr->responseCode(), 404);
269         COMPARE(tr->contentLength(), 0);
270     }
271     
272     cout << "all tests passed ok" << endl;
273     return EXIT_SUCCESS;
274 }