]> git.mxchange.org Git - simgear.git/blob - simgear/io/HTTPClient.cxx
Unit test for SGBinObj, and fix a bug in large-indice handling the test revealed.
[simgear.git] / simgear / io / HTTPClient.cxx
1 #include "HTTPClient.hxx"
2
3 #include <sstream>
4 #include <cassert>
5 #include <list>
6
7 #include <boost/foreach.hpp>
8 #include <boost/algorithm/string/case_conv.hpp>
9
10 #include <simgear/io/sg_netChat.hxx>
11 #include <simgear/misc/strutils.hxx>
12 #include <simgear/compiler.h>
13 #include <simgear/debug/logstream.hxx>
14 #include <simgear/timing/timestamp.hxx>
15
16 #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
17 #include "version.h"
18 #else
19 #  if !defined(SIMGEAR_VERSION)
20 #    define SIMGEAR_VERSION "simgear-development"
21 #  endif
22 #endif
23
24 using std::string;
25 using std::stringstream;
26 using std::vector;
27
28 //#include <iostream>
29 //using namespace std;
30
31 namespace simgear
32 {
33
34 namespace HTTP
35 {
36
37 extern const int DEFAULT_HTTP_PORT = 80;
38
39 class Connection : public NetChat
40 {
41 public:
42     Connection(Client* pr) :
43         client(pr),
44         state(STATE_CLOSED),
45         port(DEFAULT_HTTP_PORT)
46     {
47         
48     }
49     
50     void setServer(const string& h, short p)
51     {
52         host = h;
53         port = p;
54     }
55     
56     // socket-level errors
57     virtual void handleError(int error)
58     {        
59         NetChat::handleError(error);
60         if (activeRequest) {
61             SG_LOG(SG_IO, SG_INFO, "HTTP socket error");
62             activeRequest->setFailure(error, "socket error");
63             activeRequest = NULL;
64         }
65     
66         state = STATE_SOCKET_ERROR;
67     }
68     
69     virtual void handleClose()
70     {
71         NetChat::handleClose();
72         
73         if ((state == STATE_GETTING_BODY) && activeRequest) {
74         // force state here, so responseComplete can avoid closing the 
75         // socket again
76             state =  STATE_CLOSED;
77             responseComplete();
78         } else {
79             state = STATE_CLOSED;
80         }
81     }
82     
83     void queueRequest(const Request_ptr& r)
84     {
85         if (!activeRequest) {
86             startRequest(r);
87         } else {
88             queuedRequests.push_back(r);
89         }
90     }
91     
92     void startRequest(const Request_ptr& r)
93     {
94         if (state == STATE_CLOSED) {
95             if (!connectToHost()) {
96                 return;
97             }
98             
99             state = STATE_IDLE;
100         }
101                 
102         activeRequest = r;
103         state = STATE_SENT_REQUEST;
104         bodyTransferSize = -1;
105         chunkedTransfer = false;
106         setTerminator("\r\n");
107         
108         stringstream headerData;
109         string path = r->path();
110         if (!client->proxyHost().empty()) {
111             path = r->url();
112         }
113
114         headerData << r->method() << " " << path << " HTTP/1.1\r\n";
115         headerData << "Host: " << r->hostAndPort() << "\r\n";
116         headerData << "User-Agent:" << client->userAgent() << "\r\n";
117         if (!client->proxyAuth().empty()) {
118             headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
119         }
120
121         BOOST_FOREACH(string h, r->requestHeaders()) {
122             headerData << h << ": " << r->header(h) << "\r\n";
123         }
124
125         headerData << "\r\n"; // final CRLF to terminate the headers
126
127     // TODO - add request body support for PUT, etc operations
128
129         bool ok = push(headerData.str().c_str());
130         if (!ok) {
131             SG_LOG(SG_IO, SG_WARN, "HTTP writing to socket failed");
132             state = STATE_SOCKET_ERROR;
133             return;
134         }        
135     }
136     
137     virtual void collectIncomingData(const char* s, int n)
138     {
139         if ((state == STATE_GETTING_BODY) || (state == STATE_GETTING_CHUNKED_BYTES)) {
140             activeRequest->processBodyBytes(s, n);
141         } else {
142             buffer += string(s, n);
143         }
144     }
145     
146     virtual void foundTerminator(void)
147     {
148         switch (state) {
149         case STATE_SENT_REQUEST:
150             activeRequest->responseStart(buffer);
151             state = STATE_GETTING_HEADERS;
152             buffer.clear();
153             break;
154             
155         case STATE_GETTING_HEADERS:
156             processHeader();
157             buffer.clear();
158             break;
159             
160         case STATE_GETTING_BODY:
161             responseComplete();
162             break;
163         
164         case STATE_GETTING_CHUNKED:
165             processChunkHeader();
166             break;
167             
168         case STATE_GETTING_CHUNKED_BYTES:
169             setTerminator("\r\n");
170             state = STATE_GETTING_CHUNKED;
171             break;
172             
173
174         case STATE_GETTING_TRAILER:
175             processTrailer();
176             buffer.clear();
177             break;
178         
179         default:
180             break;
181         }
182     }
183     
184     bool hasIdleTimeout() const
185     {
186         if (state != STATE_IDLE) {
187             return false;
188         }
189         
190         return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
191     }
192     
193     bool hasError() const
194     {
195         return (state == STATE_SOCKET_ERROR);
196     }
197     
198     bool shouldStartNext() const
199     {
200         return !activeRequest && !queuedRequests.empty() && 
201             ((state == STATE_CLOSED) || (state == STATE_IDLE));
202     }
203     
204     void startNext()
205     {
206         Request_ptr next = queuedRequests.front();
207         queuedRequests.pop_front();
208         startRequest(next);
209     }
210 private:
211     bool connectToHost()
212     {
213         SG_LOG(SG_IO, SG_INFO, "HTTP connecting to " << host << ":" << port);
214         
215         if (!open()) {
216             SG_LOG(SG_ALL, SG_WARN, "HTTP::Connection: connectToHost: open() failed");
217             return false;
218         }
219         
220         if (connect(host.c_str(), port) != 0) {
221             return false;
222         }
223         
224         return true;
225     }
226     
227     
228     void processHeader()
229     {
230         string h = strutils::simplify(buffer);
231         if (h.empty()) { // blank line terminates headers
232             headersComplete();
233             
234             if (chunkedTransfer) {
235                 state = STATE_GETTING_CHUNKED;
236             } else {
237                 setByteCount(bodyTransferSize); // may be -1, that's fine
238                 state = STATE_GETTING_BODY;
239             }
240             
241             return;
242         }
243         
244         int colonPos = buffer.find(':');
245         if (colonPos < 0) {
246             SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h);
247             return;
248         }
249         
250         string key = strutils::simplify(buffer.substr(0, colonPos));
251         string lkey = boost::to_lower_copy(key);
252         string value = strutils::strip(buffer.substr(colonPos + 1));
253         
254         // only consider these if getting headers (as opposed to trailers 
255         // of a chunked transfer)
256         if (state == STATE_GETTING_HEADERS) {
257             if (lkey == "content-length") {
258                 int sz = strutils::to_int(value);
259                 if (bodyTransferSize <= 0) {
260                     bodyTransferSize = sz;
261                 }
262                 activeRequest->setResponseLength(sz);
263             } else if (lkey == "transfer-length") {
264                 bodyTransferSize = strutils::to_int(value);
265             } else if (lkey == "transfer-encoding") {
266                 processTransferEncoding(value);
267             }
268         }
269     
270         activeRequest->responseHeader(lkey, value);
271     }
272     
273     void processTransferEncoding(const string& te)
274     {
275         if (te == "chunked") {
276             chunkedTransfer = true;
277         } else {
278             SG_LOG(SG_IO, SG_WARN, "unsupported transfer encoding:" << te);
279             // failure
280         }
281     }
282     
283     void processChunkHeader()
284     {
285         if (buffer.empty()) {
286             // blank line after chunk data
287             return;
288         }
289         
290         int chunkSize = 0;
291         int semiPos = buffer.find(';');
292         if (semiPos >= 0) {
293             // extensions ignored for the moment
294             chunkSize = strutils::to_int(buffer.substr(0, semiPos), 16);
295         } else {
296             chunkSize = strutils::to_int(buffer, 16);
297         }
298         
299         buffer.clear();
300         if (chunkSize == 0) {  //  trailer start
301             state = STATE_GETTING_TRAILER;
302             return;
303         }
304         
305         state = STATE_GETTING_CHUNKED_BYTES;
306         setByteCount(chunkSize);
307     }
308     
309     void processTrailer()
310     {        
311         if (buffer.empty()) {
312             // end of trailers
313             responseComplete();
314             return;
315         }
316         
317     // process as a normal header
318         processHeader();
319     }
320     
321     void headersComplete()
322     {
323         activeRequest->responseHeadersComplete();
324     }
325     
326     void responseComplete()
327     {
328         activeRequest->responseComplete();
329         client->requestFinished(this);
330         //cout << "response complete: " << activeRequest->url() << endl;
331         
332         bool doClose = activeRequest->closeAfterComplete();
333         activeRequest = NULL;
334         if (state == STATE_GETTING_BODY) {
335             state = STATE_IDLE;
336             if (doClose) {
337             // this will bring us into handleClose() above, which updates
338             // state to STATE_CLOSED
339                 close();
340             }
341         }
342         
343         setTerminator("\r\n");
344         
345     // if we have more requests, and we're idle, can start the next
346     // request immediately. Note we cannot do this if we're in STATE_CLOSED,
347     // since NetChannel::close cleans up state after calling handleClose;
348     // instead we pick up waiting requests in update()
349         if (!queuedRequests.empty() && (state == STATE_IDLE)) {
350             startNext();
351         } else {
352             idleTime.stamp();
353         }
354     }
355     
356     enum ConnectionState {
357         STATE_IDLE = 0,
358         STATE_SENT_REQUEST,
359         STATE_GETTING_HEADERS,
360         STATE_GETTING_BODY,
361         STATE_GETTING_CHUNKED,
362         STATE_GETTING_CHUNKED_BYTES,
363         STATE_GETTING_TRAILER,
364         STATE_SOCKET_ERROR,
365         STATE_CLOSED             ///< connection should be closed now
366     };
367     
368     Client* client;
369     Request_ptr activeRequest;
370     ConnectionState state;
371     string host;
372     short port;
373     std::string buffer;
374     int bodyTransferSize;
375     SGTimeStamp idleTime;
376     bool chunkedTransfer;
377     
378     std::list<Request_ptr> queuedRequests;
379 };
380
381 Client::Client()
382 {
383     setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
384 }
385
386 void Client::update()
387 {
388     NetChannel::poll();
389         
390     ConnectionDict::iterator it = _connections.begin();
391     for (; it != _connections.end(); ) {
392         if (it->second->hasIdleTimeout() || it->second->hasError()) {
393         // connection has been idle for a while, clean it up
394         // (or has an error condition, again clean it up)
395             SG_LOG(SG_IO, SG_INFO, "cleaning up " << it->second);
396             ConnectionDict::iterator del = it++;
397             delete del->second;
398             _connections.erase(del);
399         } else {
400             if (it->second->shouldStartNext()) {
401                 it->second->startNext();
402             }
403             
404             ++it;
405         }
406     } // of connecion iteration
407 }
408
409 void Client::makeRequest(const Request_ptr& r)
410 {
411     string host = r->host();
412     int port = r->port();
413     if (!_proxy.empty()) {
414         host = _proxy;
415         port = _proxyPort;
416     }
417     
418     stringstream ss;
419     ss << host << "-" << port;
420     string connectionId = ss.str();
421     
422     if (_connections.find(connectionId) == _connections.end()) {
423         Connection* con = new Connection(this);
424         con->setServer(host, port);
425         _connections[connectionId] = con;
426     }
427     
428     _connections[connectionId]->queueRequest(r);
429 }
430
431 void Client::requestFinished(Connection* con)
432 {
433     
434 }
435
436 void Client::setUserAgent(const string& ua)
437 {
438     _userAgent = ua;
439 }
440
441 void Client::setProxy(const string& proxy, int port, const string& auth)
442 {
443     _proxy = proxy;
444     _proxyPort = port;
445     _proxyAuth = auth;
446 }
447
448 } // of namespace HTTP
449
450 } // of namespace simgear