Class ClientToProxyConnection

  • All Implemented Interfaces:
    ChannelHandler, ChannelInboundHandler

    public class ClientToProxyConnection
    extends ProxyConnection<HttpRequest>

    Represents a connection from a client to our proxy. Each ClientToProxyConnection can have multiple ProxyToServerConnections, at most one per outbound host:port.

    Once a ProxyToServerConnection has been created for a given server, it is continually reused. The ProxyToServerConnection goes through its own lifecycle of connects and disconnects, with different underlying Channels, but only a single ProxyToServerConnection object is used per server. The one exception to this is CONNECT tunneling - if a connection has been used for CONNECT tunneling, that connection will never be reused.

    As the ProxyToServerConnections receive responses from their servers, they feed these back to the client by calling respond(ProxyToServerConnection, HttpFilters, HttpRequest, HttpResponse, HttpObject) .

    • Field Detail

      • ABSOLUTE_URI_PATTERN

        private static final Pattern ABSOLUTE_URI_PATTERN
        Used for case-insensitive comparisons when checking direct proxy request.
      • serverConnectionsByHostAndPort

        private final Map<String,​ProxyToServerConnection> serverConnectionsByHostAndPort
        Keep track of all ProxyToServerConnections by host+port.
      • numberOfCurrentlyConnectingServers

        private final AtomicInteger numberOfCurrentlyConnectingServers
        Keep track of how many servers are currently in the process of connecting.
      • haProxyMessage

        private HAProxyMessage haProxyMessage
        Keep track of proxy protocol header
      • numberOfCurrentlyConnectedServers

        private final AtomicInteger numberOfCurrentlyConnectedServers
        Keep track of how many servers are currently connected.
      • numberOfReusedServerConnections

        private final AtomicInteger numberOfReusedServerConnections
        Keep track of how many times we were able to reuse a connection.
      • currentServerConnection

        private volatile ProxyToServerConnection currentServerConnection
        This is the current server connection that we're using while transferring chunked data.
      • currentFilters

        private volatile HttpFilters currentFilters
        The current filters to apply to incoming requests/chunks.
      • clientSslSession

        private volatile SSLSession clientSslSession
      • mitming

        private volatile boolean mitming
        Tracks whether this ClientToProxyConnection is current doing MITM.
      • currentRequest

        private volatile HttpRequest currentRequest
        The current HTTP request that this connection is currently servicing.
      • RespondCONNECTSuccessful

        ConnectionFlowStep RespondCONNECTSuccessful
        Tells the Client that its HTTP CONNECT request was successful.
    • Method Detail

      • doReadHTTPInitial

        private ConnectionState doReadHTTPInitial​(HttpRequest httpRequest)

        Reads an HttpRequest.

        If we don't yet have a ProxyToServerConnection for the desired server, this takes care of creating it.

        Note - the "server" could be a chained proxy, not the final endpoint for the request.

      • isRequestToOriginServer

        private boolean isRequestToOriginServer​(HttpRequest httpRequest)
        Returns true if the specified request is a request to an origin server, rather than to a proxy server. If this request is being MITM'd, this method always returns false. The format of requests to a proxy server are defined in RFC 7230, section 5.3.2 (all other requests are considered requests to an origin server):
                 When making a request to a proxy, other than a CONNECT or server-wide
                 OPTIONS request (as detailed below), a client MUST send the target
                 URI in absolute-form as the request-target.
                 [...]
                 An example absolute-form of request-line would be:
                 GET https://www.example.org/pub/WWW/TheProject.html HTTP/1.1
                 To allow for transition to the absolute-form for all requests in some
                 future version of HTTP, a server MUST accept the absolute-form in
                 requests, even though HTTP/1.1 clients will only send them in
                 requests to proxies.
             
        Parameters:
        httpRequest - the request to evaluate
        Returns:
        true if the specified request is a request to an origin server, otherwise false
      • respond

        void respond​(ProxyToServerConnection serverConnection,
                     HttpFilters filters,
                     HttpRequest currentHttpRequest,
                     HttpResponse currentHttpResponse,
                     HttpObject httpObject)
        Send a response to the client.
        Parameters:
        serverConnection - the ProxyToServerConnection that's responding
        filters - the filters to apply to the response
        currentHttpRequest - the HttpRequest that prompted this response
        currentHttpResponse - the HttpResponse corresponding to this data (when doing chunked transfers, this is the initial HttpResponse object that came in before the other chunks)
        httpObject - the data with which to respond
      • resetCurrentRequest

        private void resetCurrentRequest()
      • serverConnectionSucceeded

        protected void serverConnectionSucceeded​(ProxyToServerConnection serverConnection,
                                                 boolean shouldForwardInitialRequest)
        If the ProxyToServerConnection completes its connection lifecycle successfully, this method is called to let us know about it.
      • serverConnectionFailed

        protected boolean serverConnectionFailed​(ProxyToServerConnection serverConnection,
                                                 ConnectionState lastStateBeforeFailure,
                                                 Throwable cause)
        If the ProxyToServerConnection fails to complete its connection lifecycle successfully, this method is called to let us know about it.

        After failing to connect to the server, one of two things can happen:

        1. If the server was a chained proxy, we fall back to connecting to the ultimate endpoint directly.
        2. If the server was the ultimate endpoint, we return a 502 Bad Gateway to the client.
        Parameters:
        serverConnection -
        lastStateBeforeFailure -
        cause - what caused the failure
        Returns:
        true if we're falling back to a another chained proxy (or direct connection) and trying again
      • resumeReadingIfNecessary

        private void resumeReadingIfNecessary()
      • serverDisconnected

        protected void serverDisconnected​(ProxyToServerConnection serverConnection)
        On disconnect of the server, track that we have one fewer connected servers and then disconnect the client if necessary.
      • becameSaturated

        protected void becameSaturated()
        When the ClientToProxyConnection becomes saturated, stop reading on all associated ProxyToServerConnections.
        Overrides:
        becameSaturated in class ProxyConnection<HttpRequest>
      • becameWritable

        protected void becameWritable()
        When the ClientToProxyConnection becomes writable, resume reading on all associated ProxyToServerConnections.
        Overrides:
        becameWritable in class ProxyConnection<HttpRequest>
      • serverBecameSaturated

        protected void serverBecameSaturated​(ProxyToServerConnection serverConnection)
        When a server becomes saturated, we stop reading from the client.
      • serverBecameWriteable

        protected void serverBecameWriteable​(ProxyToServerConnection serverConnection)
        When a server becomes writeable, we check to see if all servers are writeable and if they are, we resume reading.
      • removeHandlerIfPresent

        private void removeHandlerIfPresent​(String name)
      • isAcceptProxyProtocol

        boolean isAcceptProxyProtocol()
        Is the proxy server set to accept a proxy protocol header
        Returns:
        True if the proxy server set to accept a proxy protocol header. False otherwise
      • isSendProxyProtocol

        boolean isSendProxyProtocol()
        Is the proxy server set to send a proxy protocol header
        Returns:
        True if the proxy server set to send a proxy protocol header. False otherwise
      • closeConnectionsAfterWriteIfNecessary

        private void closeConnectionsAfterWriteIfNecessary​(ProxyToServerConnection serverConnection,
                                                           HttpRequest currentHttpRequest,
                                                           HttpResponse currentHttpResponse,
                                                           HttpObject httpObject)
        This method takes care of closing client to proxy and/or proxy to server connections after finishing a write.
      • shouldCloseClientConnection

        private boolean shouldCloseClientConnection​(HttpRequest req,
                                                    HttpResponse res,
                                                    HttpObject httpObject)
        Determine whether or not the client connection should be closed.
      • shouldCloseServerConnection

        private boolean shouldCloseServerConnection​(HttpRequest req,
                                                    HttpResponse res,
                                                    HttpObject msg)
        Determines if the remote connection should be closed based on the request and response pair. If the request is HTTP 1.0 with no keep-alive header, for example, the connection should be closed. This in part determines if we should close the connection. Here's the relevant section of RFC 2616: "HTTP/1.1 defines the "close" connection option for the sender to signal that the connection will be closed after completion of the response. For example, Connection: close in either the request or the response header fields indicates that the connection SHOULD NOT be considered `persistent' (section 8.1) after the current request/response is complete."
        Parameters:
        req - The request.
        res - The response.
        msg - The message.
        Returns:
        Returns true if the connection should close.
      • authenticationRequired

        private boolean authenticationRequired​(HttpRequest request)

        Checks whether the given HttpRequest requires authentication.

        If the request contains credentials, these are checked.

        If authentication is still required, either because no credentials were provided or the credentials were wrong, this writes a 407 response to the client.

      • writeAuthenticationRequired

        private void writeAuthenticationRequired​(String realm)
      • fixHttpVersionHeaderIfNecessary

        private void fixHttpVersionHeaderIfNecessary​(HttpResponse httpResponse)
        Chunked encoding is an HTTP 1.1 feature, but sometimes we get a chunked response that reports its HTTP version as 1.0. In this case, we change it to 1.1.
      • modifyRequestHeadersToReflectProxying

        private void modifyRequestHeadersToReflectProxying​(HttpRequest httpRequest)
        If and only if our proxy is not running in transparent mode, modify the request headers to reflect that it was proxied.
      • isNextHopOriginServer

        private boolean isNextHopOriginServer()
      • modifyResponseHeadersToReflectProxying

        private void modifyResponseHeadersToReflectProxying​(HttpResponse httpResponse)
        If and only if our proxy is not running in transparent mode, modify the response headers to reflect that it was proxied.
      • switchProxyConnectionHeader

        private void switchProxyConnectionHeader​(HttpHeaders headers)
        Switch the de-facto standard "Proxy-Connection" header to "Connection" when we pass it along to the remote host. This is largely undocumented but seems to be what most browsers and servers expect.
        Parameters:
        headers - The headers to modify
      • stripConnectionTokens

        private void stripConnectionTokens​(HttpHeaders headers)
        RFC2616 Section 14.10 HTTP/1.1 proxies MUST parse the Connection header field before a message is forwarded and, for each connection-token in this field, remove any header field(s) from the message with the same name as the connection-token.
        Parameters:
        headers - The headers to modify
      • stripHopByHopHeaders

        private void stripHopByHopHeaders​(HttpHeaders headers)
        Removes all headers that should not be forwarded. See RFC 2616 13.5.1 End-to-end and Hop-by-hop Headers.
        Parameters:
        headers - The headers to modify
      • writeBadGateway

        private boolean writeBadGateway​(HttpRequest httpRequest)
        Tells the client that something went wrong trying to proxy its request. If the Bad Gateway is a response to an HTTP HEAD request, the response will contain no body, but the Content-Length header will be set to the value it would have been if this 502 Bad Gateway were in response to a GET.
        Parameters:
        httpRequest - the HttpRequest that is resulting in the Bad Gateway response
        Returns:
        true if the connection will be kept open, or false if it will be disconnected
      • writeBadRequest

        private boolean writeBadRequest​(HttpRequest httpRequest)
        Tells the client that the request was malformed or erroneous. If the Bad Request is a response to an HTTP HEAD request, the response will contain no body, but the Content-Length header will be set to the value it would have been if this Bad Request were in response to a GET.
        Returns:
        true if the connection will be kept open, or false if it will be disconnected
      • writeGatewayTimeout

        private boolean writeGatewayTimeout​(HttpRequest httpRequest)
        Tells the client that the connection to the server, or possibly to some intermediary service (such as DNS), timed out. If the Gateway Timeout is a response to an HTTP HEAD request, the response will contain no body, but the Content-Length header will be set to the value it would have been if this 504 Gateway Timeout were in response to a GET.
        Parameters:
        httpRequest - the HttpRequest that is resulting in the Gateway Timeout response
        Returns:
        true if the connection will be kept open, or false if it will be disconnected
      • respondWithShortCircuitResponse

        private boolean respondWithShortCircuitResponse​(HttpResponse httpResponse)
        Responds to the client with the specified "short-circuit" response. The response will be sent through the HttpFilters.proxyToClientResponse(HttpObject) filter method before writing it to the client. The client will not be disconnected, unless the response includes a "Connection: close" header, or the filter returns a null HttpResponse (in which case no response will be written to the client and the connection will be disconnected immediately). If the response is not a Bad Gateway or Gateway Timeout response, the response's headers will be modified to reflect proxying, including adding a Via header, Date header, etc.
        Parameters:
        httpResponse - the response to return to the client
        Returns:
        true if the connection will be kept open, or false if it will be disconnected.
      • identifyHostAndPort

        private String identifyHostAndPort​(HttpRequest httpRequest)
        Identify the host and port for a request.
      • writeEmptyBuffer

        private void writeEmptyBuffer()
        Write an empty buffer at the end of a chunked transfer. We need to do this to handle the way Netty creates HttpChunks from responses that aren't in fact chunked from the remote server using Transfer-Encoding: chunked. Netty turns these into pseudo-chunked responses in cases where the response would otherwise fill up too much memory or where the length of the response body is unknown. This is handy because it means we can start streaming response bodies back to the client without reading the entire response. The problem is that in these pseudo-cases the last chunk is encoded to null, and this thwarts normal ChannelFutures from propagating operationComplete events on writes to appropriate channel listeners. We work around this by writing an empty buffer in those cases and using the empty buffer's future instead to handle any operations we need to when responses are fully written back to clients.
      • isMitming

        public boolean isMitming()
      • setMitming

        protected void setMitming​(boolean isMitming)
      • recordClientConnected

        private void recordClientConnected()
      • recordClientSSLHandshakeSucceeded

        private void recordClientSSLHandshakeSucceeded()
      • recordClientDisconnected

        private void recordClientDisconnected()