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