docs: def OpenURL(self,
                url,
                verify_cb=lambda x: True,
                data=None,
                params=None,
                headers=None,
                method="GET",
                timeout=None):
      """Get the requested URL.
  
      Note that we do not have any concept of timing here - we try to connect
      through all proxies as fast as possible until one works. Timing and poll
      frequency is left to the calling code.
  
      Args:
        url: The URL to fetch
        verify_cb: An optional callback which can be used to validate the URL. It
          receives the HTTPObject and return True if this seems OK, False
          otherwise. For example, if we are behind a captive portal we might
          receive invalid object even though HTTP status is 200.
        data: Parameters to send in POST bodies (See Requests documentation).
        params: Parameters to send in GET URLs (See Requests documentation).
        headers: Additional headers (See Requests documentation)
        method: The HTTP method to use. If not set we select one automatically.
        timeout: See Requests documentation.
  
      Returns:
        An HTTPObject instance or None if a connection could not be made.
  
      """
      # Start checking the proxy from the last value found.
      tries = 0
      last_error = 500
  
      while tries < len(self.proxies):
        proxy_index = self.last_proxy_index % len(self.proxies)
        proxy = self.proxies[proxy_index]
        try:
          proxydict = {}
          if proxy:
            proxydict["http"] = proxy
            proxydict["https"] = proxy
  
          headers = (headers or {}).copy()
          headers["Cache-Control"] = "no-cache"
  
          if data:
            method = "POST"
  
          duration, handle = self._RetryRequest(
              url=url,
              data=data,
              params=params,
              headers=headers,
              method=method,
              timeout=timeout,
              proxies=proxydict,
          )
  
          data = handle.content
  
          result = HTTPObject(
              url=url, data=data, proxy=proxy, code=200, duration=duration)
  
          if not verify_cb(result):
            raise IOError("Data not verified.")
  
          # The last connection worked.
          self.consecutive_connection_errors = 0
          return result
  
        except requests.RequestException as e:
          # Especially trap a 406 error message - it means the client needs to
          # enroll.
          if e.response is not None:
            last_error = e.response.status_code
            if last_error == 406:
              # A 406 is not considered an error as the frontend is reachable. If
              # we considered it as an error the client would be unable to send
              # the enrollment request since connection errors disable message
              # draining.
              self.consecutive_connection_errors = 0
              return HTTPObject(code=406)
  
          # Try the next proxy
          self.last_proxy_index = proxy_index + 1
          tries += 1
  
        # Catch any exceptions that dont have a code (e.g. socket.error).
        except IOError:
          # Try the next proxy
          self.last_proxy_index = proxy_index + 1
          tries += 1
          last_error = 500
        # Catch unexpected exceptions. If the error is proxy related it makes
        # sense to cycle the proxy before reraising. One error we have seen here
        # is ProxySchemeUnknown but urllib can raise many different exceptions, it
        # doesn't make sense to enumerate them all.
        except Exception:  # pylint: disable=broad-except
          logging.exception(
              "Got an unexpected exception while connecting to the server.")
          # Try the next proxy
          self.last_proxy_index = proxy_index + 1
          tries += 1
          last_error = 500
  
      # We failed to connect at all here.
      return HTTPObject(code=last_error)