docs: def fetch_title(self, url, hostname_tag=False, friendly_errors=False):
          """Fetch the document at *url* and return a `Deferred` yielding
          the document title or summary as a Unicode string.  *url* may be
          a Unicode string IRI, a byte string URI, or a Twisted `URL`.
  
          If *hostname_tag* is true, prefix the extracted title with the
          hostname of the initially requested URI or IRI in the form that
          was originally provided, as well as the hostname of the final
          ASCII-only URI if it differs due to redirects or normalization.
  
          If *friendly_errors* is true, catch common connection errors and
          return a description of the error as the extracted title instead
          of reraising.  Otherwise, all errors bubble to the caller.
          """
          title = None
          if isinstance(url, unicode):
              url = URL.fromText(url)
          elif isinstance(url, str):
              url = URL.fromText(url.decode('ascii'))
          current = url
          response = None
          for _ in xrange(self.max_soft_redirects):
              last_response = response
              # This encoding should be safe, since asURI() only returns
              # URIs with ASCII code points.
              request = self.agent.request(
                  'GET', current.asURI().asText().encode('ascii'))
              if friendly_errors:
                  request.addErrback(describe_error)
              response = yield request
              if isinstance(response, basestring):
                  # We got an error message from describe_error.  Bail.
                  title = response
                  break
              response.setPreviousResponse(last_response)
              content_type = cgi.parse_header(
                  response.headers.getRawHeaders('Content-Type', [''])[0])[0]
              if content_type in self.extractors:
                  extractor = self.extractors[content_type]
                  extracted = yield extractor.extract(response)
                  if isinstance(extracted, Redirect):
                      current = URL.fromText(
                          response.request.absoluteURI.decode('ascii')).click(
                          extracted.location)
                      continue
                  title = extracted
              # The only case where we'd want to loop again is when the
              # response returned is a soft redirect.
              break
          else:
              if friendly_errors:
                  title = u'Encountered too many redirects.'
              else:
                  raise ResponseFailed([Failure(InfiniteRedirection(
                      599, 'Too many soft redirects',
                      location=current.asURI().asText().encode('ascii')))])
          if title is None:
              title = u'{} document'.format(content_type or u'Unknown')
              if response.length is not UNKNOWN_LENGTH:
                  title += u' ({})'.format(filesize(response.length))
          if hostname_tag:
              tag = url.host
              if isinstance(response, Response):
                  initial = url.host
                  final = URL.fromText(
                      response.request.absoluteURI.decode('ascii')).host
                  if initial != final:
                      tag = u'{} \u2192 {}'.format(initial, final)
              title = u'[{}] {}'.format(tag, title)
          returnValue(title)