Browse Source

smarter proxy specs; multiple proxy servers

David Rose 22 years ago
parent
commit
ba755587b7

+ 87 - 19
panda/src/downloader/httpChannel.cxx

@@ -41,6 +41,7 @@ HTTPChannel::
 HTTPChannel(HTTPClient *client) :
   _client(client)
 {
+  _proxy_next_index = 0;
   _persistent_connection = false;
   _connect_timeout = connect_timeout;
   _http_timeout = http_timeout;
@@ -62,7 +63,6 @@ HTTPChannel(HTTPClient *client) :
   _status_code = 0;
   _status_string = string();
   _response_type = RT_none;
-  _proxy = _client->get_proxy();
   _http_version = _client->get_http_version();
   _http_version_string = _client->get_http_version_string();
   _state = S_new;
@@ -254,7 +254,10 @@ run() {
 
   bool repeat_later;
   do {
-    if (_bio.is_null()) {
+    // If we're in a state that expects to have a connection already
+    // (that is, any state other that S_try_next_proxy), then
+    // reestablish the connection if it has been dropped.
+    if (_bio.is_null() && _state != S_try_next_proxy) {
       if (_connect_count > http_max_connect_count) {
         // Too many connection attempts, just give up.  We should
         // never trigger this failsafe, since the code in each
@@ -267,8 +270,6 @@ run() {
       }
 
       // No connection.  Attempt to establish one.
-      _proxy = _client->get_proxy();
-      
       if (_proxy.empty()) {
         _bio = new BioPtr(_request.get_url());
       } else {
@@ -297,6 +298,10 @@ run() {
     }
 
     switch (_state) {
+    case S_try_next_proxy:
+      repeat_later = run_try_next_proxy();
+      break;
+
     case S_connecting:
       repeat_later = run_connecting();
       break;
@@ -626,6 +631,32 @@ reached_done_state() {
   }
 }
   
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPChannel::run_try_next_proxy
+//       Access: Private
+//  Description: This state is reached when a previous connection
+//               attempt fails.  If we have multiple proxies in line
+//               to try, it sets us up for the next proxy and tries to
+//               connect again; otherwise, it sets the state to
+//               S_failure.
+////////////////////////////////////////////////////////////////////
+bool HTTPChannel::
+run_try_next_proxy() {
+  if (_proxy_next_index < _proxies.size()) {
+    // Try the next proxy in sequence.
+    _proxy = _proxies[_proxy_next_index];
+    _proxy_auth = (HTTPAuthorization *)NULL;
+    _proxy_next_index++;
+    close_connection();
+    _state = S_connecting;
+    return false;
+  }
+
+  // No more proxies to try, or we're not using a proxy.
+  _state = S_failure;
+  return false;
+}
+  
 ////////////////////////////////////////////////////////////////////
 //     Function: HTTPChannel::run_connecting
 //       Access: Private
@@ -647,7 +678,7 @@ run_connecting() {
 #ifdef REPORT_OPENSSL_ERRORS
     ERR_print_errors_fp(stderr);
 #endif
-    _state = S_failure;
+    _state = S_try_next_proxy;
     return false;
   }
 
@@ -680,7 +711,7 @@ run_connecting_wait() {
   if (fd < 0) {
     downloader_cat.warning()
       << "nonblocking socket BIO has no file descriptor.\n";
-    _state = S_failure;
+    _state = S_try_next_proxy;
     return false;
   }
 
@@ -706,7 +737,7 @@ run_connecting_wait() {
   if (errcode < 0) {
     downloader_cat.warning()
       << "Error in select.\n";
-    _state = S_failure;
+    _state = S_try_next_proxy;
     return false;
   }
   
@@ -719,7 +750,7 @@ run_connecting_wait() {
       downloader_cat.info()
         << "Timeout connecting to " 
         << _request.get_url().get_server_and_port() << ".\n";
-      _state = S_failure;
+      _state = S_try_next_proxy;
       return false;
     }
     return true;
@@ -778,7 +809,7 @@ run_proxy_request_sent() {
       // Huh, the proxy hung up on us as soon as we tried to connect.
       if (_response_type == RT_hangup) {
         // This was our second immediate hangup in a row.  Give up.
-        _state = S_failure;
+        _state = S_try_next_proxy;
         
       } else {
         // Try again, once.
@@ -851,7 +882,7 @@ run_proxy_reading_header() {
       _status_code += 1000;
     }
 
-    _state = S_failure;
+    _state = S_try_next_proxy;
     return false;
   }
 
@@ -1038,7 +1069,7 @@ run_request_sent() {
       // Huh, the server hung up on us as soon as we tried to connect.
       if (_response_type == RT_hangup) {
         // This was our second immediate hangup in a row.  Give up.
-        _state = S_failure;
+        _state = S_try_next_proxy;
         
       } else {
         // Try again, once.
@@ -1050,7 +1081,7 @@ run_request_sent() {
       // Time to give up.
       downloader_cat.info()
         << "Timeout waiting for " << _request.get_url().get_server_and_port() << ".\n";
-      _state = S_failure;
+      _state = S_try_next_proxy;
     }
 
     return true;
@@ -1085,7 +1116,7 @@ run_reading_header() {
         << "Connection lost while reading HTTP response.\n";
       if (_response_type == RT_http_hangup) {
         // This was our second hangup in a row.  Give up.
-        _state = S_failure;
+        _state = S_try_next_proxy;
         
       } else {
         // Try again, once.
@@ -1097,7 +1128,7 @@ run_reading_header() {
       // Time to give up.
       downloader_cat.info()
         << "Timeout waiting for " << _request.get_url().get_server_and_port() << ".\n";
-      _state = S_failure;
+      _state = S_try_next_proxy;
     }
     return true;
   }
@@ -1250,6 +1281,19 @@ run_reading_header() {
     }
   }
 
+  if (_state == S_read_header && 
+      ((get_status_code() / 100) == 5 || get_status_code() == 407) &&
+      !_proxy.empty() && !_proxy_tunnel && _proxy_next_index < _proxies.size()) {
+    // If we were using a proxy (but not tunneling through the proxy)
+    // and we got some kind of a server error, try the next proxy in
+    // sequence (if we have one).  This handles the case of a working
+    // proxy that cannot see the host (and so returns 504 or something
+    // along those lines).
+    _state = S_try_next_proxy;
+    return false;
+  }
+
+  // Otherwise, we're good to go.
   return false;
 }
 
@@ -1534,13 +1578,37 @@ begin_request(HTTPEnum::Method method, const DocumentSpec &url,
               size_t first_byte, size_t last_byte) {
   reset_for_new_request();
 
-  // Changing the proxy, or the nonblocking state, is grounds for
-  // dropping the old connection, if any.
-  if (_proxy != _client->get_proxy()) {
-    _proxy = _client->get_proxy();
+  // Get the set of proxies that are appropriate for this URL.
+  _proxies.clear();
+  _proxy_next_index = 0;
+  _client->get_proxies_for_url(url.get_url(), _proxies);
+
+  // If we still have a live connection to a proxy that is on the
+  // list, that proxy should be moved immediately to the front of the
+  // list (to minimize restarting connections unnecessarily).
+  if (!_bio.is_null() && !_proxies.empty() && !_proxy.empty()) {
+    Proxies::iterator pi = find(_proxies.begin(), _proxies.end(), _proxy);
+    if (pi != _proxies.end()) {
+      _proxies.erase(pi);
+      _proxies.insert(_proxies.begin(), _proxy);
+    }
+  }
+
+  URLSpec new_proxy;
+  if (_proxy_next_index < _proxies.size()) {
+    new_proxy = _proxies[_proxy_next_index];
+    _proxy_next_index++;
+  }
+
+  // Changing the proxy is grounds for dropping the old connection, if
+  // any.
+  if (_proxy != new_proxy) {
+    _proxy = new_proxy;
+    _proxy_auth = (HTTPAuthorization *)NULL;
     reset_to_new();
   }
 
+  // Ditto with changing the nonblocking state.
   if (_nonblocking != nonblocking) {
     _nonblocking = nonblocking;
     reset_to_new();
@@ -1818,7 +1886,7 @@ parse_http_response(const string &line) {
     _status_string = "Not an HTTP response";
     if (_response_type == RT_non_http) {
       // This was our second non-HTTP response in a row.  Give up.
-      _state = S_failure;
+      _state = S_try_next_proxy;
 
     } else {
       // Maybe we were just in some bad state.  Drop the connection

+ 6 - 0
panda/src/downloader/httpChannel.h

@@ -150,6 +150,7 @@ public:
 
 private:
   bool reached_done_state();
+  bool run_try_next_proxy();
   bool run_connecting();
   bool run_connecting_wait();
   bool run_proxy_ready();
@@ -204,7 +205,11 @@ private:
   void reset_to_new();
   void close_connection();
 
+  typedef pvector<URLSpec> Proxies;
+
   HTTPClient *_client;
+  Proxies _proxies;
+  size_t _proxy_next_index;
   URLSpec _proxy;
   PT(BioPtr) _bio;
   PT(BioStreamPtr) _source;
@@ -286,6 +291,7 @@ private:
   // off.
   enum State {
     S_new,
+    S_try_next_proxy,
     S_connecting,
     S_connecting_wait,
     S_proxy_ready,

+ 0 - 22
panda/src/downloader/httpClient.I

@@ -17,28 +17,6 @@
 ////////////////////////////////////////////////////////////////////
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: HTTPClient::set_proxy
-//       Access: Published
-//  Description: Specifies the proxy URL to handle all http and
-//               https requests.
-////////////////////////////////////////////////////////////////////
-INLINE void HTTPClient::
-set_proxy(const URLSpec &proxy) {
-  _proxy = proxy;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: HTTPClient::get_proxy
-//       Access: Published
-//  Description: Returns the proxy URL to handle all http and
-//               https requests.
-////////////////////////////////////////////////////////////////////
-INLINE const URLSpec &HTTPClient::
-get_proxy() const {
-  return _proxy;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: HTTPClient::set_http_version
 //       Access: Published

+ 397 - 2
panda/src/downloader/httpClient.cxx

@@ -44,6 +44,52 @@ bool HTTPClient::_ssl_initialized = false;
 X509_STORE *HTTPClient::_x509_store = NULL;
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: trim_blanks
+//  Description:
+////////////////////////////////////////////////////////////////////
+static string
+trim_blanks(const string &str) {
+  size_t start = 0;
+  while (start < str.length() && isspace(str[start])) {
+    start++;
+  }
+
+  size_t end = str.length();
+  while (end > start && isspace(str[end - 1])) {
+    end--;
+  }
+
+  return str.substr(start, end - start);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: tokenize
+//  Description: Chops the source string up into pieces delimited by
+//               any of the characters specified in delimiters.
+//               Repeated delimiter characters represent zero-length
+//               tokens.
+//
+//               It is the user's responsibility to ensure the output
+//               vector is cleared before calling this function; the
+//               results will simply be appended to the end of the
+//               vector.
+////////////////////////////////////////////////////////////////////
+static void
+tokenize(const string &str, vector_string &words, const string &delimiters) {
+  size_t p = 0;
+  while (p < str.length()) {
+    size_t q = str.find_first_of(delimiters, p);
+    if (q == string::npos) {
+      words.push_back(str.substr(p));
+      return;
+    }
+    words.push_back(str.substr(p, q - p));
+    p = q + 1;
+  }
+  words.push_back(string());
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: HTTPClient::Constructor
 //       Access: Published
@@ -55,7 +101,7 @@ HTTPClient() {
   _verify_ssl = verify_ssl ? VS_normal : VS_no_verify;
   _ssl_ctx = (SSL_CTX *)NULL;
 
-  _proxy = URLSpec(http_proxy, 1);
+  set_proxy_spec(http_proxy);
   if (!http_proxy_username.empty()) {
     set_username("*proxy", "", http_proxy_username);
   }
@@ -104,7 +150,8 @@ HTTPClient(const HTTPClient &copy) {
 ////////////////////////////////////////////////////////////////////
 void HTTPClient::
 operator = (const HTTPClient &copy) {
-  _proxy = copy._proxy;
+  _proxies_by_scheme = copy._proxies_by_scheme;
+  _direct_hosts = copy._direct_hosts;
   _http_version = copy._http_version;
   _verify_ssl = copy._verify_ssl;
   _usernames = copy._usernames;
@@ -140,6 +187,328 @@ HTTPClient::
   clear_expected_servers();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::set_proxy
+//       Access: Published
+//  Description: Specifies the proxy URL to handle all http and
+//               https requests.  Deprecated.
+////////////////////////////////////////////////////////////////////
+void HTTPClient::
+set_proxy(const URLSpec &proxy) {
+  set_proxy_spec(proxy.get_url());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::get_proxy
+//       Access: Published
+//  Description: Returns the proxy URL to handle all http and
+//               https requests.  Deprecated.
+////////////////////////////////////////////////////////////////////
+URLSpec HTTPClient::
+get_proxy() const {
+  pvector<URLSpec> proxies;
+  get_proxies_for_url(URLSpec("http://"), proxies);
+  if (!proxies.empty()) {
+    return proxies[0];
+  }
+  return URLSpec();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::set_proxy_spec
+//       Access: Published
+//  Description: Specifies the complete set of proxies to use for all
+//               schemes.  This is either a semicolon-delimited set of
+//               hostname:ports, or a semicolon-delimited set of pairs
+//               of the form "scheme=hostname:port", or a combination.
+//               A particular scheme and/or proxy host may be listed
+//               more than once.  This is a convenience function that
+//               can be used in place of explicit calls to add_proxy()
+//               for each scheme/proxy pair.
+////////////////////////////////////////////////////////////////////
+void HTTPClient::
+set_proxy_spec(const string &proxy_spec) {
+  clear_proxy();
+
+  // Tokenize the string based on the semicolons.
+  vector_string proxies;
+  tokenize(proxy_spec, proxies, ";");
+  
+  for (vector_string::const_iterator pi = proxies.begin();
+       pi != proxies.end();
+       ++pi) {
+    const string &spec = (*pi);
+
+    // Divide out the scheme and the hostname.
+    string scheme;
+    URLSpec url;
+    size_t equals = spec.find('=');
+    if (equals == string::npos) {
+      scheme = "";
+      url = URLSpec(spec, true);
+    } else {
+      scheme = trim_blanks(spec.substr(0, equals));
+      url = URLSpec(spec.substr(equals + 1), true);
+    }
+
+    if (!url.has_scheme()) {
+      // The default scheme for talking to proxies is HTTP.
+      url.set_scheme("http");
+    }
+
+    add_proxy(scheme, url);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::get_proxy_spec
+//       Access: Published
+//  Description: Returns the complete set of proxies to use for all
+//               schemes.  This is a string of the form specified by
+//               set_proxy_spec(), above.  Note that the string
+//               returned by this function may not be exactly the same
+//               as the string passed into set_proxy_spec(), since the
+//               string is regenerated from the internal storage
+//               structures and may therefore be reordered.
+////////////////////////////////////////////////////////////////////
+string HTTPClient::
+get_proxy_spec() const {
+  string result;
+
+  ProxiesByScheme::const_iterator si;
+  for (si = _proxies_by_scheme.begin(); si != _proxies_by_scheme.end(); ++si) {
+    const string &scheme = (*si).first;
+    const Proxies &proxies = (*si).second;
+    Proxies::const_iterator pi;
+    for (pi = proxies.begin(); pi != proxies.end(); ++pi) {
+      const URLSpec &url = (*pi);
+      if (!result.empty()) {
+        result += ";";
+      }
+      if (!scheme.empty()) {
+        result += scheme;
+        result += "=";
+      }
+      result += url.get_url();
+    }
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::set_direct_host_spec
+//       Access: Published
+//  Description: Specifies the set of hosts that should be connected
+//               to directly, without using a proxy.  This is a
+//               semicolon-separated list of hostnames or ip addresses,
+//               that may contain wildcard characters ("*").
+////////////////////////////////////////////////////////////////////
+void HTTPClient::
+set_direct_host_spec(const string &direct_host_spec) {
+  clear_direct_host();
+
+  // Tokenize the string based on the semicolons.
+  vector_string hosts;
+  tokenize(direct_host_spec, hosts, ";");
+  
+  for (vector_string::const_iterator hi = hosts.begin();
+       hi != hosts.end();
+       ++hi) {
+    const string &spec = (*hi);
+    add_direct_host(trim_blanks(spec));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::get_direct_host_spec
+//       Access: Published
+//  Description: Returns the set of hosts that should be connected
+//               to directly, without using a proxy, as a
+//               semicolon-separated list of hostnames or ip addresses,
+//               that may contain wildcard characters ("*").
+////////////////////////////////////////////////////////////////////
+string HTTPClient::
+get_direct_host_spec() const {
+  string result;
+
+  vector_string::const_iterator si;
+  for (si = _direct_hosts.begin(); si != _direct_hosts.end(); ++si) {
+    const string &host = (*si);
+
+    if (!result.empty()) {
+      result += ";";
+    }
+    result += host;
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::clear_proxy
+//       Access: Published
+//  Description: Resets the proxy spec to empty.  Subsequent calls to
+//               add_proxy() may be made to build up the set of proxy
+//               servers.
+////////////////////////////////////////////////////////////////////
+void HTTPClient::
+clear_proxy() {
+  _proxies_by_scheme.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::add_proxy
+//       Access: Published
+//  Description: Adds the indicated proxy host as a proxy for
+//               communications on the given scheme.  Usually the
+//               scheme is "http" or "https".  It may be the empty
+//               string to indicate a general proxy.
+////////////////////////////////////////////////////////////////////
+void HTTPClient::
+add_proxy(const string &scheme, const URLSpec &proxy) {
+  // The scheme is always converted to lowercase.
+  string lc_scheme;
+  lc_scheme.reserve(scheme.length());
+  for (string::const_iterator si = scheme.begin(); si != scheme.end(); ++si) {
+    lc_scheme += tolower(*si);
+  }
+
+  // Remove the trailing colon, if there is one.
+  if (!lc_scheme.empty() && lc_scheme[lc_scheme.length() - 1] == ':') {
+    lc_scheme = lc_scheme.substr(0, lc_scheme.length() - 1);
+  }
+
+  _proxies_by_scheme[lc_scheme].push_back(proxy);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::clear_direct_host
+//       Access: Published
+//  Description: Resets the set of direct hosts to empty.  Subsequent
+//               calls to add_direct_host() may be made to build up
+//               the list of hosts that do not require a proxy
+//               connection.
+////////////////////////////////////////////////////////////////////
+void HTTPClient::
+clear_direct_host() {
+  _direct_hosts.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::add_direct_host
+//       Access: Published
+//  Description: Adds the indicated name to the set of hostnames that
+//               are connected to directly, without using a proxy.
+//               This name may be either a DNS name or an IP address,
+//               and it may include the * as a wildcard character.
+////////////////////////////////////////////////////////////////////
+void HTTPClient::
+add_direct_host(const string &hostname) {
+  // The hostname is always converted to lowercase.
+  string lc_hostname;
+  lc_hostname.reserve(hostname.length());
+  for (string::const_iterator si = hostname.begin(); 
+       si != hostname.end(); 
+       ++si) {
+    lc_hostname += tolower(*si);
+  }
+
+  _direct_hosts.push_back(lc_hostname);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::get_proxies_for_url
+//       Access: Published
+//  Description: Fills up the indicated vector with the list of
+//               URLSpec objects, in the order in which they should be
+//               tried, that are appropriate for the indicated URL.
+//               The vector is left empty if a direct connection
+//               should be used.
+//
+//               It is the user's responsibility to empty this vector
+//               before calling this method; otherwise, the proxy
+//               URL's will simply be appended to the existing list.
+////////////////////////////////////////////////////////////////////
+void HTTPClient::
+get_proxies_for_url(const URLSpec &url, pvector<URLSpec> &proxies) const {
+  // First, check if the hostname matches any listed in direct_hosts.
+  string hostname = url.get_server();
+
+  // TODO: This should be a glob match, not a literal match.
+  vector_string::const_iterator si;
+  for (si = _direct_hosts.begin(); si != _direct_hosts.end(); ++si) {
+    if ((*si) == hostname) {
+      // It matches, so don't use any proxies.
+      return;
+    }
+  }
+
+  // Now choose the appropriate proxy based on the scheme.
+  string scheme = url.get_scheme();
+  bool got_any = false;
+
+  if (scheme.empty()) {
+    // An empty scheme implies we will want to make a direct
+    // connection to this host, so we will need a socks-style or
+    // https-style scheme.
+    if (get_proxies_for_scheme("socks", proxies)) {
+      got_any = true;
+    }
+    if (get_proxies_for_scheme("https", proxies)) {
+      got_any = true;
+    }
+
+  } else {
+    // Otherwise, try to match the proxy to the scheme.
+    if (get_proxies_for_scheme(scheme, proxies)) {
+      got_any = true;
+    }
+  }
+
+  // If we didn't find our scheme of choice, fall back to the default
+  // proxy type.
+  if (!got_any) {
+    if (get_proxies_for_scheme("", proxies)) {
+      got_any = true;
+    }
+  }
+
+  // And failing that, try the http proxy.
+  if (!got_any) {
+    get_proxies_for_scheme("http", proxies);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::get_proxies_for_url
+//       Access: Published
+//  Description: Returns a semicolon-delimited list of proxies, in the
+//               order in which they should be tried, that are
+//               appropriate for the indicated URL.  The empty string
+//               is returned if a direct connection should be used.
+////////////////////////////////////////////////////////////////////
+string HTTPClient::
+get_proxies_for_url(const URLSpec &url) const {
+  pvector<URLSpec> proxies;
+  get_proxies_for_url(url, proxies);
+
+  string result;
+  if (!proxies.empty()) {
+    pvector<URLSpec>::const_iterator pi = proxies.begin();
+    result += (*pi).get_url();
+    ++pi;
+
+    while (pi != proxies.end()) {
+      result += ";";
+      result += (*pi).get_url();
+      ++pi;
+    }
+  }
+
+  return result;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: HTTPClient::set_username
 //       Access: Published
@@ -467,6 +836,32 @@ get_ssl_ctx() {
   return _ssl_ctx;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::get_proxies_for_scheme
+//       Access: Private
+//  Description: Adds the proxy servers associated with the indicated
+//               scheme, if any, to the list.  Returns true if any
+//               were added, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool HTTPClient::
+get_proxies_for_scheme(const string &scheme, pvector<URLSpec> &proxies) const {
+  ProxiesByScheme::const_iterator si = _proxies_by_scheme.find(scheme);
+  if (si == _proxies_by_scheme.end()) {
+    return false;
+  }
+  const Proxies &scheme_proxies = (*si).second;
+  if (scheme_proxies.empty()) {
+    return false;
+  }
+
+  Proxies::const_iterator pi;
+  for (pi = scheme_proxies.begin(); pi != scheme_proxies.end(); ++pi) {
+    proxies.push_back(*pi);
+  }
+
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: HTTPClient::add_http_username
 //       Access: Private

+ 27 - 3
panda/src/downloader/httpClient.h

@@ -32,6 +32,9 @@
 #include "httpAuthorization.h"
 #include "httpEnum.h"
 #include "pointerTo.h"
+#include "pvector.h"
+#include "pmap.h"
+#include "vector_string.h"
 
 #include <openssl/ssl.h>
 
@@ -59,8 +62,22 @@ PUBLISHED:
   void operator = (const HTTPClient &copy);
   ~HTTPClient();
 
-  INLINE void set_proxy(const URLSpec &proxy);
-  INLINE const URLSpec &get_proxy() const;
+  void set_proxy(const URLSpec &proxy);
+  URLSpec get_proxy() const;
+
+  void set_proxy_spec(const string &proxy_spec);
+  string get_proxy_spec() const;
+
+  void set_direct_host_spec(const string &direct_host_spec);
+  string get_direct_host_spec() const;
+
+  void clear_proxy();
+  void add_proxy(const string &scheme, const URLSpec &proxy);
+  void clear_direct_host();
+  void add_direct_host(const string &hostname);
+
+  void get_proxies_for_url(const URLSpec &url, pvector<URLSpec> &proxies) const;
+  string get_proxies_for_url(const URLSpec &url) const;
 
   void set_username(const string &server, const string &realm, const string &username);
   string get_username(const string &server, const string &realm) const;
@@ -93,6 +110,9 @@ public:
   SSL_CTX *get_ssl_ctx();
 
 private:
+  bool get_proxies_for_scheme(const string &scheme, 
+                              pvector<URLSpec> &proxies) const;
+
   void add_http_username(const string &http_username);
   string select_username(const URLSpec &url, bool is_proxy, 
                          const string &realm) const;
@@ -113,7 +133,11 @@ private:
                                void *arg);
 #endif
 
-  URLSpec _proxy;
+  typedef pvector<URLSpec> Proxies;
+  typedef pmap<string, Proxies> ProxiesByScheme;
+  ProxiesByScheme _proxies_by_scheme;
+  vector_string _direct_hosts;
+
   HTTPEnum::HTTPVersion _http_version;
   VerifySSL _verify_ssl;
 

+ 13 - 2
panda/src/downloader/urlSpec.cxx

@@ -447,7 +447,19 @@ set_query(const string &query) {
 ////////////////////////////////////////////////////////////////////
 void URLSpec::
 set_url(const string &url, bool server_name_expected) {
-  _url = url;
+  size_t p, q;
+
+  // Omit leading and trailing whitespace.
+  p = 0;
+  while (p < url.length() && isspace(url[p])) {
+    p++;
+  }
+  q = url.length();
+  while (q > p && isspace(url[q - 1])) {
+    q--;
+  }
+
+  _url = url.substr(p, q - p);
   _flags = 0;
 
   if (url.empty()) {
@@ -457,7 +469,6 @@ set_url(const string &url, bool server_name_expected) {
 
   // First, replace backslashes with forward slashes, since this is a
   // common mistake among Windows users.
-  size_t p;
   for (p = 0; p < _url.length(); p++) {
     if (_url[p] == '\\') {
       _url[p] = '/';