2 * \file HTTPClient.cxx - simple HTTP client engine for SimHear
5 // Written by James Turner
7 // Copyright (C) 2013 James Turner <zakalawe@mac.com>
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Library General Public
11 // License as published by the Free Software Foundation; either
12 // version 2 of the License, or (at your option) any later version.
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Library General Public License for more details.
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 #include "HTTPClient.hxx"
26 #include "HTTPFileRequest.hxx"
30 #include <cstdlib> // rand()
36 #include <boost/foreach.hpp>
37 #include <boost/algorithm/string/case_conv.hpp>
39 #include <simgear/simgear_config.h>
41 #if defined(ENABLE_CURL)
42 #include <curl/multi.h>
44 #include <simgear/io/HTTPContentDecode.hxx>
47 #include <simgear/io/sg_netChat.hxx>
49 #include <simgear/misc/strutils.hxx>
50 #include <simgear/compiler.h>
51 #include <simgear/debug/logstream.hxx>
52 #include <simgear/timing/timestamp.hxx>
53 #include <simgear/structure/exception.hxx>
55 #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
58 # if !defined(SIMGEAR_VERSION)
59 # define SIMGEAR_VERSION "simgear-development"
69 extern const int DEFAULT_HTTP_PORT = 80;
70 const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
71 const unsigned int MAX_INFLIGHT_REQUESTS = 32;
74 typedef std::multimap<std::string, Connection*> ConnectionDict;
75 typedef std::list<Request_ptr> RequestList;
77 class Client::ClientPrivate
80 #if defined(ENABLE_CURL)
82 bool haveActiveRequests;
84 NetChannelPoller poller;
85 // connections by host (potentially more than one)
86 ConnectionDict connections;
89 std::string userAgent;
92 std::string proxyAuth;
93 unsigned int maxConnections;
95 RequestList pendingRequests;
99 SGTimeStamp timeTransferSample;
100 unsigned int bytesTransferred;
101 unsigned int lastTransferRate;
102 uint64_t totalBytesDownloaded;
105 #if !defined(ENABLE_CURL)
106 class Connection : public NetChat
109 Connection(Client* pr, const std::string& conId) :
112 port(DEFAULT_HTTP_PORT),
117 virtual ~Connection()
121 virtual void handleBufferRead (NetBuffer& buffer)
123 if( !activeRequest || !activeRequest->isComplete() )
124 return NetChat::handleBufferRead(buffer);
126 // Request should be aborted (signaled by setting its state to complete).
128 // force the state to GETTING_BODY, to simplify logic in
129 // responseComplete and handleClose
130 state = STATE_GETTING_BODY;
134 void setServer(const std::string& h, short p)
140 // socket-level errors
141 virtual void handleError(int error)
143 const char* errStr = strerror(error);
144 SG_LOG(SG_IO, SG_WARN, "HTTP Connection handleError:" << error << " ("
151 // connection level failure, eg name lookup or routing
152 // we won't have an active request yet, so let's fail all of the
153 // requests since we presume it's a systematic failure for
154 // the host in question
155 BOOST_FOREACH(Request_ptr req, sentRequests) {
156 req->setFailure(error, errStr);
159 BOOST_FOREACH(Request_ptr req, queuedRequests) {
160 req->setFailure(error, errStr);
163 sentRequests.clear();
164 queuedRequests.clear();
167 NetChat::handleError(error);
169 activeRequest->setFailure(error, errStr);
170 activeRequest = NULL;
171 _contentDecoder.reset();
174 state = STATE_SOCKET_ERROR;
179 handleError(ETIMEDOUT);
182 virtual void handleClose()
184 NetChat::handleClose();
186 // closing of the connection from the server side when getting the body,
187 bool canCloseState = (state == STATE_GETTING_BODY);
188 if (canCloseState && activeRequest) {
189 // force state here, so responseComplete can avoid closing the
191 state = STATE_CLOSED;
194 if (state == STATE_WAITING_FOR_RESPONSE) {
195 assert(!sentRequests.empty());
196 sentRequests.front()->setFailure(500, "server closed connection unexpectedly");
197 // no active request, but don't restore the front sent one
198 sentRequests.erase(sentRequests.begin());
202 activeRequest->setFailure(500, "server closed connection");
203 // remove the failed request from sentRequests, so it does
205 RequestList::iterator it = std::find(sentRequests.begin(),
206 sentRequests.end(), activeRequest);
207 if (it != sentRequests.end()) {
208 sentRequests.erase(it);
210 activeRequest = NULL;
211 _contentDecoder.reset();
214 state = STATE_CLOSED;
217 if (sentRequests.empty()) {
221 // restore sent requests to the queue, so they will be re-sent
222 // when the connection opens again
223 queuedRequests.insert(queuedRequests.begin(),
224 sentRequests.begin(), sentRequests.end());
225 sentRequests.clear();
228 void queueRequest(const Request_ptr& r)
230 queuedRequests.push_back(r);
231 tryStartNextRequest();
236 assert(!sentRequests.empty());
237 assert(state == STATE_WAITING_FOR_RESPONSE);
239 activeRequest = sentRequests.front();
241 activeRequest->responseStart(buffer);
242 } catch (sg_exception& e) {
247 state = STATE_GETTING_HEADERS;
249 if (activeRequest->responseCode() == 204) {
250 noMessageBody = true;
251 } else if (activeRequest->method() == "HEAD") {
252 noMessageBody = true;
254 noMessageBody = false;
257 bodyTransferSize = -1;
258 chunkedTransfer = false;
259 _contentDecoder.reset();
262 void tryStartNextRequest()
264 while( !queuedRequests.empty()
265 && queuedRequests.front()->isComplete() )
266 queuedRequests.pop_front();
268 if (queuedRequests.empty()) {
273 if (sentRequests.size() > MAX_INFLIGHT_REQUESTS) {
277 if (state == STATE_CLOSED) {
278 if (!connectToHost()) {
283 setTerminator("\r\n");
287 Request_ptr r = queuedRequests.front();
290 std::stringstream headerData;
291 std::string path = r->path();
292 assert(!path.empty());
293 std::string query = r->query();
294 std::string bodyData;
296 if (!client->proxyHost().empty()) {
297 path = r->scheme() + "://" + r->host() + r->path();
300 if (r->bodyType() == CONTENT_TYPE_URL_ENCODED) {
301 headerData << r->method() << " " << path << " HTTP/1.1\r\n";
302 bodyData = query.substr(1); // URL-encode, drop the leading '?'
303 headerData << "Content-Type:" << CONTENT_TYPE_URL_ENCODED << "\r\n";
304 headerData << "Content-Length:" << bodyData.size() << "\r\n";
306 headerData << r->method() << " " << path << query << " HTTP/1.1\r\n";
307 if( r->hasBodyData() )
309 headerData << "Content-Length:" << r->bodyLength() << "\r\n";
310 headerData << "Content-Type:" << r->bodyType() << "\r\n";
314 headerData << "Host: " << r->hostAndPort() << "\r\n";
315 headerData << "User-Agent:" << client->userAgent() << "\r\n";
316 headerData << "Accept-Encoding: deflate, gzip\r\n";
317 if (!client->proxyAuth().empty()) {
318 headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
321 BOOST_FOREACH(const StringMap::value_type& h, r->requestHeaders()) {
322 headerData << h.first << ": " << h.second << "\r\n";
325 headerData << "\r\n"; // final CRLF to terminate the headers
326 if (!bodyData.empty()) {
327 headerData << bodyData;
330 bool ok = push(headerData.str().c_str());
332 SG_LOG(SG_IO, SG_WARN, "HTTPClient: over-stuffed the socket");
333 // we've over-stuffed the socket, give up for now, let things
334 // drain down before trying to start any more requests.
338 if( r->hasBodyData() )
339 for(size_t body_bytes_sent = 0; body_bytes_sent < r->bodyLength();)
342 size_t len = r->getBodyData(buf, body_bytes_sent, 4096);
345 if( !bufferSend(buf, len) )
349 "overflow the HTTP::Connection output buffer");
350 state = STATE_SOCKET_ERROR;
353 body_bytes_sent += len;
359 "HTTP asynchronous request body generation is unsupported");
364 SG_LOG(SG_IO, SG_DEBUG, "con:" << connectionId << " did start request:" << r->url());
365 // successfully sent, remove from queue, and maybe send the next
366 queuedRequests.pop_front();
367 sentRequests.push_back(r);
368 state = STATE_WAITING_FOR_RESPONSE;
370 // pipelining, let's maybe send the next request right away
371 tryStartNextRequest();
374 virtual void collectIncomingData(const char* s, int n)
377 client->receivedBytes(static_cast<unsigned int>(n));
379 if( (state == STATE_GETTING_BODY)
380 || (state == STATE_GETTING_CHUNKED_BYTES) )
381 _contentDecoder.receivedBytes(s, n);
386 virtual void foundTerminator(void)
390 case STATE_WAITING_FOR_RESPONSE:
394 case STATE_GETTING_HEADERS:
399 case STATE_GETTING_BODY:
403 case STATE_GETTING_CHUNKED:
404 processChunkHeader();
407 case STATE_GETTING_CHUNKED_BYTES:
408 setTerminator("\r\n");
409 state = STATE_GETTING_CHUNKED;
414 case STATE_GETTING_TRAILER:
420 SG_LOG(SG_IO, SG_WARN, "HTTP got data in IDLE state, bad server?");
427 bool hasIdleTimeout() const
429 if ((state != STATE_IDLE) && (state != STATE_CLOSED)) {
433 assert(sentRequests.empty());
434 bool isTimedOut = (idleTime.elapsedMSec() > (1000 * 10)); // 10 seconds
438 bool hasErrorTimeout() const
440 if ((state == STATE_IDLE) || (state == STATE_CLOSED)) {
444 bool isTimedOut = (idleTime.elapsedMSec() > (1000 * 30)); // 30 seconds
448 bool hasError() const
450 return (state == STATE_SOCKET_ERROR);
453 bool shouldStartNext() const
455 return !queuedRequests.empty() && (sentRequests.size() < MAX_INFLIGHT_REQUESTS);
458 bool isActive() const
460 return !queuedRequests.empty() || !sentRequests.empty();
463 void debugDumpRequests() const
465 SG_LOG(SG_IO, SG_DEBUG, "requests for:" << host << ":" << port << " (conId=" << connectionId
466 << "; state=" << state << ")");
468 SG_LOG(SG_IO, SG_DEBUG, "\tactive:" << activeRequest->url());
470 SG_LOG(SG_IO, SG_DEBUG, "\tNo active request");
473 BOOST_FOREACH(Request_ptr req, sentRequests) {
474 SG_LOG(SG_IO, SG_DEBUG, "\tsent:" << req->url());
477 BOOST_FOREACH(Request_ptr req, queuedRequests) {
478 SG_LOG(SG_IO, SG_DEBUG, "\tqueued:" << req->url());
484 SG_LOG(SG_IO, SG_DEBUG, "HTTP connecting to " << host << ":" << port);
487 SG_LOG(SG_IO, SG_WARN, "HTTP::Connection: connectToHost: open() failed");
491 if (connect(host.c_str(), port) != 0) {
492 SG_LOG(SG_IO, SG_WARN, "HTTP::Connection: connectToHost: connect() failed");
502 std::string h = strutils::simplify(buffer);
503 if (h.empty()) { // blank line terminates headers
508 int colonPos = buffer.find(':');
510 SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h);
514 std::string key = strutils::simplify(buffer.substr(0, colonPos));
515 std::string lkey = boost::to_lower_copy(key);
516 std::string value = strutils::strip(buffer.substr(colonPos + 1));
518 // only consider these if getting headers (as opposed to trailers
519 // of a chunked transfer)
520 if (state == STATE_GETTING_HEADERS) {
521 if (lkey == "content-length") {
523 int sz = strutils::to_int(value);
524 if (bodyTransferSize <= 0) {
525 bodyTransferSize = sz;
527 activeRequest->setResponseLength(sz);
528 } else if (lkey == "transfer-length") {
529 bodyTransferSize = strutils::to_int(value);
530 } else if (lkey == "transfer-encoding") {
531 processTransferEncoding(value);
532 } else if (lkey == "content-encoding") {
533 _contentDecoder.setEncoding(value);
537 activeRequest->responseHeader(lkey, value);
540 void processTransferEncoding(const std::string& te)
542 if (te == "chunked") {
543 chunkedTransfer = true;
545 SG_LOG(SG_IO, SG_WARN, "unsupported transfer encoding:" << te);
550 void processChunkHeader()
552 if (buffer.empty()) {
553 // blank line after chunk data
558 int semiPos = buffer.find(';');
560 // extensions ignored for the moment
561 chunkSize = strutils::to_int(buffer.substr(0, semiPos), 16);
563 chunkSize = strutils::to_int(buffer, 16);
567 if (chunkSize == 0) { // trailer start
568 state = STATE_GETTING_TRAILER;
572 state = STATE_GETTING_CHUNKED_BYTES;
573 setByteCount(chunkSize);
576 void processTrailer()
578 if (buffer.empty()) {
584 // process as a normal header
588 void headersComplete()
590 activeRequest->responseHeadersComplete();
591 _contentDecoder.initWithRequest(activeRequest);
593 if (chunkedTransfer) {
594 state = STATE_GETTING_CHUNKED;
595 } else if (noMessageBody || (bodyTransferSize == 0)) {
596 // force the state to GETTING_BODY, to simplify logic in
597 // responseComplete and handleClose
598 state = STATE_GETTING_BODY;
601 setByteCount(bodyTransferSize); // may be -1, that's fine
602 state = STATE_GETTING_BODY;
606 void responseComplete()
608 Request_ptr completedRequest = activeRequest;
609 _contentDecoder.finish();
611 assert(sentRequests.front() == activeRequest);
612 sentRequests.pop_front();
613 bool doClose = activeRequest->closeAfterComplete();
614 activeRequest = NULL;
616 if ((state == STATE_GETTING_BODY) || (state == STATE_GETTING_TRAILER)) {
618 // this will bring us into handleClose() above, which updates
619 // state to STATE_CLOSED
622 // if we have additional requests waiting, try to start them now
623 tryStartNextRequest();
627 if (state != STATE_CLOSED) {
628 state = sentRequests.empty() ? STATE_IDLE : STATE_WAITING_FOR_RESPONSE;
631 // notify request after we change state, so this connection is idle
632 // if completion triggers other requests (which is likely)
633 completedRequest->responseComplete();
634 client->requestFinished(this);
636 setTerminator("\r\n");
639 enum ConnectionState {
641 STATE_WAITING_FOR_RESPONSE,
642 STATE_GETTING_HEADERS,
644 STATE_GETTING_CHUNKED,
645 STATE_GETTING_CHUNKED_BYTES,
646 STATE_GETTING_TRAILER,
648 STATE_CLOSED ///< connection should be closed now
652 Request_ptr activeRequest;
653 ConnectionState state;
657 int bodyTransferSize;
658 SGTimeStamp idleTime;
659 bool chunkedTransfer;
662 RequestList queuedRequests;
663 RequestList sentRequests;
665 ContentDecoder _contentDecoder;
666 std::string connectionId;
668 #endif // of !ENABLE_CURL
674 d->maxConnections = 4;
675 d->bytesTransferred = 0;
676 d->lastTransferRate = 0;
677 d->timeTransferSample.stamp();
678 d->totalBytesDownloaded = 0;
680 setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
681 #if defined(ENABLE_CURL)
682 static bool didInitCurlGlobal = false;
683 if (!didInitCurlGlobal) {
684 curl_global_init(CURL_GLOBAL_ALL);
685 didInitCurlGlobal = true;
688 d->curlMulti = curl_multi_init();
694 #if defined(ENABLE_CURL)
695 curl_multi_cleanup(d->curlMulti);
699 void Client::setMaxConnections(unsigned int maxCon)
702 throw sg_range_exception("illegal HTTP::Client::setMaxConnections value");
705 d->maxConnections = maxCon;
706 #if defined(ENABLE_CURL)
707 curl_multi_setopt(d->curlMulti, CURLMOPT_MAXCONNECTS, (long) maxCon);
711 void Client::update(int waitTimeout)
713 #if defined(ENABLE_CURL)
714 int remainingActive, messagesInQueue;
715 curl_multi_perform(d->curlMulti, &remainingActive);
716 d->haveActiveRequests = (remainingActive > 0);
719 while ((msg = curl_multi_info_read(d->curlMulti, &messagesInQueue))) {
720 if (msg->msg == CURLMSG_DONE) {
722 CURL *e = msg->easy_handle;
723 curl_easy_getinfo(e, CURLINFO_PRIVATE, &req);
726 curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &responseCode);
728 if (msg->data.result == 0) {
729 req->responseComplete();
731 fprintf(stderr, "Result: %d - %s\n",
732 msg->data.result, curl_easy_strerror(msg->data.result));
733 req->setFailure(msg->data.result, curl_easy_strerror(msg->data.result));
736 curl_multi_remove_handle(d->curlMulti, e);
738 // balance the reference we take in makeRequest
739 SGReferenced::put(req);
740 curl_easy_cleanup(e);
743 SG_LOG(SG_IO, SG_ALERT, "CurlMSG:" << msg->msg);
745 } // of curl message processing loop
747 if (!d->poller.hasChannels() && (waitTimeout > 0)) {
748 SGTimeStamp::sleepForMSec(waitTimeout);
750 d->poller.poll(waitTimeout);
753 bool waitingRequests = !d->pendingRequests.empty();
754 ConnectionDict::iterator it = d->connections.begin();
755 for (; it != d->connections.end(); ) {
756 Connection* con = it->second;
757 if (con->hasIdleTimeout() ||
759 con->hasErrorTimeout() ||
760 (!con->isActive() && waitingRequests))
762 if (con->hasErrorTimeout()) {
763 // tell the connection we're timing it out
764 con->handleTimeout();
767 // connection has been idle for a while, clean it up
768 // (or if we have requests waiting for a different host,
769 // or an error condition
770 ConnectionDict::iterator del = it++;
772 d->connections.erase(del);
774 if (it->second->shouldStartNext()) {
775 it->second->tryStartNextRequest();
779 } // of connection iteration
781 if (waitingRequests && (d->connections.size() < d->maxConnections)) {
782 RequestList waiting(d->pendingRequests);
783 d->pendingRequests.clear();
785 // re-submit all waiting requests in order; this takes care of
786 // finding multiple pending items targetted to the same (new)
788 BOOST_FOREACH(Request_ptr req, waiting) {
795 void Client::makeRequest(const Request_ptr& r)
797 if( r->isComplete() )
800 if( r->url().find("://") == std::string::npos ) {
801 r->setFailure(EINVAL, "malformed URL");
805 #if defined(ENABLE_CURL)
806 CURL* curlRequest = curl_easy_init();
807 curl_easy_setopt(curlRequest, CURLOPT_URL, r->url().c_str());
809 // manually increase the ref count of the request
810 SGReferenced::get(r.get());
811 curl_easy_setopt(curlRequest, CURLOPT_PRIVATE, r.get());
812 // disable built-in libCurl progress feedback
813 curl_easy_setopt(curlRequest, CURLOPT_NOPROGRESS, 1);
815 curl_easy_setopt(curlRequest, CURLOPT_WRITEFUNCTION, requestWriteCallback);
816 curl_easy_setopt(curlRequest, CURLOPT_WRITEDATA, r.get());
817 curl_easy_setopt(curlRequest, CURLOPT_HEADERFUNCTION, requestHeaderCallback);
818 curl_easy_setopt(curlRequest, CURLOPT_HEADERDATA, r.get());
820 curl_easy_setopt(curlRequest, CURLOPT_USERAGENT, d->userAgent.c_str());
822 if (!d->proxy.empty()) {
823 curl_easy_setopt(curlRequest, CURLOPT_PROXY, d->proxy.c_str());
824 curl_easy_setopt(curlRequest, CURLOPT_PROXYPORT, d->proxyPort);
826 if (!d->proxyAuth.empty()) {
827 curl_easy_setopt(curlRequest, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
828 curl_easy_setopt(curlRequest, CURLOPT_PROXYUSERPWD, d->proxyAuth.c_str());
832 std::string method = boost::to_lower_copy(r->method());
833 if (method == "get") {
834 curl_easy_setopt(curlRequest, CURLOPT_HTTPGET, 1);
835 } else if (method == "put") {
836 curl_easy_setopt(curlRequest, CURLOPT_PUT, 1);
837 curl_easy_setopt(curlRequest, CURLOPT_UPLOAD, 1);
838 } else if (method == "post") {
839 // see http://curl.haxx.se/libcurl/c/CURLOPT_POST.html
840 curl_easy_setopt(curlRequest, CURLOPT_HTTPPOST, 1);
842 std::string q = r->query().substr(1);
843 curl_easy_setopt(curlRequest, CURLOPT_COPYPOSTFIELDS, q.c_str());
845 // reset URL to exclude query pieces
846 std::string urlWithoutQuery = r->url();
847 std::string::size_type queryPos = urlWithoutQuery.find('?');
848 urlWithoutQuery.resize(queryPos);
849 curl_easy_setopt(curlRequest, CURLOPT_URL, urlWithoutQuery.c_str());
851 curl_easy_setopt(curlRequest, CURLOPT_CUSTOMREQUEST, r->method().c_str());
854 struct curl_slist* headerList = NULL;
855 if (r->hasBodyData() && (method != "post")) {
856 curl_easy_setopt(curlRequest, CURLOPT_UPLOAD, 1);
857 curl_easy_setopt(curlRequest, CURLOPT_INFILESIZE, r->bodyLength());
858 curl_easy_setopt(curlRequest, CURLOPT_READFUNCTION, requestReadCallback);
859 curl_easy_setopt(curlRequest, CURLOPT_READDATA, r.get());
860 std::string h = "Content-Type:" + r->bodyType();
861 headerList = curl_slist_append(headerList, h.c_str());
864 StringMap::const_iterator it;
865 for (it = r->requestHeaders().begin(); it != r->requestHeaders().end(); ++it) {
866 std::string h = it->first + ": " + it->second;
867 headerList = curl_slist_append(headerList, h.c_str());
870 if (headerList != NULL) {
871 curl_easy_setopt(curlRequest, CURLOPT_HTTPHEADER, headerList);
874 curl_multi_add_handle(d->curlMulti, curlRequest);
875 d->haveActiveRequests = true;
877 // FIXME - premature?
881 if( r->url().find("http://") != 0 ) {
882 r->setFailure(EINVAL, "only HTTP protocol is supported");
886 std::string host = r->host();
887 int port = r->port();
888 if (!d->proxy.empty()) {
893 Connection* con = NULL;
894 std::stringstream ss;
895 ss << host << "-" << port;
896 std::string connectionId = ss.str();
897 bool havePending = !d->pendingRequests.empty();
898 bool atConnectionsLimit = d->connections.size() >= d->maxConnections;
899 ConnectionDict::iterator consEnd = d->connections.end();
901 // assign request to an existing Connection.
902 // various options exist here, examined in order
903 ConnectionDict::iterator it = d->connections.find(connectionId);
904 if (atConnectionsLimit && (it == consEnd)) {
905 // maximum number of connections active, queue this request
906 // when a connection goes inactive, we'll start this one
907 d->pendingRequests.push_back(r);
911 // scan for an idle Connection to the same host (likely if we're
912 // retrieving multiple resources from the same host in quick succession)
913 // if we have pending requests (waiting for a free Connection), then
914 // force new requests on this id to always use the first Connection
915 // (instead of the random selection below). This ensures that when
916 // there's pressure on the number of connections to keep alive, one
917 // host can't DoS every other.
919 for (; (it != consEnd) && (it->first == connectionId); ++it, ++count) {
920 if (havePending || !it->second->isActive()) {
926 if (!con && atConnectionsLimit) {
927 // all current connections are busy (active), and we don't
928 // have free connections to allocate, so let's assign to
929 // an existing one randomly. Ideally we'd used whichever one will
930 // complete first but we don't have that info.
931 int index = rand() % count;
932 for (it = d->connections.find(connectionId); index > 0; --index) { ; }
936 // allocate a new connection object
938 con = new Connection(this, connectionId);
939 con->setServer(host, port);
940 d->poller.addChannel(con);
941 d->connections.insert(d->connections.end(),
942 ConnectionDict::value_type(connectionId, con));
945 con->queueRequest(r);
949 //------------------------------------------------------------------------------
950 FileRequestRef Client::save( const std::string& url,
951 const std::string& filename )
953 FileRequestRef req = new FileRequest(url, filename);
958 //------------------------------------------------------------------------------
959 MemoryRequestRef Client::load(const std::string& url)
961 MemoryRequestRef req = new MemoryRequest(url);
966 void Client::requestFinished(Connection* con)
971 void Client::setUserAgent(const std::string& ua)
976 const std::string& Client::userAgent() const
981 const std::string& Client::proxyHost() const
986 const std::string& Client::proxyAuth() const
991 void Client::setProxy( const std::string& proxy,
993 const std::string& auth )
1000 bool Client::hasActiveRequests() const
1002 #if defined(ENABLE_CURL)
1003 return d->haveActiveRequests;
1005 ConnectionDict::const_iterator it = d->connections.begin();
1006 for (; it != d->connections.end(); ++it) {
1007 if (it->second->isActive()) return true;
1014 void Client::receivedBytes(unsigned int count)
1016 d->bytesTransferred += count;
1017 d->totalBytesDownloaded += count;
1020 unsigned int Client::transferRateBytesPerSec() const
1022 unsigned int e = d->timeTransferSample.elapsedMSec();
1024 // too long a window, ignore
1025 d->timeTransferSample.stamp();
1026 d->bytesTransferred = 0;
1027 d->lastTransferRate = 0;
1031 if (e < 100) { // avoid really narrow windows
1032 return d->lastTransferRate;
1035 unsigned int ratio = (d->bytesTransferred * 1000) / e;
1036 // run a low-pass filter
1037 unsigned int smoothed = ((400 - e) * d->lastTransferRate) + (e * ratio);
1040 d->timeTransferSample.stamp();
1041 d->bytesTransferred = 0;
1042 d->lastTransferRate = smoothed;
1046 uint64_t Client::totalBytesDownloaded() const
1048 return d->totalBytesDownloaded;
1051 size_t Client::requestWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
1053 size_t byteSize = size * nmemb;
1055 Request* req = static_cast<Request*>(userdata);
1056 req->processBodyBytes(ptr, byteSize);
1060 size_t Client::requestReadCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
1062 size_t maxBytes = size * nmemb;
1063 Request* req = static_cast<Request*>(userdata);
1064 size_t actualBytes = req->getBodyData(ptr, 0, maxBytes);
1068 size_t Client::requestHeaderCallback(char *rawBuffer, size_t size, size_t nitems, void *userdata)
1070 size_t byteSize = size * nitems;
1071 Request* req = static_cast<Request*>(userdata);
1072 std::string h = strutils::simplify(std::string(rawBuffer, byteSize));
1074 if (req->readyState() == HTTP::Request::OPENED) {
1075 req->responseStart(h);
1080 // got a 100-continue reponse; restart
1081 if (req->responseCode() == 100) {
1082 req->setReadyState(HTTP::Request::OPENED);
1086 req->responseHeadersComplete();
1090 if (req->responseCode() == 100) {
1091 return byteSize; // skip headers associated with 100-continue status
1094 size_t colonPos = h.find(':');
1095 if (colonPos == std::string::npos) {
1096 SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h);
1100 std::string key = strutils::simplify(h.substr(0, colonPos));
1101 std::string lkey = boost::to_lower_copy(key);
1102 std::string value = strutils::strip(h.substr(colonPos + 1));
1104 req->responseHeader(lkey, value);
1108 void Client::debugDumpRequests()
1110 #if defined(ENABLE_CURL)
1113 SG_LOG(SG_IO, SG_INFO, "== HTTP connection dump");
1114 ConnectionDict::iterator it = d->connections.begin();
1115 for (; it != d->connections.end(); ++it) {
1116 it->second->debugDumpRequests();
1118 SG_LOG(SG_IO, SG_INFO, "==");
1122 } // of namespace HTTP
1124 } // of namespace simgear