docs: def perform_request(self, method, url, params=None, body=None):
          """
          Perform the actual request.
          Retrieve a connection.
          Pass all the information to it's perform_request method and return the data.
  
          :arg method: HTTP method to use
          :arg url: absolute url (without host) to target
          :arg params: dictionary of query parameters, will be handed over to the
              underlying :class:`~pydrill.Connection` class for serialization
          :arg body: body of the request, will be serializes using serializer and
              passed to the connection
          """
          if body is not None:
              body = self.serializer.dumps(body)
  
              # some clients or environments don't support sending GET with body
              if method in ('HEAD', 'GET') and self.send_get_body_as != 'GET':
                  # send it as post instead
                  if self.send_get_body_as == 'POST':
                      method = 'POST'
  
                  # or as source parameter
                  elif self.send_get_body_as == 'source':
                      if params is None:
                          params = {}
                      params['source'] = body
                      body = None
  
          if body is not None:
              try:
                  body = body.encode('utf-8')
              except (UnicodeDecodeError, AttributeError):
                  # bytes/str - no need to re-encode
                  pass
  
          ignore = ()
          timeout = None
          if params:
              timeout = params.pop('request_timeout', None)
              ignore = params.pop('ignore', ())
              if isinstance(ignore, int):
                  ignore = (ignore,)
  
          for attempt in range(self.max_retries + 1):
              connection = self.get_connection()
  
              try:
                  response, data, duration = connection.perform_request(method, url, params, body, ignore=ignore,
                                                                        timeout=timeout)
              except TransportError as e:
                  retry = False
                  if isinstance(e, ConnectionTimeout):
                      retry = self.retry_on_timeout
                  elif isinstance(e, ConnectionError):
                      retry = True
                  elif e.status_code in self.retry_on_status:
                      retry = True
  
                  if retry:
                      if attempt == self.max_retries:
                          raise
                  else:
                      raise
              else:
                  if data:
                      data = self.deserializer.loads(data, mimetype=response.headers.get('Content-Type'))
                  else:
                      data = {}
                  return response, data, duration