def request()

in eden/scm/edenscm/mercurial/httpclient/__init__.py [0:0]


    def request(self, method, path, body=None, headers=None, expect_continue=False):
        """Send a request to the server.

        For increased flexibility, this does not return the response
        object. Future versions of HTTPConnection that juggle multiple
        sockets will be able to send (for example) 5 requests all at
        once, and then let the requests arrive as data is
        available. Use the `getresponse()` method to retrieve the
        response.
        """
        if headers is None:
            headers = {}
        method = _ensurebytes(method)
        path = _ensurebytes(path)
        if self.busy():
            raise httplib.CannotSendRequest(
                "Can not send another request before " "current response is read!"
            )
        self._current_response_taken = False

        logger.info(
            "sending %s request for %s to %s on port %s",
            method,
            path,
            self.host,
            self.port,
        )

        hdrs = _foldheaders(headers)
        # Figure out headers that have to be computed from the request
        # body.
        chunked = False
        if body and HDR_CONTENT_LENGTH not in hdrs:
            if getattr(body, "__len__", False):
                hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, b"%d" % len(body))
            elif getattr(body, "read", False):
                hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING, XFER_ENCODING_CHUNKED)
                chunked = True
            else:
                raise BadRequestData("body has no __len__() nor read()")
        # Figure out expect-continue header
        if hdrs.get("expect", ("", ""))[1].lower() == b"100-continue":
            expect_continue = True
        elif expect_continue:
            hdrs["expect"] = (b"Expect", b"100-Continue")
        # httplib compatibility: if the user specified a
        # proxy-authorization header, that's actually intended for a
        # proxy CONNECT action, not the real request, but only if
        # we're going to use a proxy.
        pheaders = dict(self._proxy_headers)
        if self._proxy_host and self.ssl:
            pa = hdrs.pop("proxy-authorization", None)
            if pa is not None:
                pheaders["proxy-authorization"] = pa
        # Build header data
        outgoing_headers = self._buildheaders(method, path, hdrs, self.http_version)

        # If we're reusing the underlying socket, there are some
        # conditions where we'll want to retry, so make a note of the
        # state of self.sock
        fresh_socket = self.sock is None
        self._connect(pheaders)
        response = None
        first = True

        while (outgoing_headers or body) and not (response and response.complete()):
            select_timeout = self.timeout
            out = outgoing_headers or body
            blocking_on_continue = False
            if (
                expect_continue
                and not outgoing_headers
                and not (response and (response.headers or response.continued))
            ):
                logger.info(
                    "waiting up to %s seconds for" " continue response from server",
                    self.continue_timeout,
                )
                select_timeout = self.continue_timeout
                blocking_on_continue = True
                out = False
            if out:
                w = [self.sock]
            else:
                w = []
            r, w, x = select.select([self.sock], w, [], select_timeout)
            # if we were expecting a 100 continue and it's been long
            # enough, just go ahead and assume it's ok. This is the
            # recommended behavior from the RFC.
            if r == w == x == []:
                if blocking_on_continue:
                    expect_continue = False
                    logger.info(
                        "no response to continue expectation from "
                        "server, optimistically sending request body"
                    )
                else:
                    raise HTTPTimeoutException("timeout sending data")
            was_first = first

            # incoming data
            if r:
                try:
                    try:
                        data = r[0].recv(INCOMING_BUFFER_SIZE)
                    except ssl.SSLError as e:
                        if e.args[0] != ssl.SSL_ERROR_WANT_READ:
                            raise
                        logger.debug(
                            "SSL_ERROR_WANT_READ while sending " "data, retrying..."
                        )
                        continue
                    if not data:
                        logger.info("socket appears closed in read")
                        self.sock = None
                        self._current_response = None
                        if response is not None:
                            # We're a friend of the response class, so let
                            # us use the private attribute.
                            # pylint: disable=W0212
                            response._close()
                        # This if/elif ladder is a bit subtle,
                        # comments in each branch should help.
                        if response is not None and response.complete():
                            # Server responded completely and then
                            # closed the socket. We should just shut
                            # things down and let the caller get their
                            # response.
                            logger.info(
                                "Got an early response, " "aborting remaining request."
                            )
                            break
                        elif was_first and response is None:
                            # Most likely a keepalive that got killed
                            # on the server's end. Commonly happens
                            # after getting a really large response
                            # from the server.
                            logger.info(
                                "Connection appeared closed in read on first"
                                " request loop iteration, will retry."
                            )
                            self._reconnect("read", pheaders)
                            continue
                        else:
                            # We didn't just send the first data hunk,
                            # and either have a partial response or no
                            # response at all. There's really nothing
                            # meaningful we can do here.
                            raise HTTPStateError(
                                "Connection appears closed after "
                                "some request data was written, but the "
                                "response was missing or incomplete!"
                            )
                    logger.debug("read %d bytes in request()", len(data))
                    if response is None:
                        response = self.response_class(r[0], self.timeout, method)
                    # We're a friend of the response class, so let us
                    # use the private attribute.
                    # pylint: disable=W0212
                    response._load_response(data)
                    # Jump to the next select() call so we load more
                    # data if the server is still sending us content.
                    continue
                except socket.error as e:
                    if e.errno != errno.EPIPE and not was_first:
                        raise

            # outgoing data
            if w and out:
                try:
                    if getattr(out, "read", False):
                        # pylint guesses the type of out incorrectly here
                        # pylint: disable=E1103
                        data = out.read(OUTGOING_BUFFER_SIZE)
                        if not data:
                            continue
                        if len(data) < OUTGOING_BUFFER_SIZE:
                            if chunked:
                                body = b"0" + EOL + EOL
                            else:
                                body = None
                        if chunked:
                            # This encode is okay because we know
                            # hex() is building us only 0-9 and a-f
                            # digits.
                            asciilen = hex(len(data))[2:].encode("ascii")
                            out = asciilen + EOL + data + EOL
                        else:
                            out = data
                    amt = w[0].send(out)
                except socket.error as e:
                    if e.errno == ssl.SSL_ERROR_WANT_WRITE and self.ssl:
                        # This means that SSL hasn't flushed its buffer into
                        # the socket yet.
                        # TODO: find a way to block on ssl flushing its buffer
                        # similar to selecting on a raw socket.
                        continue
                    if e.errno == errno.EWOULDBLOCK or e.errno == errno.EAGAIN:
                        continue
                    elif e.errno not in (errno.ECONNRESET, errno.EPIPE) and not first:
                        raise
                    self._reconnect("write", pheaders)
                    amt = self.sock.send(out)
                logger.debug("sent %d", amt)
                first = False
                if out is body:
                    body = out[amt:]
                else:
                    outgoing_headers = out[amt:]
        # End of request-sending loop.

        # close if the server response said to or responded before eating
        # the whole request
        if response is None:
            response = self.response_class(self.sock, self.timeout, method)
            if not fresh_socket:
                if not response._select():
                    # This means the response failed to get any response
                    # data at all, and in all probability the socket was
                    # closed before the server even saw our request. Try
                    # the request again on a fresh socket.
                    logger.debug(
                        "response._select() failed during request()."
                        " Assuming request needs to be retried."
                    )
                    self.sock = None
                    # Call this method explicitly to re-try the
                    # request. We don't use self.request() because
                    # some tools (notably Mercurial) expect to be able
                    # to subclass and redefine request(), and they
                    # don't have the same argspec as we do.
                    #
                    # TODO restructure sending of requests to avoid
                    # this recursion
                    return HTTPConnection.request(
                        self,
                        method,
                        path,
                        body=body,
                        headers=headers,
                        expect_continue=expect_continue,
                    )
        data_left = bool(outgoing_headers or body)
        if data_left:
            logger.info(
                "stopped sending request early, " "will close the socket to be safe."
            )
            response.will_close = True
        if response.will_close:
            # The socket will be closed by the response, so we disown
            # the socket
            self.sock = None
        self._current_response = response