def start_request(self, method, url, headers=None, bodylen=None):
        """Start a new HTTP request.

        The optional *headers* argument contains the headers to send. It must
        be a sequence of ``(name, value)`` tuples.

        The optional *bodylen* parameter is a hint that specifies the length of
        the body that will follow. A length of -1 indicates no body, 0 means an
        empty body, and a positive number indicates the body size in bytes.
        This parameter helps determine whether to use the chunked transfer
        encoding. Normally when the body size is known chunked encoding is not used.
        """
        self._headers = headers or []
        agent = host = clen = trailer = None
        # Check the headers provided, and capture some information about the
        # request from them.
        for name, value in self._headers:
            lname = name.lower()
            # Only HTTP applications are allowed to set "hop-by-hop" headers.
            if lname in hop_by_hop:
                raise ValueError('header {} is hop-by-hop'.format(name))
            elif lname == 'user-agent':
                agent = value
            elif lname == 'host':
                host = value
            elif lname == 'content-length':
                clen = int(value)
            elif lname == 'trailer':
                trailer = parse_trailer(value)
            elif lname == 'content-type' and value.startswith('text/'):
                ctype, params = parse_content_type(value)
                self._charset = params.get('charset')
        version = self._protocol._version
        # The Host header is mandatory in 1.1. Add it if it's missing.
        if host is None and version == '1.1' and self._protocol._server_name:
            self._headers.append(('Host', self._protocol._server_name))
        # Identify ourselves.
        if agent is None:
            self._headers.append(('User-Agent', self._protocol.identifier))
        # Check if we need to use chunked encoding due to unknown body size.
        if clen is None and bodylen is None:
            if version == '1.0':
                raise HttpError('body size unknown for HTTP/1.0')
            self._chunked = True
        self._content_length = clen
        # Check if trailers are requested and if so need to switch to chunked.
        if trailer:
            if version == '1.0':
                raise HttpError('cannot support trailers for HTTP/1.0')
            if clen is not None:
                remove_headers(self._headers, 'Content-Length')
            self._chunked = True
        self._trailer = trailer
        # Add Content-Length if we know the body size and are not using chunked.
        if not self._chunked and clen is None and bodylen >= 0:
            self._headers.append(('Content-Length', str(bodylen)))
            self._content_length = bodylen
        # Complete the "Hop by hop" headers.
        if version == '1.0':
            self._headers.append(('Connection', 'keep-alive'))
        elif version == '1.1':
            self._headers.append(('Connection', 'te'))
            self._headers.append(('TE', 'trailers'))
        if self._chunked:
            self._headers.append(('Transfer-Encoding', 'chunked'))
        # Start the request
        self._protocol._requests.append(method)
        header = create_request(version, method, url, self._headers)
        self._protocol.writer.write(header)
Start a new HTTP request.

        The optional *headers* argument contains the headers to send. It must
        be a sequence of ``(name, value)`` tuples.

        The optional *bodylen* parameter is a hint that specifies the length of
        the body that will follow. A length of -1 indicates no body, 0 means an
        empty body, and a positive number indicates the body size in bytes.
        This parameter helps determine whether to use the chunked transfer
        encoding. Normally when the body size is known chunked encoding is not used.