]> git.mxchange.org Git - simgear.git/blob - simgear/io/test_HTTP.cxx
Fix #1783: repeated error message on console
[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 const char* BODY3 = "Cras ut neque nulla. Duis ut velit neque, sit amet "
26 "pharetra risus. In est ligula, lacinia vitae congue in, sollicitudin at "
27 "libero. Mauris pharetra pretium elit, nec placerat dui semper et. Maecenas "
28 "magna magna, placerat sed luctus ac, commodo et ligula. Mauris at purus et "
29 "nisl molestie auctor placerat at quam. Donec sapien magna, venenatis sed "
30 "iaculis id, fringilla vel arcu. Duis sed neque nisi. Cras a arcu sit amet "
31 "risus ultrices varius. Integer sagittis euismod dui id varius. Cras vel "
32 "justo gravida metus.";
33
34 const unsigned int body2Size = 8 * 1024;
35 char body2[body2Size];
36
37 #define COMPARE(a, b) \
38     if ((a) != (b))  { \
39         cerr << "failed:" << #a << " != " << #b << endl; \
40         cerr << "\tgot:'" << a << "'" << endl; \
41         exit(1); \
42     }
43
44 #define VERIFY(a) \
45     if (!(a))  { \
46         cerr << "failed:" << #a << endl; \
47         exit(1); \
48     }
49     
50 class TestRequest : public HTTP::Request
51 {
52 public:
53     bool complete;
54     bool failed;
55     string bodyData;
56
57     TestRequest(const std::string& url, const std::string method = "GET") : 
58         HTTP::Request(url, method),
59         complete(false)
60     {
61
62     }
63     
64     std::map<string, string> headers;
65 protected:
66     
67     virtual void onDone()
68     {
69         complete = true;
70     }  
71     
72     virtual void onFail()
73     {
74         failed = true;
75     }
76     
77     virtual void gotBodyData(const char* s, int n)
78     {
79       //std::cout << "got body data:'" << string(s, n) << "'" <<std::endl;
80         bodyData += string(s, n);
81     }
82     
83     virtual void responseHeader(const string& header, const string& value)
84     {
85         headers[header] =  value;
86     }
87 };
88
89 class TestServerChannel : public NetChat
90 {
91 public:  
92     enum State
93     {
94         STATE_IDLE = 0,
95         STATE_HEADERS,
96         STATE_CLOSING,
97         STATE_REQUEST_BODY
98     };
99     
100     TestServerChannel()
101     {
102         state = STATE_IDLE;
103         setTerminator("\r\n");
104         
105     }
106     
107     virtual void collectIncomingData(const char* s, int n)
108     {
109         buffer += string(s, n);
110     }
111     
112     virtual void foundTerminator(void)
113     {
114         if (state == STATE_IDLE) {
115             state = STATE_HEADERS;
116             string_list line = strutils::split(buffer, NULL, 3);
117             if (line.size() < 3) {
118                 cerr << "malformed request:" << buffer << endl;
119                 exit(-1);
120             }
121             
122             method = line[0];
123             path = line[1];
124             
125             string::size_type queryPos = path.find('?');
126             if (queryPos != string::npos) {
127                 parseArgs(path.substr(queryPos + 1));
128                 path = path.substr(0, queryPos);
129             }
130             
131             httpVersion = line[2];
132             requestHeaders.clear();
133             buffer.clear();
134         } else if (state == STATE_HEADERS) {
135             string s = strutils::simplify(buffer);
136             if (s.empty()) {
137                 buffer.clear();
138                 receivedRequestHeaders();
139                 return;
140             }
141             
142             string::size_type colonPos = buffer.find(':');
143             if (colonPos == string::npos) {
144                 cerr << "malformed HTTP response header:" << buffer << endl;
145                 buffer.clear();
146                 return;
147             }
148
149             string key = strutils::simplify(buffer.substr(0, colonPos));
150             string value = strutils::strip(buffer.substr(colonPos + 1));
151             requestHeaders[key] = value;
152             buffer.clear();
153         } else if (state == STATE_REQUEST_BODY) {
154             receivedBody();
155             setTerminator("\r\n");
156         } else if (state == STATE_CLOSING) {
157           // ignore!
158         }
159     }  
160     
161     void parseArgs(const string& argData)
162     {
163         string_list argv = strutils::split(argData, "&");
164         for (unsigned int a=0; a<argv.size(); ++a) {
165             string::size_type eqPos = argv[a].find('=');
166             if (eqPos == string::npos) {
167                 cerr << "malformed HTTP argument:" << argv[a] << endl;
168                 continue;
169             }
170
171             string key = argv[a].substr(0, eqPos);
172             string value = argv[a].substr(eqPos + 1);
173             args[key] = value;
174         }
175     }
176     
177     void receivedRequestHeaders()
178     {
179         state = STATE_IDLE;
180         
181         if (path == "/test1") {
182             string contentStr(BODY1);
183             stringstream d;
184             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
185             d << "Content-Length:" << contentStr.size() << "\r\n";
186             d << "\r\n"; // final CRLF to terminate the headers
187             d << contentStr;
188             push(d.str().c_str());
189         } else if (path == "/testLorem") {
190             string contentStr(BODY3);
191             stringstream d;
192             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
193             d << "Content-Length:" << contentStr.size() << "\r\n";
194             d << "\r\n"; // final CRLF to terminate the headers
195             d << contentStr;
196             push(d.str().c_str());
197         } else if (path == "/test_zero_length_content") {
198             string contentStr;
199             stringstream d;
200             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
201             d << "Content-Length:" << contentStr.size() << "\r\n";
202             d << "\r\n"; // final CRLF to terminate the headers
203             d << contentStr;
204             push(d.str().c_str());
205         } else if (path == "/test_headers") {
206             COMPARE(requestHeaders["X-Foo"], string("Bar"));
207             COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
208             
209             string contentStr(BODY1);
210             stringstream d;
211             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
212             d << "Content-Length:" << contentStr.size() << "\r\n";
213             d << "\r\n"; // final CRLF to terminate the headers
214             d << contentStr;
215             push(d.str().c_str());
216         } else if (path == "/test2") {
217             sendBody2();
218         } else if (path == "/testchunked") {
219             stringstream d;
220             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
221             d << "Transfer-Encoding:chunked\r\n";
222             d << "\r\n";
223             d << "8\r\n"; // first chunk
224             d << "ABCDEFGH\r\n";
225             d << "6\r\n"; // second chunk
226             d << "ABCDEF\r\n";
227             d << "10\r\n"; // third chunk
228             d << "ABCDSTUVABCDSTUV\r\n";
229             d << "0\r\n"; // start of trailer
230             d << "X-Foobar: wibble\r\n"; // trailer data
231             d << "\r\n";
232             push(d.str().c_str());
233         } else if (path == "http://www.google.com/test2") {
234             // proxy test
235             if (requestHeaders["Host"] != "www.google.com") {
236                 sendErrorResponse(400, true, "bad destination");
237             }
238             
239             if (requestHeaders["Proxy-Authorization"] != string()) {
240                 sendErrorResponse(401, false, "bad auth"); // shouldn't supply auth
241             }
242             
243             sendBody2();
244         } else if (path == "http://www.google.com/test3") {
245             // proxy test
246             if (requestHeaders["Host"] != "www.google.com") {
247                 sendErrorResponse(400, true, "bad destination");
248             }
249
250             if (requestHeaders["Proxy-Authorization"] != "ABCDEF") {
251                 sendErrorResponse(401, false, "bad auth"); // forbidden
252             }
253
254             sendBody2();
255         } else if (strutils::starts_with(path, "/test_1_0")) {
256             string contentStr(BODY1);
257             if (strutils::ends_with(path, "/B")) {
258                 contentStr = BODY3;
259             }
260             stringstream d;
261             d << "HTTP/1.0 " << 200 << " " << reasonForCode(200) << "\r\n";
262             d << "\r\n"; // final CRLF to terminate the headers
263             d << contentStr;
264             push(d.str().c_str());
265             closeAfterSending();
266         } else if (path == "/test_close") {
267             string contentStr(BODY1);
268             stringstream d;
269             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
270             d << "Connection: close\r\n";
271             d << "\r\n"; // final CRLF to terminate the headers
272             d << contentStr;
273             push(d.str().c_str());
274             closeAfterSending();
275         } else if (path == "/test_args") {
276             if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
277                 sendErrorResponse(400, true, "bad arguments");
278                 return;
279             }
280
281             string contentStr(BODY1);
282             stringstream d;
283             d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
284             d << "Content-Length:" << contentStr.size() << "\r\n";
285             d << "\r\n"; // final CRLF to terminate the headers
286             d << contentStr;
287             push(d.str().c_str());
288         } else if (path == "/test_post") {
289             if (requestHeaders["Content-Type"] != "application/x-www-form-urlencoded") {
290                 cerr << "bad content type: '" << requestHeaders["Content-Type"] << "'" << endl;
291                  sendErrorResponse(400, true, "bad content type");
292                  return;
293             }
294             
295             requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
296             setByteCount(requestContentLength);
297             state = STATE_REQUEST_BODY;
298         } else {
299             sendErrorResponse(404, false, "");
300         }
301     }
302     
303     void closeAfterSending()
304     {
305       state = STATE_CLOSING;
306       closeWhenDone();
307     }
308   
309     void receivedBody()
310     {
311         state = STATE_IDLE;
312         if (method == "POST") {
313             parseArgs(buffer);
314         }
315         
316         if (path == "/test_post") {
317             if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
318                 sendErrorResponse(400, true, "bad arguments");
319                 return;
320             }
321             
322             stringstream d;
323             d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
324             d << "\r\n"; // final CRLF to terminate the headers
325             push(d.str().c_str());
326             
327             cerr << "sent 204 response ok" << endl;
328         }
329     }
330     
331     void sendBody2()
332     {
333         stringstream d;
334         d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
335         d << "Content-Length:" << body2Size << "\r\n";
336         d << "\r\n"; // final CRLF to terminate the headers
337         push(d.str().c_str());
338         bufferSend(body2, body2Size);
339     }
340     
341     void sendErrorResponse(int code, bool close, string content)
342     {
343         cerr << "sending error " << code << " for " << path << endl;
344         stringstream headerData;
345         headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n";
346         headerData << "Content-Length:" << content.size() << "\r\n";
347         headerData << "\r\n"; // final CRLF to terminate the headers
348         push(headerData.str().c_str());
349         push(content.c_str());
350         
351         if (close) {
352             closeWhenDone();
353         }
354     }
355     
356     string reasonForCode(int code) 
357     {
358         switch (code) {
359             case 200: return "OK";
360             case 204: return "no content";
361             case 404: return "not found";
362             default: return "unknown code";
363         }
364     }
365     
366     State state;
367     string buffer;
368     string method;
369     string path;
370     string httpVersion;
371     std::map<string, string> requestHeaders;
372     std::map<string, string> args;
373     int requestContentLength;
374 };
375
376 class TestServer : public NetChannel
377 {
378     simgear::NetChannelPoller _poller;
379 public:   
380     TestServer()
381     {
382         Socket::initSockets();
383
384         open();
385         bind(NULL, 2000); // localhost, any port
386         listen(5);
387         
388         _poller.addChannel(this);
389     }
390     
391     virtual ~TestServer()
392     {    
393     }
394     
395     virtual bool writable (void) { return false ; }
396
397     virtual void handleAccept (void)
398     {
399         simgear::IPAddress addr ;
400         int handle = accept ( &addr ) ;
401         //cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
402         TestServerChannel* chan = new TestServerChannel();
403         chan->setHandle(handle);
404         
405         _poller.addChannel(chan);
406     }
407     
408     void poll()
409     {
410         _poller.poll();
411     }
412 };
413
414 TestServer testServer;
415
416 void waitForComplete(HTTP::Client* cl, TestRequest* tr)
417 {
418     SGTimeStamp start(SGTimeStamp::now());
419     while (start.elapsedMSec() <  10000) {
420         cl->update();
421         testServer.poll();
422         
423         if (tr->complete) {
424             return;
425         }
426         SGTimeStamp::sleepForMSec(15);
427     }
428     
429     cerr << "timed out" << endl;
430 }
431
432 void waitForFailed(HTTP::Client* cl, TestRequest* tr)
433 {
434     SGTimeStamp start(SGTimeStamp::now());
435     while (start.elapsedMSec() <  10000) {
436         cl->update();
437         testServer.poll();
438         
439         if (tr->failed) {
440             return;
441         }
442         SGTimeStamp::sleepForMSec(15);
443     }
444     
445     cerr << "timed out waiting for failure" << endl;
446 }
447
448 int main(int argc, char* argv[])
449 {
450     
451     HTTP::Client cl;
452     // force all requests to use the same connection for this test
453     cl.setMaxConnections(1); 
454     
455 // test URL parsing
456     TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
457     COMPARE(tr1->scheme(), "http");
458     COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
459     COMPARE(tr1->host(), "localhost.woo.zar");
460     COMPARE(tr1->port(), 2000);
461     COMPARE(tr1->path(), "/test1");
462     
463     TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
464     COMPARE(tr2->scheme(), "http");
465     COMPARE(tr2->hostAndPort(), "192.168.1.1");
466     COMPARE(tr2->host(), "192.168.1.1");
467     COMPARE(tr2->port(), 80);
468     COMPARE(tr2->path(), "/test1/dir/thing/file.png");
469     
470 // basic get request
471     {
472         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
473         HTTP::Request_ptr own(tr);
474         cl.makeRequest(tr);
475
476         waitForComplete(&cl, tr);
477         COMPARE(tr->responseCode(), 200);
478         COMPARE(tr->responseReason(), string("OK"));
479         COMPARE(tr->responseLength(), strlen(BODY1));
480         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
481         COMPARE(tr->bodyData, string(BODY1));
482     }
483     
484     {
485       TestRequest* tr = new TestRequest("http://localhost:2000/testLorem");
486       HTTP::Request_ptr own(tr);
487       cl.makeRequest(tr);
488       
489       waitForComplete(&cl, tr);
490       COMPARE(tr->responseCode(), 200);
491       COMPARE(tr->responseReason(), string("OK"));
492       COMPARE(tr->responseLength(), strlen(BODY3));
493       COMPARE(tr->responseBytesReceived(), strlen(BODY3));
494       COMPARE(tr->bodyData, string(BODY3));
495     }
496   
497     {
498         TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
499         HTTP::Request_ptr own(tr);
500         cl.makeRequest(tr);
501         waitForComplete(&cl, tr);
502         COMPARE(tr->responseCode(), 200);
503     }
504     
505     cerr << "done args" << endl;
506     
507     {
508         TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
509         HTTP::Request_ptr own(tr);
510         tr->requestHeader("X-Foo") = "Bar";
511         tr->requestHeader("X-AnotherHeader") = "A longer value";
512         cl.makeRequest(tr);
513
514         waitForComplete(&cl, tr);
515         COMPARE(tr->responseCode(), 200);
516         COMPARE(tr->responseReason(), string("OK"));
517         COMPARE(tr->responseLength(), strlen(BODY1));
518         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
519         COMPARE(tr->bodyData, string(BODY1));
520     }
521     
522 // larger get request
523     for (unsigned int i=0; i<body2Size; ++i) {
524         body2[i] = (i << 4) | (i >> 2);
525     }
526     
527     {
528         TestRequest* tr = new TestRequest("http://localhost:2000/test2");
529         HTTP::Request_ptr own(tr);
530         cl.makeRequest(tr);
531         waitForComplete(&cl, tr);
532         COMPARE(tr->responseCode(), 200);
533         COMPARE(tr->responseBytesReceived(), body2Size);
534         COMPARE(tr->bodyData, string(body2, body2Size));
535     }
536     
537     cerr << "testing chunked" << endl;
538     {
539         TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
540         HTTP::Request_ptr own(tr);
541         cl.makeRequest(tr);
542
543         waitForComplete(&cl, tr);
544         COMPARE(tr->responseCode(), 200);
545         COMPARE(tr->responseReason(), string("OK"));
546         COMPARE(tr->responseBytesReceived(), 30);
547         COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
548     // check trailers made it too
549         COMPARE(tr->headers["x-foobar"], string("wibble"));
550     }
551     
552 // test 404
553     {
554         TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
555         HTTP::Request_ptr own(tr);
556         cl.makeRequest(tr);
557         waitForComplete(&cl, tr);
558         COMPARE(tr->responseCode(), 404);
559         COMPARE(tr->responseReason(), string("not found"));
560         COMPARE(tr->responseLength(), 0);
561     }
562
563     cout << "done 404 test" << endl;
564
565     {
566         TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
567         HTTP::Request_ptr own(tr);
568         cl.makeRequest(tr);
569         waitForComplete(&cl, tr);
570         COMPARE(tr->responseCode(), 200);
571     }
572
573     cout << "done1" << endl;
574 // test HTTP/1.0
575     {
576         TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
577         HTTP::Request_ptr own(tr);
578         cl.makeRequest(tr);
579         waitForComplete(&cl, tr);
580         COMPARE(tr->responseCode(), 200);
581         COMPARE(tr->responseLength(), strlen(BODY1));
582         COMPARE(tr->bodyData, string(BODY1));
583     }
584
585     cout << "done2" << endl;
586 // test HTTP/1.1 Connection::close
587     {
588         TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
589         HTTP::Request_ptr own(tr);
590         cl.makeRequest(tr);
591         waitForComplete(&cl, tr);
592         COMPARE(tr->responseCode(), 200);
593         COMPARE(tr->responseLength(), strlen(BODY1));
594         COMPARE(tr->bodyData, string(BODY1));
595     }
596     cout << "done3" << endl;
597 // test connectToHost failure
598 /*
599     {
600         TestRequest* tr = new TestRequest("http://not.found/something");
601         HTTP::Request_ptr own(tr);
602         cl.makeRequest(tr);
603         waitForFailed(tr);
604         COMPARE(tr->responseCode(), -1);
605     }
606     */
607 // test proxy
608     {
609         cl.setProxy("localhost", 2000);
610         TestRequest* tr = new TestRequest("http://www.google.com/test2");
611         HTTP::Request_ptr own(tr);
612         cl.makeRequest(tr);
613         waitForComplete(&cl, tr);
614         COMPARE(tr->responseCode(), 200);
615         COMPARE(tr->responseLength(), body2Size);
616         COMPARE(tr->bodyData, string(body2, body2Size));
617     }
618     
619     {
620         cl.setProxy("localhost", 2000, "ABCDEF");
621         TestRequest* tr = new TestRequest("http://www.google.com/test3");
622         HTTP::Request_ptr own(tr);
623         cl.makeRequest(tr);
624         waitForComplete(&cl, tr);
625         COMPARE(tr->responseCode(), 200);
626         COMPARE(tr->responseBytesReceived(), body2Size);
627         COMPARE(tr->bodyData, string(body2, body2Size));
628     }
629     
630 // pipelining
631     cout << "testing HTTP 1.1 pipelineing" << endl;
632   
633     {
634                                 
635         cl.setProxy("", 80);
636         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
637         HTTP::Request_ptr own(tr);
638         cl.makeRequest(tr);
639         
640         
641         TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
642         HTTP::Request_ptr own2(tr2);
643         cl.makeRequest(tr2);
644         
645         TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
646         HTTP::Request_ptr own3(tr3);
647         cl.makeRequest(tr3);
648         
649         waitForComplete(&cl, tr3);
650         VERIFY(tr->complete);
651         VERIFY(tr2->complete);
652         COMPARE(tr->bodyData, string(BODY1));
653       
654         COMPARE(tr2->responseLength(), strlen(BODY3));
655         COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
656         COMPARE(tr2->bodyData, string(BODY3));
657       
658         COMPARE(tr3->bodyData, string(BODY1));
659     }
660     
661 // multiple requests with an HTTP 1.0 server
662     {
663         cout << "http 1.0 multiple requests" << endl;
664         
665         cl.setProxy("", 80);
666         TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
667         HTTP::Request_ptr own(tr);
668         cl.makeRequest(tr);
669         
670         TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
671         HTTP::Request_ptr own2(tr2);
672         cl.makeRequest(tr2);
673         
674         TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
675         HTTP::Request_ptr own3(tr3);
676         cl.makeRequest(tr3);
677         
678         waitForComplete(&cl, tr3);
679         VERIFY(tr->complete);
680         VERIFY(tr2->complete);
681         
682         COMPARE(tr->responseLength(), strlen(BODY1));
683         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
684         COMPARE(tr->bodyData, string(BODY1));
685       
686         COMPARE(tr2->responseLength(), strlen(BODY3));
687         COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
688         COMPARE(tr2->bodyData, string(BODY3));
689         COMPARE(tr3->bodyData, string(BODY1));
690     }
691     
692 // POST
693     {
694         cout << "POST" << endl;
695         TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
696         tr->setBodyData("", "application/x-www-form-urlencoded");
697         
698         HTTP::Request_ptr own(tr);
699         cl.makeRequest(tr);
700         waitForComplete(&cl, tr);
701         COMPARE(tr->responseCode(), 204);
702     }
703     
704     // test_zero_length_content
705     {
706         cout << "zero-length-content-response" << endl;
707         TestRequest* tr = new TestRequest("http://localhost:2000/test_zero_length_content");
708         HTTP::Request_ptr own(tr);
709         cl.makeRequest(tr);
710         waitForComplete(&cl, tr);
711         COMPARE(tr->responseCode(), 200);
712         COMPARE(tr->bodyData, string());
713         COMPARE(tr->responseBytesReceived(), 0);
714     }
715     
716     
717     cout << "all tests passed ok" << endl;
718     return EXIT_SUCCESS;
719 }