]> git.mxchange.org Git - simgear.git/blob - simgear/io/test_HTTP.cxx
Lots of (mostly) doxygen fixes/cleanup.
[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         open();
383         bind(NULL, 2000); // localhost, any port
384         listen(5);
385         
386         _poller.addChannel(this);
387     }
388     
389     virtual ~TestServer()
390     {    
391     }
392     
393     virtual bool writable (void) { return false ; }
394
395     virtual void handleAccept (void)
396     {
397         simgear::IPAddress addr ;
398         int handle = accept ( &addr ) ;
399         //cout << "did accept from " << addr.getHost() << ":" << addr.getPort() << endl;
400         TestServerChannel* chan = new TestServerChannel();
401         chan->setHandle(handle);
402         
403         _poller.addChannel(chan);
404     }
405     
406     void poll()
407     {
408         _poller.poll();
409     }
410 };
411
412 TestServer testServer;
413
414 void waitForComplete(HTTP::Client* cl, TestRequest* tr)
415 {
416     SGTimeStamp start(SGTimeStamp::now());
417     while (start.elapsedMSec() <  1000) {
418         cl->update();
419         testServer.poll();
420         
421         if (tr->complete) {
422             return;
423         }
424         SGTimeStamp::sleepForMSec(1);
425     }
426     
427     cerr << "timed out" << endl;
428 }
429
430 void waitForFailed(HTTP::Client* cl, TestRequest* tr)
431 {
432     SGTimeStamp start(SGTimeStamp::now());
433     while (start.elapsedMSec() <  1000) {
434         cl->update();
435         testServer.poll();
436         
437         if (tr->failed) {
438             return;
439         }
440         SGTimeStamp::sleepForMSec(1);
441     }
442     
443     cerr << "timed out waiting for failure" << endl;
444 }
445
446 int main(int argc, char* argv[])
447 {
448     
449     HTTP::Client cl;
450     // force all requests to use the same connection for this test
451     cl.setMaxConnections(1); 
452     
453 // test URL parsing
454     TestRequest* tr1 = new TestRequest("http://localhost.woo.zar:2000/test1?foo=bar");
455     COMPARE(tr1->scheme(), "http");
456     COMPARE(tr1->hostAndPort(), "localhost.woo.zar:2000");
457     COMPARE(tr1->host(), "localhost.woo.zar");
458     COMPARE(tr1->port(), 2000);
459     COMPARE(tr1->path(), "/test1");
460     
461     TestRequest* tr2 = new TestRequest("http://192.168.1.1/test1/dir/thing/file.png");
462     COMPARE(tr2->scheme(), "http");
463     COMPARE(tr2->hostAndPort(), "192.168.1.1");
464     COMPARE(tr2->host(), "192.168.1.1");
465     COMPARE(tr2->port(), 80);
466     COMPARE(tr2->path(), "/test1/dir/thing/file.png");
467     
468 // basic get request
469     {
470         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
471         HTTP::Request_ptr own(tr);
472         cl.makeRequest(tr);
473
474         waitForComplete(&cl, tr);
475         COMPARE(tr->responseCode(), 200);
476         COMPARE(tr->responseReason(), string("OK"));
477         COMPARE(tr->responseLength(), strlen(BODY1));
478         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
479         COMPARE(tr->bodyData, string(BODY1));
480     }
481     
482     {
483       TestRequest* tr = new TestRequest("http://localhost:2000/testLorem");
484       HTTP::Request_ptr own(tr);
485       cl.makeRequest(tr);
486       
487       waitForComplete(&cl, tr);
488       COMPARE(tr->responseCode(), 200);
489       COMPARE(tr->responseReason(), string("OK"));
490       COMPARE(tr->responseLength(), strlen(BODY3));
491       COMPARE(tr->responseBytesReceived(), strlen(BODY3));
492       COMPARE(tr->bodyData, string(BODY3));
493     }
494   
495     {
496         TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
497         HTTP::Request_ptr own(tr);
498         cl.makeRequest(tr);
499         waitForComplete(&cl, tr);
500         COMPARE(tr->responseCode(), 200);
501     }
502     
503     cerr << "done args" << endl;
504     
505     {
506         TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
507         HTTP::Request_ptr own(tr);
508         tr->requestHeader("X-Foo") = "Bar";
509         tr->requestHeader("X-AnotherHeader") = "A longer value";
510         cl.makeRequest(tr);
511
512         waitForComplete(&cl, tr);
513         COMPARE(tr->responseCode(), 200);
514         COMPARE(tr->responseReason(), string("OK"));
515         COMPARE(tr->responseLength(), strlen(BODY1));
516         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
517         COMPARE(tr->bodyData, string(BODY1));
518     }
519     
520 // larger get request
521     for (unsigned int i=0; i<body2Size; ++i) {
522         body2[i] = (i << 4) | (i >> 2);
523     }
524     
525     {
526         TestRequest* tr = new TestRequest("http://localhost:2000/test2");
527         HTTP::Request_ptr own(tr);
528         cl.makeRequest(tr);
529         waitForComplete(&cl, tr);
530         COMPARE(tr->responseCode(), 200);
531         COMPARE(tr->responseBytesReceived(), body2Size);
532         COMPARE(tr->bodyData, string(body2, body2Size));
533     }
534     
535     cerr << "testing chunked" << endl;
536     {
537         TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
538         HTTP::Request_ptr own(tr);
539         cl.makeRequest(tr);
540
541         waitForComplete(&cl, tr);
542         COMPARE(tr->responseCode(), 200);
543         COMPARE(tr->responseReason(), string("OK"));
544         COMPARE(tr->responseBytesReceived(), 30);
545         COMPARE(tr->bodyData, "ABCDEFGHABCDEFABCDSTUVABCDSTUV");
546     // check trailers made it too
547         COMPARE(tr->headers["x-foobar"], string("wibble"));
548     }
549     
550 // test 404
551     {
552         TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
553         HTTP::Request_ptr own(tr);
554         cl.makeRequest(tr);
555         waitForComplete(&cl, tr);
556         COMPARE(tr->responseCode(), 404);
557         COMPARE(tr->responseReason(), string("not found"));
558         COMPARE(tr->responseLength(), 0);
559     }
560
561     cout << "done 404 test" << endl;
562
563     {
564         TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
565         HTTP::Request_ptr own(tr);
566         cl.makeRequest(tr);
567         waitForComplete(&cl, tr);
568         COMPARE(tr->responseCode(), 200);
569     }
570
571     cout << "done1" << endl;
572 // test HTTP/1.0
573     {
574         TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0");
575         HTTP::Request_ptr own(tr);
576         cl.makeRequest(tr);
577         waitForComplete(&cl, tr);
578         COMPARE(tr->responseCode(), 200);
579         COMPARE(tr->responseLength(), strlen(BODY1));
580         COMPARE(tr->bodyData, string(BODY1));
581     }
582
583     cout << "done2" << endl;
584 // test HTTP/1.1 Connection::close
585     {
586         TestRequest* tr = new TestRequest("http://localhost:2000/test_close");
587         HTTP::Request_ptr own(tr);
588         cl.makeRequest(tr);
589         waitForComplete(&cl, tr);
590         COMPARE(tr->responseCode(), 200);
591         COMPARE(tr->responseLength(), strlen(BODY1));
592         COMPARE(tr->bodyData, string(BODY1));
593     }
594     cout << "done3" << endl;
595 // test connectToHost failure
596 /*
597     {
598         TestRequest* tr = new TestRequest("http://not.found/something");
599         HTTP::Request_ptr own(tr);
600         cl.makeRequest(tr);
601         waitForFailed(tr);
602         COMPARE(tr->responseCode(), -1);
603     }
604     */
605 // test proxy
606     {
607         cl.setProxy("localhost", 2000);
608         TestRequest* tr = new TestRequest("http://www.google.com/test2");
609         HTTP::Request_ptr own(tr);
610         cl.makeRequest(tr);
611         waitForComplete(&cl, tr);
612         COMPARE(tr->responseCode(), 200);
613         COMPARE(tr->responseLength(), body2Size);
614         COMPARE(tr->bodyData, string(body2, body2Size));
615     }
616     
617     {
618         cl.setProxy("localhost", 2000, "ABCDEF");
619         TestRequest* tr = new TestRequest("http://www.google.com/test3");
620         HTTP::Request_ptr own(tr);
621         cl.makeRequest(tr);
622         waitForComplete(&cl, tr);
623         COMPARE(tr->responseCode(), 200);
624         COMPARE(tr->responseBytesReceived(), body2Size);
625         COMPARE(tr->bodyData, string(body2, body2Size));
626     }
627     
628 // pipelining
629     cout << "testing HTTP 1.1 pipelineing" << endl;
630   
631     {
632                                 
633         cl.setProxy("", 80);
634         TestRequest* tr = new TestRequest("http://localhost:2000/test1");
635         HTTP::Request_ptr own(tr);
636         cl.makeRequest(tr);
637         
638         
639         TestRequest* tr2 = new TestRequest("http://localhost:2000/testLorem");
640         HTTP::Request_ptr own2(tr2);
641         cl.makeRequest(tr2);
642         
643         TestRequest* tr3 = new TestRequest("http://localhost:2000/test1");
644         HTTP::Request_ptr own3(tr3);
645         cl.makeRequest(tr3);
646         
647         waitForComplete(&cl, tr3);
648         VERIFY(tr->complete);
649         VERIFY(tr2->complete);
650         COMPARE(tr->bodyData, string(BODY1));
651       
652         COMPARE(tr2->responseLength(), strlen(BODY3));
653         COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
654         COMPARE(tr2->bodyData, string(BODY3));
655       
656         COMPARE(tr3->bodyData, string(BODY1));
657     }
658     
659 // multiple requests with an HTTP 1.0 server
660     {
661         cout << "http 1.0 multiple requests" << endl;
662         
663         cl.setProxy("", 80);
664         TestRequest* tr = new TestRequest("http://localhost:2000/test_1_0/A");
665         HTTP::Request_ptr own(tr);
666         cl.makeRequest(tr);
667         
668         TestRequest* tr2 = new TestRequest("http://localhost:2000/test_1_0/B");
669         HTTP::Request_ptr own2(tr2);
670         cl.makeRequest(tr2);
671         
672         TestRequest* tr3 = new TestRequest("http://localhost:2000/test_1_0/C");
673         HTTP::Request_ptr own3(tr3);
674         cl.makeRequest(tr3);
675         
676         waitForComplete(&cl, tr3);
677         VERIFY(tr->complete);
678         VERIFY(tr2->complete);
679         
680         COMPARE(tr->responseLength(), strlen(BODY1));
681         COMPARE(tr->responseBytesReceived(), strlen(BODY1));
682         COMPARE(tr->bodyData, string(BODY1));
683       
684         COMPARE(tr2->responseLength(), strlen(BODY3));
685         COMPARE(tr2->responseBytesReceived(), strlen(BODY3));
686         COMPARE(tr2->bodyData, string(BODY3));
687         COMPARE(tr3->bodyData, string(BODY1));
688     }
689     
690 // POST
691     {
692         cout << "POST" << endl;
693         TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
694         tr->setBodyData("", "application/x-www-form-urlencoded");
695         
696         HTTP::Request_ptr own(tr);
697         cl.makeRequest(tr);
698         waitForComplete(&cl, tr);
699         COMPARE(tr->responseCode(), 204);
700     }
701     
702     // test_zero_length_content
703     {
704         cout << "zero-length-content-response" << endl;
705         TestRequest* tr = new TestRequest("http://localhost:2000/test_zero_length_content");
706         HTTP::Request_ptr own(tr);
707         cl.makeRequest(tr);
708         waitForComplete(&cl, tr);
709         COMPARE(tr->responseCode(), 200);
710         COMPARE(tr->bodyData, string());
711         COMPARE(tr->responseBytesReceived(), 0);
712     }
713     
714     
715     cout << "all tests passed ok" << endl;
716     return EXIT_SUCCESS;
717 }