]> git.mxchange.org Git - simgear.git/blob - simgear/io/HTTPClient.cxx
Tweaks to HTTP code, in preparation for using it for metar - especially, test code...
[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 " __DATE__
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     void connectToHost(const string& host, short port)
47     {
48         open();
49         connect(host.c_str(), port);
50     }
51     
52     void queueRequest(const Request_ptr& r)
53     {
54         if (!activeRequest) {
55             startRequest(r);
56         } else {
57             queuedRequests.push_back(r);
58         }
59     }
60     
61     void startRequest(const Request_ptr& r)
62     {
63         activeRequest = r;
64         state = STATE_IDLE;
65         bodyTransferSize = 0;
66         
67         stringstream headerData;
68         string path = r->path();
69         if (!client->proxyHost().empty()) {
70             path = "http://" + r->hostAndPort() + path;
71         }
72
73         headerData << r->method() << " " << path << " HTTP/1.1 " << client->userAgent() << "\r\n";
74         headerData << "Host: " << r->hostAndPort() << "\r\n";
75
76         if (!client->proxyAuth().empty()) {
77             headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
78         }
79
80         BOOST_FOREACH(string h, r->requestHeaders()) {
81             headerData << h << ": " << r->header(h) << "\r\n";
82         }
83
84         headerData << "\r\n"; // final CRLF to terminate the headers
85
86     // TODO - add request body support for PUT, etc operations
87
88         push(headerData.str().c_str());
89     }
90     
91     virtual void collectIncomingData(const char* s, int n)
92     {
93         if (state == STATE_GETTING_BODY) {
94             activeRequest->gotBodyData(s, n);
95         } else {
96             buffer += string(s, n);
97         }
98     }
99     
100     virtual void foundTerminator(void)
101     {
102         switch (state) {
103         case STATE_IDLE:
104             activeRequest->responseStart(buffer);
105             state = STATE_GETTING_HEADERS;
106             buffer.clear();
107             break;
108             
109         case STATE_GETTING_HEADERS:
110             processHeader();
111             buffer.clear();
112             break;
113             
114         case STATE_GETTING_BODY:
115             responseComplete();
116             state = STATE_IDLE;
117             setTerminator("\r\n");
118             
119             if (!queuedRequests.empty()) {
120                 Request_ptr next = queuedRequests.front();
121                 queuedRequests.pop_front();
122                 startRequest(next);
123             } else {
124                 idleTime.stamp();
125             }
126             
127             break;
128         }
129     }
130     
131     bool hasIdleTimeout() const
132     {
133         if (state != STATE_IDLE) {
134             return false;
135         }
136         
137         return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
138     }
139 private:
140     void processHeader()
141     {
142         string h = strutils::simplify(buffer);
143         if (h.empty()) { // blank line terminates headers
144             headersComplete();
145             
146             if (bodyTransferSize > 0) {
147                 state = STATE_GETTING_BODY;
148                 setByteCount(bodyTransferSize);
149             } else {
150                 responseComplete();
151                 state = STATE_IDLE; // no response body, we're done
152             }
153             return;
154         }
155         
156         int colonPos = buffer.find(':');
157         if (colonPos < 0) {
158             SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h);
159             return;
160         }
161         
162         string key = strutils::simplify(buffer.substr(0, colonPos));
163         string lkey = boost::to_lower_copy(key);
164         string value = strutils::strip(buffer.substr(colonPos + 1));
165         
166         if (lkey == "content-length" && (bodyTransferSize <= 0)) {
167             bodyTransferSize = strutils::to_int(value);
168         } else if (lkey == "transfer-length") {
169             bodyTransferSize = strutils::to_int(value);
170         }
171         
172         activeRequest->responseHeader(lkey, value);
173     }
174     
175     void headersComplete()
176     {
177         activeRequest->responseHeadersComplete();
178     }
179     
180     void responseComplete()
181     {
182         activeRequest->responseComplete();
183         client->requestFinished(this);
184         activeRequest = NULL;
185     }
186     
187     enum ConnectionState {
188         STATE_IDLE = 0,
189         STATE_GETTING_HEADERS,
190         STATE_GETTING_BODY
191     };
192     
193     Client* client;
194     Request_ptr activeRequest;
195     ConnectionState state;
196     std::string buffer;
197     int bodyTransferSize;
198     SGTimeStamp idleTime;
199     
200     std::list<Request_ptr> queuedRequests;
201 };
202
203 Client::Client()
204 {
205     setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
206 }
207
208 void Client::update()
209 {
210     ConnectionDict::iterator it = _connections.begin();
211     for (; it != _connections.end(); ) {
212         if (it->second->hasIdleTimeout()) {
213         // connection has been idle for a while, clean it up
214             ConnectionDict::iterator del = it++;
215             delete del->second;
216             _connections.erase(del);
217         } else {
218             ++it;
219         }
220     } // of connecion iteration
221 }
222
223 void Client::makeRequest(const Request_ptr& r)
224 {
225     string host = r->hostAndPort();
226     if (!_proxy.empty()) {
227         host = _proxy;
228     }
229     
230     if (_connections.find(host) == _connections.end()) {
231         Connection* con = new Connection(this);
232         con->connectToHost(r->host(), r->port());
233         _connections[host] = con;
234     }
235     
236     _connections[host]->queueRequest(r);
237 }
238
239 void Client::requestFinished(Connection* con)
240 {
241     
242 }
243
244 void Client::setUserAgent(const string& ua)
245 {
246     _userAgent = ua;
247 }
248
249 void Client::setProxy(const string& proxy, const string& auth)
250 {
251     _proxy = proxy;
252     _proxyAuth = auth;
253 }
254
255 } // of namespace HTTP
256
257 } // of namespace simgear