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.

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)