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