]> git.mxchange.org Git - simgear.git/blob - simgear/io/HTTPClient.cxx
Support non-blocking address lookups, and switch to getaddrinfo over gethostbyname...
[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 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_IDLE;
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_IDLE:
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         case STATE_GETTING_TRAILER:
174             processTrailer();
175             buffer.clear();
176             break;
177         
178         default:
179             break;
180         }
181     }
182     
183     bool hasIdleTimeout() const
184     {
185         if (state != STATE_IDLE) {
186             return false;
187         }
188         
189         return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
190     }
191     
192     bool hasError() const
193     {
194         return (state == STATE_SOCKET_ERROR);
195     }
196     
197     bool shouldStartNext() const
198     {
199         return !activeRequest && !queuedRequests.empty() && 
200             ((state == STATE_CLOSED) || (state == STATE_IDLE));
201     }
202     
203     void startNext()
204     {
205         Request_ptr next = queuedRequests.front();
206         queuedRequests.pop_front();
207         startRequest(next);
208     }
209 private:
210     bool connectToHost()
211     {
212         SG_LOG(SG_IO, SG_INFO, "HTTP connecting to " << host << ":" << port);
213         
214         if (!open()) {
215             SG_LOG(SG_ALL, SG_WARN, "HTTP::Connection: connectToHost: open() failed");
216             return false;
217         }
218         
219         if (connect(host.c_str(), port) != 0) {
220             return false;
221         }
222         
223         return true;
224     }
225     
226     
227     void processHeader()
228     {
229         string h = strutils::simplify(buffer);
230         if (h.empty()) { // blank line terminates headers
231             headersComplete();
232             
233             if (chunkedTransfer) {
234                 state = STATE_GETTING_CHUNKED;
235             } else {
236                 setByteCount(bodyTransferSize); // may be -1, that's fine
237                 state = STATE_GETTING_BODY;
238             }
239             
240             return;
241         }
242         
243         int colonPos = buffer.find(':');
244         if (colonPos < 0) {
245             SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h);
246             return;
247         }
248         
249         string key = strutils::simplify(buffer.substr(0, colonPos));
250         string lkey = boost::to_lower_copy(key);
251         string value = strutils::strip(buffer.substr(colonPos + 1));
252         
253         // only consider these if getting headers (as opposed to trailers 
254         // of a chunked transfer)
255         if (state == STATE_GETTING_HEADERS) {
256             if (lkey == "content-length") {
257                 int sz = strutils::to_int(value);
258                 if (bodyTransferSize <= 0) {
259                     bodyTransferSize = sz;
260                 }
261                 activeRequest->setResponseLength(sz);
262             } else if (lkey == "transfer-length") {
263                 bodyTransferSize = strutils::to_int(value);
264             } else if (lkey == "transfer-encoding") {
265                 processTransferEncoding(value);
266             }
267         }
268     
269         activeRequest->responseHeader(lkey, value);
270     }
271     
272     void processTransferEncoding(const string& te)
273     {
274         if (te == "chunked") {
275             chunkedTransfer = true;
276         } else {
277             SG_LOG(SG_IO, SG_WARN, "unsupported transfer encoding:" << te);
278             // failure
279         }
280     }
281     
282     void processChunkHeader()
283     {
284         if (buffer.empty()) {
285             // blank line after chunk data
286             return;
287         }
288         
289         int chunkSize = 0;
290         int semiPos = buffer.find(';');
291         if (semiPos >= 0) {
292             // extensions ignored for the moment
293             chunkSize = strutils::to_int(buffer.substr(0, semiPos), 16);
294         } else {
295             chunkSize = strutils::to_int(buffer, 16);
296         }
297         
298         buffer.clear();
299         if (chunkSize == 0) {  //  trailer start
300             state = STATE_GETTING_TRAILER;
301             return;
302         }
303         
304         state = STATE_GETTING_CHUNKED_BYTES;
305         setByteCount(chunkSize);
306     }
307     
308     void processTrailer()
309     {        
310         if (buffer.empty()) {
311             // end of trailers
312             responseComplete();
313             return;
314         }
315         
316     // process as a normal header
317         processHeader();
318     }
319     
320     void headersComplete()
321     {
322         activeRequest->responseHeadersComplete();
323     }
324     
325     void responseComplete()
326     {
327         activeRequest->responseComplete();
328         client->requestFinished(this);
329         //cout << "response complete: " << activeRequest->url() << endl;
330         
331         bool doClose = activeRequest->closeAfterComplete();
332         activeRequest = NULL;
333         if (state == STATE_GETTING_BODY) {
334             state = STATE_IDLE;
335             if (doClose) {
336             // this will bring us into handleClose() above, which updates
337             // state to STATE_CLOSED
338                 close();
339             }
340         }
341         
342         setTerminator("\r\n");
343         
344     // if we have more requests, and we're idle, can start the next
345     // request immediately. Note we cannot do this if we're in STATE_CLOSED,
346     // since NetChannel::close cleans up state after calling handleClose;
347     // instead we pick up waiting requests in update()
348         if (!queuedRequests.empty() && (state == STATE_IDLE)) {
349             startNext();
350         } else {
351             idleTime.stamp();
352         }
353     }
354     
355     enum ConnectionState {
356         STATE_IDLE = 0,
357         STATE_GETTING_HEADERS,
358         STATE_GETTING_BODY,
359         STATE_GETTING_CHUNKED,
360         STATE_GETTING_CHUNKED_BYTES,
361         STATE_GETTING_TRAILER,
362         STATE_SOCKET_ERROR,
363         STATE_CLOSED             ///< connection should be closed now
364     };
365     
366     Client* client;
367     Request_ptr activeRequest;
368     ConnectionState state;
369     string host;
370     short port;
371     std::string buffer;
372     int bodyTransferSize;
373     SGTimeStamp idleTime;
374     bool chunkedTransfer;
375     
376     std::list<Request_ptr> queuedRequests;
377 };
378
379 Client::Client()
380 {
381     setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
382 }
383
384 void Client::update()
385 {
386     NetChannel::poll();
387         
388     ConnectionDict::iterator it = _connections.begin();
389     for (; it != _connections.end(); ) {
390         if (it->second->hasIdleTimeout() || it->second->hasError()) {
391         // connection has been idle for a while, clean it up
392         // (or has an error condition, again clean it up)
393             SG_LOG(SG_IO, SG_INFO, "cleaning up " << it->second);
394             ConnectionDict::iterator del = it++;
395             delete del->second;
396             _connections.erase(del);
397         } else {
398             if (it->second->shouldStartNext()) {
399                 it->second->startNext();
400             }
401             
402             ++it;
403         }
404     } // of connecion iteration
405 }
406
407 void Client::makeRequest(const Request_ptr& r)
408 {
409     string host = r->host();
410     int port = r->port();
411     if (!_proxy.empty()) {
412         host = _proxy;
413         port = _proxyPort;
414     }
415     
416     stringstream ss;
417     ss << host << "-" << port;
418     string connectionId = ss.str();
419     
420     if (_connections.find(connectionId) == _connections.end()) {
421         Connection* con = new Connection(this);
422         con->setServer(host, port);
423         _connections[connectionId] = con;
424     }
425     
426     _connections[connectionId]->queueRequest(r);
427 }
428
429 void Client::requestFinished(Connection* con)
430 {
431     
432 }
433
434 void Client::setUserAgent(const string& ua)
435 {
436     _userAgent = ua;
437 }
438
439 void Client::setProxy(const string& proxy, int port, const string& auth)
440 {
441     _proxy = proxy;
442     _proxyPort = port;
443     _proxyAuth = auth;
444 }
445
446 } // of namespace HTTP
447
448 } // of namespace simgear