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