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