+ if( r->isComplete() )
+ return;
+
+ if( r->url().find("://") == std::string::npos ) {
+ r->setFailure(EINVAL, "malformed URL");
+ return;
+ }
+
+ r->_client = this;
+
+#if defined(ENABLE_CURL)
+ ClientPrivate::RequestCurlMap::iterator rit = d->requests.find(r);
+ assert(rit == d->requests.end());
+
+ CURL* curlRequest = curl_easy_init();
+ curl_easy_setopt(curlRequest, CURLOPT_URL, r->url().c_str());
+
+ d->requests[r] = curlRequest;
+
+ curl_easy_setopt(curlRequest, CURLOPT_PRIVATE, r.get());
+ // disable built-in libCurl progress feedback
+ curl_easy_setopt(curlRequest, CURLOPT_NOPROGRESS, 1);
+
+ curl_easy_setopt(curlRequest, CURLOPT_WRITEFUNCTION, requestWriteCallback);
+ curl_easy_setopt(curlRequest, CURLOPT_WRITEDATA, r.get());
+ curl_easy_setopt(curlRequest, CURLOPT_HEADERFUNCTION, requestHeaderCallback);
+ curl_easy_setopt(curlRequest, CURLOPT_HEADERDATA, r.get());
+
+ curl_easy_setopt(curlRequest, CURLOPT_USERAGENT, d->userAgent.c_str());
+ curl_easy_setopt(curlRequest, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+
+ if (!d->proxy.empty()) {
+ curl_easy_setopt(curlRequest, CURLOPT_PROXY, d->proxy.c_str());
+ curl_easy_setopt(curlRequest, CURLOPT_PROXYPORT, d->proxyPort);
+
+ if (!d->proxyAuth.empty()) {
+ curl_easy_setopt(curlRequest, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
+ curl_easy_setopt(curlRequest, CURLOPT_PROXYUSERPWD, d->proxyAuth.c_str());
+ }
+ }
+
+ std::string method = boost::to_lower_copy(r->method());
+ if (method == "get") {
+ curl_easy_setopt(curlRequest, CURLOPT_HTTPGET, 1);
+ } else if (method == "put") {
+ curl_easy_setopt(curlRequest, CURLOPT_PUT, 1);
+ curl_easy_setopt(curlRequest, CURLOPT_UPLOAD, 1);
+ } else if (method == "post") {
+ // see http://curl.haxx.se/libcurl/c/CURLOPT_POST.html
+ curl_easy_setopt(curlRequest, CURLOPT_HTTPPOST, 1);
+
+ std::string q = r->query().substr(1);
+ curl_easy_setopt(curlRequest, CURLOPT_COPYPOSTFIELDS, q.c_str());
+
+ // reset URL to exclude query pieces
+ std::string urlWithoutQuery = r->url();
+ std::string::size_type queryPos = urlWithoutQuery.find('?');
+ urlWithoutQuery.resize(queryPos);
+ curl_easy_setopt(curlRequest, CURLOPT_URL, urlWithoutQuery.c_str());
+ } else {
+ curl_easy_setopt(curlRequest, CURLOPT_CUSTOMREQUEST, r->method().c_str());
+ }
+
+ struct curl_slist* headerList = NULL;
+ if (r->hasBodyData() && (method != "post")) {
+ curl_easy_setopt(curlRequest, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(curlRequest, CURLOPT_INFILESIZE, r->bodyLength());
+ curl_easy_setopt(curlRequest, CURLOPT_READFUNCTION, requestReadCallback);
+ curl_easy_setopt(curlRequest, CURLOPT_READDATA, r.get());
+ std::string h = "Content-Type:" + r->bodyType();
+ headerList = curl_slist_append(headerList, h.c_str());
+ }
+
+ StringMap::const_iterator it;
+ for (it = r->requestHeaders().begin(); it != r->requestHeaders().end(); ++it) {
+ std::string h = it->first + ": " + it->second;
+ headerList = curl_slist_append(headerList, h.c_str());
+ }
+
+ if (headerList != NULL) {
+ curl_easy_setopt(curlRequest, CURLOPT_HTTPHEADER, headerList);
+ }
+
+ curl_multi_add_handle(d->curlMulti, curlRequest);
+
+// this seems premature, but we don't have a callback from Curl we could
+// use to trigger when the requst is actually sent.
+ r->requestStart();
+
+#else
+ if( r->url().find("http://") != 0 ) {
+ r->setFailure(EINVAL, "only HTTP protocol is supported");
+ return;
+ }
+
+ std::string host = r->host();
+ int port = r->port();
+ if (!d->proxy.empty()) {
+ host = d->proxy;
+ port = d->proxyPort;
+ }
+
+ Connection* con = NULL;
+ std::stringstream ss;
+ ss << host << "-" << port;
+ std::string connectionId = ss.str();
+ bool havePending = !d->pendingRequests.empty();
+ bool atConnectionsLimit = d->connections.size() >= d->maxConnections;
+ ConnectionDict::iterator consEnd = d->connections.end();
+
+ // assign request to an existing Connection.
+ // various options exist here, examined in order
+ ConnectionDict::iterator it = d->connections.find(connectionId);
+ if (atConnectionsLimit && (it == consEnd)) {
+ // maximum number of connections active, queue this request
+ // when a connection goes inactive, we'll start this one
+ d->pendingRequests.push_back(r);
+ return;
+ }
+
+ // scan for an idle Connection to the same host (likely if we're
+ // retrieving multiple resources from the same host in quick succession)
+ // if we have pending requests (waiting for a free Connection), then
+ // force new requests on this id to always use the first Connection
+ // (instead of the random selection below). This ensures that when
+ // there's pressure on the number of connections to keep alive, one
+ // host can't DoS every other.
+ int count = 0;
+ for (; (it != consEnd) && (it->first == connectionId); ++it, ++count) {
+ if (havePending || !it->second->isActive()) {
+ con = it->second;
+ break;
+ }
+ }
+
+ bool atHostConnectionsLimit = (count >= d->maxHostConnections);
+
+ if (!con && (atConnectionsLimit || atHostConnectionsLimit)) {
+ // all current connections are busy (active), and we don't
+ // have free connections to allocate, so let's assign to
+ // an existing one randomly. Ideally we'd used whichever one will
+ // complete first but we don't have that info.
+ int index = rand() % count;
+ for (it = d->connections.find(connectionId); index > 0; --index, ++it) { ; }
+ con = it->second;
+ }
+
+ // allocate a new connection object
+ if (!con) {
+ static int connectionSuffx = 0;
+
+ std::stringstream ss;
+ ss << connectionId << "-" << connectionSuffx++;
+
+ SG_LOG(SG_IO, SG_DEBUG, "allocating new connection for ID:" << ss.str());
+ con = new Connection(this, ss.str());
+ con->setServer(host, port);
+ con->setMaxPipelineLength(d->maxPipelineDepth);
+ d->poller.addChannel(con);
+ d->connections.insert(d->connections.end(),
+ ConnectionDict::value_type(connectionId, con));
+ }
+
+ SG_LOG(SG_IO, SG_DEBUG, "queing request for " << r->url() << " on:" << con->connectionId());
+ con->queueRequest(r);
+#endif
+}
+
+void Client::cancelRequest(const Request_ptr &r, std::string reason)
+{
+#if defined(ENABLE_CURL)
+ ClientPrivate::RequestCurlMap::iterator it = d->requests.find(r);
+ if(it == d->requests.end()) {
+ // already being removed, presumably inside ::update()
+ // nothing more to do
+ return;
+ }
+
+ CURLMcode err = curl_multi_remove_handle(d->curlMulti, it->second);
+ assert(err == CURLM_OK);
+
+ // clear the request pointer form the curl-easy object
+ curl_easy_setopt(it->second, CURLOPT_PRIVATE, 0);
+
+ curl_easy_cleanup(it->second);
+ d->requests.erase(it);
+#else
+ ConnectionDict::iterator it = d->connections.begin();
+ for (; it != d->connections.end(); ++it) {
+ (it->second)->cancelRequest(r);
+ }
+#endif
+ r->setFailure(-1, reason);
+}
+
+//------------------------------------------------------------------------------
+FileRequestRef Client::save( const std::string& url,
+ const std::string& filename )
+{
+ FileRequestRef req = new FileRequest(url, filename);
+ makeRequest(req);
+ return req;
+}
+
+//------------------------------------------------------------------------------
+MemoryRequestRef Client::load(const std::string& url)
+{
+ MemoryRequestRef req = new MemoryRequest(url);
+ makeRequest(req);
+ return req;