1 #include "HTTPClient.hxx"
7 #include <boost/foreach.hpp>
8 #include <boost/algorithm/string/case_conv.hpp>
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>
16 #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
19 # if !defined(SIMGEAR_VERSION)
20 # define SIMGEAR_VERSION "simgear-development"
25 using std::stringstream;
34 extern const int DEFAULT_HTTP_PORT = 80;
36 class Connection : public NetChat
39 Connection(Client* pr) :
43 setTerminator("\r\n");
46 bool connectToHost(const string& host, short port)
49 SG_LOG(SG_ALL, SG_WARN, "HTTP::Connection: connectToHost: open() failed");
53 if (connect(host.c_str(), port) != 0) {
60 // socket-level errors
61 virtual void handleError(int error)
63 NetChat::handleError(error);
65 activeRequest->setFailure(error, "socket error");
69 state = STATE_SOCKET_ERROR;
72 void queueRequest(const Request_ptr& r)
77 queuedRequests.push_back(r);
81 void startRequest(const Request_ptr& r)
86 chunkedTransfer = false;
88 stringstream headerData;
89 string path = r->path();
90 if (!client->proxyHost().empty()) {
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";
101 BOOST_FOREACH(string h, r->requestHeaders()) {
102 headerData << h << ": " << r->header(h) << "\r\n";
105 headerData << "\r\n"; // final CRLF to terminate the headers
107 // TODO - add request body support for PUT, etc operations
109 push(headerData.str().c_str());
112 virtual void collectIncomingData(const char* s, int n)
114 if ((state == STATE_GETTING_BODY) || (state == STATE_GETTING_CHUNKED_BYTES)) {
115 activeRequest->processBodyBytes(s, n);
117 buffer += string(s, n);
121 virtual void foundTerminator(void)
125 activeRequest->responseStart(buffer);
126 state = STATE_GETTING_HEADERS;
130 case STATE_GETTING_HEADERS:
135 case STATE_GETTING_BODY:
139 case STATE_GETTING_CHUNKED:
140 processChunkHeader();
143 case STATE_GETTING_CHUNKED_BYTES:
144 setTerminator("\r\n");
145 state = STATE_GETTING_CHUNKED;
148 case STATE_GETTING_TRAILER:
158 bool hasIdleTimeout() const
160 if (state != STATE_IDLE) {
164 return idleTime.elapsedMSec() > 1000 * 10; // ten seconds
167 bool hasError() const
169 return (state == STATE_SOCKET_ERROR);
174 string h = strutils::simplify(buffer);
175 if (h.empty()) { // blank line terminates headers
178 if (chunkedTransfer) {
179 state = STATE_GETTING_CHUNKED;
180 } else if (bodyTransferSize > 0) {
181 state = STATE_GETTING_BODY;
182 setByteCount(bodyTransferSize);
189 int colonPos = buffer.find(':');
191 SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h);
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));
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;
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);
215 activeRequest->responseHeader(lkey, value);
218 void processTransferEncoding(const string& te)
220 if (te == "chunked") {
221 chunkedTransfer = true;
223 SG_LOG(SG_IO, SG_WARN, "unsupported transfer encoding:" << te);
228 void processChunkHeader()
230 if (buffer.empty()) {
231 // blank line after chunk data
236 int semiPos = buffer.find(';');
238 // extensions ignored for the moment
239 chunkSize = strutils::to_int(buffer.substr(0, semiPos), 16);
241 chunkSize = strutils::to_int(buffer, 16);
245 if (chunkSize == 0) { // trailer start
246 state = STATE_GETTING_TRAILER;
250 state = STATE_GETTING_CHUNKED_BYTES;
251 setByteCount(chunkSize);
254 void processTrailer()
256 if (buffer.empty()) {
262 // process as a normal header
266 void headersComplete()
268 activeRequest->responseHeadersComplete();
271 void responseComplete()
273 activeRequest->responseComplete();
274 client->requestFinished(this);
275 activeRequest = NULL;
278 setTerminator("\r\n");
280 if (!queuedRequests.empty()) {
281 Request_ptr next = queuedRequests.front();
282 queuedRequests.pop_front();
289 enum ConnectionState {
291 STATE_GETTING_HEADERS,
293 STATE_GETTING_CHUNKED,
294 STATE_GETTING_CHUNKED_BYTES,
295 STATE_GETTING_TRAILER,
300 Request_ptr activeRequest;
301 ConnectionState state;
303 int bodyTransferSize;
304 SGTimeStamp idleTime;
305 bool chunkedTransfer;
307 std::list<Request_ptr> queuedRequests;
312 setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
315 void Client::update()
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++;
325 _connections.erase(del);
329 } // of connecion iteration
332 void Client::makeRequest(const Request_ptr& r)
334 string host = r->host();
335 int port = r->port();
336 if (!_proxy.empty()) {
342 ss << host << "-" << port;
343 string connectionId = ss.str();
345 if (_connections.find(connectionId) == _connections.end()) {
346 Connection* con = new Connection(this);
347 bool ok = con->connectToHost(host, port);
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 << ")");
355 r->setFailure(-1, "unable to connect to host");
359 _connections[connectionId] = con;
362 _connections[connectionId]->queueRequest(r);
365 void Client::requestFinished(Connection* con)
370 void Client::setUserAgent(const string& ua)
375 void Client::setProxy(const string& proxy, int port, const string& auth)
382 } // of namespace HTTP
384 } // of namespace simgear