Interface HttpFilters

All Known Subinterfaces:
SelectiveFilters
All Known Implementing Classes:
HttpFiltersAdapter, HttpFiltersBuilder.Built, HttpFiltersChain.Chained

public interface HttpFilters
Hooks into the life of a single request/response exchange. One instance is created per request by HttpFiltersSource.filterRequest(org.microproxy.http.HttpRequest, org.microproxy.FlowContext); all methods are invoked on the virtual thread that serves the client connection, so implementations may block.

Unless the HttpFiltersSource asks for buffering, a message flows through the filter as a head (HttpRequest/HttpResponse) followed by zero or more HttpContent pieces and a final LastHttpContent. With buffering it arrives as a single FullHttpRequest/FullHttpResponse.

Every method has a no-op default, so implement only what you need. Call order for a typical exchange:

  1. clientToProxyRequest(org.microproxy.http.HttpObject)
  2. proxyToServerRequest(org.microproxy.http.HttpObject), so a filter can answer or redirect before any DNS lookup or connection
  3. when a new server connection is needed: proxyToServerResolutionStarted(java.lang.String), proxyToServerResolutionSucceeded(java.lang.String, java.net.InetSocketAddress) / proxyToServerResolutionFailed(java.lang.String) (direct connections only), then proxyToServerConnectionStarted(), proxyToServerConnectionSSLHandshakeStarted(), proxyToServerConnectionSucceeded(org.microproxy.FullFlowContext) / proxyToServerConnectionFailed()
  4. proxyToServerRequestSending(), proxyToServerRequestSent()
  5. serverToProxyResponseReceiving(), serverToProxyResponse(org.microproxy.http.HttpObject), serverToProxyResponseReceived() (or serverToProxyResponseTimedOut())
  6. proxyToClientResponse(org.microproxy.http.HttpObject), then proxyToClientResponseSent(org.microproxy.http.HttpResponse, org.microproxy.ResponseSource) once the response has been written
  7. exchangeEnded(boolean), exactly once, however the exchange ended

When the proxy answers a request itself because something failed, proxyToServerFailure(org.microproxy.ProxyFailure) may supply the answer.

With HttpProxyServerBootstrap.withLittleProxyCompatibility(), the server is resolved before proxyToServerRequest(org.microproxy.http.HttpObject), as LittleProxy does (and a name that does not resolve is answered with 502 without calling it); the connection is still made after it.

  • Method Details

    • builder

      static HttpFiltersBuilder builder()
      Starts filters made from lambdas, one per hook (see HttpFiltersBuilder).
      Returns:
      a new builder with default settings
    • requestBufferSizeInBytes

      default int requestBufferSizeInBytes(HttpRequest request)
      Asks for this request to be buffered, before clientToProxyRequest(org.microproxy.http.HttpObject) sees it: return a positive size to receive the head and body as one FullHttpRequest, or 0 to stream. A body larger than the size is answered with 413. Not consulted for CONNECT, or when the source already buffers.
      Parameters:
      request - the request being handled
      Returns:
      the requested body buffer limit in bytes, or zero to stream
    • clientToProxyRequest

      default HttpResponse clientToProxyRequest(HttpObject httpObject)
      Filters requests on their way from the client to the proxy. Return a response to answer the client directly ("short-circuit") without contacting the server; only the return value for the request head is honoured.
      Parameters:
      httpObject - the request head or body piece
      Returns:
      a short-circuit response, or null to continue forwarding
    • proxyToServerRequest

      default HttpResponse proxyToServerRequest(HttpObject httpObject)
      Filters requests on their way from the proxy to the server, after the proxy has rewritten headers. Return a response to short-circuit; only the return value for the request head is honoured.
      Parameters:
      httpObject - the request head or body piece
      Returns:
      a short-circuit response, or null to continue forwarding
    • proxyToServerRequestSending

      default void proxyToServerRequestSending()
      Called just before the request head is written to the server.
    • proxyToServerRequestSent

      default void proxyToServerRequestSent()
      Called after the complete request, including its body, has been written to the server.
    • serverToProxyResponse

      default HttpObject serverToProxyResponse(HttpObject httpObject)
      Filters responses on their way from the server to the proxy. Return the (possibly modified or replaced) object, or null to abort and disconnect the client. A CONNECT's 200 Connection established, which the proxy makes once the server is connected, comes here too (then to proxyToClientResponse(org.microproxy.http.HttpObject)), as the server's acceptance: a filter may turn it into a refusal. Not with HttpProxyServerBootstrap.withLittleProxyCompatibility(boolean), as in LittleProxy.
      Parameters:
      httpObject - the response head or body piece
      Returns:
      the object to forward, or null to abort the exchange
    • responseBufferSizeInBytes

      default int responseBufferSizeInBytes(HttpResponse response)
      Asks for this response to be buffered, after seeing its head: return a positive size to receive the head and body as one FullHttpResponse in serverToProxyResponse(org.microproxy.http.HttpObject), or 0 to stream. Unlike HttpFiltersSource.getMaximumResponseBufferSizeInBytes(), a body larger than the size is not an error: it is streamed through as usual. Ignored when the source already buffers.
      Parameters:
      response - the response being handled
      Returns:
      the requested body buffer limit in bytes, or zero to stream
    • serverToProxyResponseTimedOut

      default void serverToProxyResponseTimedOut()
      Called when the server did not respond within the idle timeout.
    • proxyToServerFailure

      default HttpResponse proxyToServerFailure(ProxyFailure failure)
      Called when the proxy is about to answer this request itself because of failure (the server could not be reached, timed out or answered badly, the request was refused, ...). Return a response to send instead of the proxy's default, or null. It is framed by the proxy and passes proxyToClientResponse(org.microproxy.http.HttpObject) like any proxy-made response. Filters are asked before the FailureResponder; the more specific callbacks (proxyToServerResolutionFailed(java.lang.String), proxyToServerConnectionFailed(), serverToProxyResponseTimedOut()) are still called as well, before this one.

      Not called when the failure happens after the response has started (the client connection is closed instead), or for requests the proxy could not parse.

      Parameters:
      failure - the proxy failure to answer
      Returns:
      a replacement failure response, or null for the default
    • serverToProxyResponseReceiving

      default void serverToProxyResponseReceiving()
      Called when the server begins sending a response.
    • serverToProxyResponseReceived

      default void serverToProxyResponseReceived()
      Called when the server has finished sending a response.
    • proxyToClientResponse

      default HttpObject proxyToClientResponse(HttpObject httpObject)
      Filters responses on their way from the proxy to the client. Return the (possibly modified) object, or null to abort and disconnect the client.

      This also sees the responses the proxy answers with itself, its error responses and the short-circuit responses returned from earlier hooks, as the ProtocolPolicy of the client's protocol says (ProtocolPolicy.generatedResponses()): by default a replacement is sent; it may be limited to changes in place (GeneratedResponses.IN_PLACE), or keep them from this hook (GeneratedResponses.SKIP). FlowContext.responseSource() says where the response came from. The proxy's 200 to a CONNECT passes serverToProxyResponse(org.microproxy.http.HttpObject) and this hook (unless ProtocolPolicy.filterConnectAnswer() is off); its 407 challenge reaches no filter. To change the proxy's own error pages, use proxyToServerFailure(org.microproxy.ProxyFailure) or a FailureResponder.

      Parameters:
      httpObject - the response head or body piece
      Returns:
      the object to send, or null to abort the exchange
    • proxyToClientResponseSent

      default void proxyToClientResponseSent(HttpResponse response, ResponseSource source)
      Called once the whole response has been written to the client, whoever made it: the server, the proxy, a filter or the cache. FlowContext.timings() then covers the whole exchange. Not called when the exchange was aborted or failed half-way through the response, or for the 407 challenge sent before filters are created.
      Parameters:
      response - the response head as it was sent (after every filter)
      source - where it came from, as reported to ActivityTracker.responseSentToClient(FlowContext, HttpResponse, ResponseSource)
    • exchangeEnded

      default void exchangeEnded(boolean completed)
      Called exactly once when this exchange is over, however it ended, so per-exchange resources (a concurrency permit, a span, a temporary file) can be released:
      • after proxyToClientResponseSent(org.microproxy.http.HttpResponse, org.microproxy.ResponseSource) for a response written in full, whoever made it (the server, the proxy's failure answer, a filter's short-circuit, the cache);
      • for a CONNECT that became a byte tunnel, or a request upgraded with 101 (WebSocket), when the tunnel closes;
      • for an intercepted CONNECT, once interception starts: the requests inside the session are exchanges of their own;
      • when the exchange was abandoned: the client disconnected, the server failed half-way through the response, a filter returned null, a filter or the proxy threw, or the proxy was stopped.

      It runs on the connection's thread before the next request on the connection is read. Exceptions it throws are logged and ignored.

      Parameters:
      completed - whether the response was written in full (proxyToClientResponseSent(org.microproxy.http.HttpResponse, org.microproxy.ResponseSource) was called)
    • proxyToServerResolutionStarted

      default InetSocketAddress proxyToServerResolutionStarted(String resolvingServerHostAndPort)
      Called before the server's host name is resolved. Return an address to skip resolution and connect there instead.
      Parameters:
      resolvingServerHostAndPort - the destination host and port being resolved
      Returns:
      the address to connect to, or null to resolve normally
    • proxyToServerResolutionFailed

      default void proxyToServerResolutionFailed(String hostAndPort)
      Called when the upstream host name cannot be resolved.
      Parameters:
      hostAndPort - the destination host and port, or null when unavailable
    • proxyToServerResolutionSucceeded

      default void proxyToServerResolutionSucceeded(String serverHostAndPort, InetSocketAddress resolvedRemoteAddress)
      Called after the upstream host name is resolved.
      Parameters:
      serverHostAndPort - the requested server host and port
      resolvedRemoteAddress - the resolved address to connect to
    • proxyToServerConnectTimeout

      default Duration proxyToServerConnectTimeout()
      The connect timeout for this request's connection attempts: to the server, or to each chained proxy tried. null (the default), zero or a negative duration keeps the server's (HttpProxyServerBootstrap.withConnectTimeout(int)). Asked when the request needs a new connection, after proxyToServerRequest(org.microproxy.http.HttpObject); a request that reuses a connection makes none. It bounds the TCP connect only, not name resolution or TLS handshakes (proxyToServerTlsHandshakeTimeout()). In a HttpFiltersChain the shortest timeout wins.
      Returns:
      the connect timeout override, or null for the server setting
    • proxyToServerTlsHandshakeTimeout

      default Duration proxyToServerTlsHandshakeTimeout()
      The TLS handshake timeout for this request's upstream handshakes: with the server when the proxy speaks TLS to it (an intercepted CONNECT, an absolute https:// URL, HTTP/2 to the server), and with each TLS chained proxy tried (https:// and h2:// ones), including the server's handshake inside a tunnel through one. null (the default), zero or a negative duration keeps the server's (HttpProxyServerBootstrap.withTlsHandshakeTimeout(java.time.Duration)); a request cannot turn the limit off. Asked when the request needs a new connection, after proxyToServerRequest(org.microproxy.http.HttpObject); a request that reuses a connection makes no handshake. A handshake that runs out of time fails the attempt with ProxyFailure.TlsFailed (a SocketTimeoutException cause, response flag UF), and the next chained proxy, if any, is tried, as after other failures. In a HttpFiltersChain the shortest timeout wins.

      Upstream HTTP/3 (QUIC) handshakes keep Http3Options.upstreamHandshakeTimeout(): their connections are shared by every client, and one that is not made in time does not fail the request but sends it over TCP, whose TLS handshake this timeout then bounds.

      Returns:
      the TLS handshake timeout override, or null for the server setting
    • chainedProxyRetryBackoff

      default RetryBackoff chainedProxyRetryBackoff()
      The backoff between this request's connection attempts when a chained proxy (or the direct fallback) fails and the next route the ChainedProxyManager offered is tried, instead of the server's (HttpProxyServerBootstrap.withChainedProxyRetryBackoff(RetryBackoff)). null (the default) keeps the server's; RetryBackoff.none() tries the next route at once. Asked when an attempt has failed and another route remains, so a request that connects on its first attempt never asks. The server's limits apply to it too: no wait before the first attempt or after the last, the policy's budget of waiting in all (by default at most 30 seconds or the connect timeout; see RetryBackoff.withBudget(java.time.Duration) and RetryBackoff.unbudgeted()), and a client that leaves ends the wait. In a HttpFiltersChain the first filter that returns a backoff wins.
      Returns:
      the retry backoff override, or null for the server setting
    • proxyToServerMitmHandshake

      default MitmServerHandshake proxyToServerMitmHandshake()
      When the proxy runs its TLS handshake with the server for this CONNECT, which it intercepts, instead of what the ProtocolPolicy of the client's protocol says (ProtocolPolicy.mitmServerHandshake()): MitmServerHandshake.DEFERRED after answering it and reading the client's ClientHello, MitmServerHandshake.EAGER before answering it. null (the default) keeps the policy's. Asked once the proxy has decided to intercept the CONNECT, after proxy authentication, clientToProxyRequest(org.microproxy.http.HttpObject) and proxyToServerAllowMitm(), so the choice can depend on the authenticated user (FlowContext.getClientDetails()) or anything the request says. In a HttpFiltersChain the first filter that returns a choice wins.
      Returns:
      when the server's handshake runs, or null for the policy's choice
    • proxyToServerConnectionStarted

      default void proxyToServerConnectionStarted()
      Called before an upstream TCP connection attempt starts.
    • proxyToServerConnectionSSLHandshakeStarted

      default void proxyToServerConnectionSSLHandshakeStarted()
      Called before the upstream TLS handshake starts.
    • proxyToServerConnectionFailed

      default void proxyToServerConnectionFailed()
      Called when an upstream connection attempt fails.
    • proxyToServerConnectionSucceeded

      default void proxyToServerConnectionSucceeded(FullFlowContext serverContext)
      Called once the connection to the server (or chained proxy) is ready for traffic.
      Parameters:
      serverContext - the server connection context
    • proxyToServerAllowMitm

      default boolean proxyToServerAllowMitm()
      Whether this CONNECT request may be intercepted when a MitmManager is configured. Return false to tunnel the bytes untouched instead.
      Returns:
      whether this CONNECT may be intercepted
    • proxyToServerAllowMitm

      default boolean proxyToServerAllowMitm(ClientHello clientHello)
      Whether this CONNECT (or transparent TLS connection) may be intercepted, now that the client's TLS ClientHello is known: return false to tunnel the bytes untouched, so the client sees the real server's certificate. Called once the client has sent its ClientHello, after proxyToServerAllowMitm(), the host rules (HttpProxyServerBootstrap.withIgnoreHosts(java.lang.String...), HttpProxyServerBootstrap.withAllowHosts(java.lang.String...)) and MitmManager.shouldIntercept(org.microproxy.ClientHello, org.microproxy.FlowContext) have allowed interception; not called for clients that do not speak TLS. True by default.
      Parameters:
      clientHello - the client's ClientHello, with its server name (SNI) and ALPN protocols
      Returns:
      whether the connection may be intercepted
    • proxyToServerAllowOfflineMitm

      default boolean proxyToServerAllowOfflineMitm()
      For a CONNECT that is being intercepted but whose server cannot be reached: return true to intercept it anyway, with a certificate made from the requested host name alone, so these filters can answer the requests inside the session (from a cache, for example). Those requests fail as usual if they do need the server.
      Returns:
      whether interception may proceed without an upstream connection
    • proxyToServerTlsIdentity

      default TlsServerIdentity proxyToServerTlsIdentity(String serverHostAndPort)
      The TLS identity of this request's connection to serverHostAndPort: the server name sent as SNI and the host the server's certificate is verified against, when they are not the request's host, e.g. for a route that dials an IP address or an internal load balancer (proxyToServerResolutionStarted(java.lang.String), a resolver, a chained proxy) while the service presents a certificate for its public name. null (the default) keeps both derived from the request's host. The address dialled is not affected, nor is the TLS with a chained proxy itself (ChainedProxy.requiresEncryption()).

      Asked for requests that reach the server over TLS (intercepted sessions, TLS origination, a reverse proxy's https:// upstream) before a server connection is chosen, and for an intercepted CONNECT before the proxy's handshake with its target. A connection made with one identity serves only requests with the same identity: it is never reused, pooled or coalesced (RFC 9113 section 9.1.1) for another, and a request with an identity does not go over HTTP/3. The certificate is still validated with the MITM manager's trust; only the names change. In a HttpFiltersChain the first member that returns an identity wins.

      Parameters:
      serverHostAndPort - the server's host:port
      Returns:
      the identity, or null for the default
    • webSocketFrameReceived

      default void webSocketFrameReceived(WebSocketFrame frame, boolean fromClient)
      Called for every WebSocket frame relayed after this request was upgraded (101 Switching Protocols with Upgrade: websocket), including inside intercepted TLS sessions. Frames are observed, not modified: they are forwarded unchanged after this returns. The two directions are relayed on separate virtual threads, so this may be called concurrently for client and server frames.

      Frames are only parsed when the filters class overrides one of the two webSocketFrameReceived methods; otherwise upgraded connections are relayed as raw bytes.

      Frames whose payload exceeds HttpProxyServerBootstrap.withMaxWebSocketFrameBufferSize(int) are streamed through and reported with WebSocketFrame.isTruncated() set.

      The default implementation delegates to the LittleProxy-compatible webSocketFrameReceived(Supplier, boolean).

      Parameters:
      frame - the WebSocket frame
      fromClient - true for client-to-server frames
    • webSocketFrameReceived

      default void webSocketFrameReceived(Supplier<byte[]> frameBytes, boolean fromClient)
      LittleProxy-compatible form of webSocketFrameReceived(WebSocketFrame, boolean): supplies the raw frame bytes as on the wire (header and still-masked payload).
      Parameters:
      frameBytes - the supplier of raw frame bytes, including the header and masked payload
      fromClient - whether the frame came from the client
    • filterWebSocketFrame

      default WebSocketFrame filterWebSocketFrame(WebSocketFrame frame, boolean fromClient)
      Rewrites or drops a WebSocket frame on an upgraded connection, after webSocketFrameReceived(WebSocketFrame, boolean) has seen it. Return frame to forward it unchanged, another frame (WebSocketFrame.withText(String), WebSocketFrame.text(String), ...) to send instead, or null to drop it. The proxy masks frames it sends towards the server.
      Parameters:
      frame - the WebSocket frame
      fromClient - true for client-to-server frames
      Returns:
      the frame to forward, or null to drop it
    • filterWebSocketMessage

      default WebSocketMessage filterWebSocketMessage(WebSocketMessage message, boolean fromClient)
      Rewrites or drops a whole WebSocket message on an upgraded connection. A text or binary message split across continuation frames arrives joined, and inflated when the connection uses permessage-deflate (RFC 7692). Return message to forward it unchanged, another message (WebSocketMessage.withText(String), WebSocketMessage.text(String, boolean), ...) to send instead, or null to drop it.
      • Messages are only joined when a filters class overrides this method or filterWebSocketMessages(org.microproxy.http.WebSocketMessage, boolean); otherwise frames are relayed one at a time.
      • With filterWebSocketFrame(org.microproxy.http.WebSocketFrame, boolean) as well, the frame hook sees each frame first, and this hook sees the message joined from what the frame hook returned. Control frames (ping, pong, close) that arrive between a message's frames reach the frame hook and are forwarded at once, without waiting for the message.
      • A message returned unchanged keeps the fragmentation it arrived with; any other message is sent as a single frame (masked towards the server, compressed when the sender's message was).
      • A message larger than HttpProxyServerBootstrap.withMaxWebSocketMessageSize(int), joined or inflated, fails the connection: both peers get a close frame with status 1009 (message too big).
      • Like the frame hooks, this is called concurrently for client and server messages.
      Parameters:
      message - the whole message
      fromClient - true for client-to-server messages
      Returns:
      the message to forward, or null to drop it
    • filterWebSocketMessages

      default List<WebSocketMessage> filterWebSocketMessages(WebSocketMessage message, boolean fromClient)
      The messages to send in place of message, in order. Each goes towards the server when its WebSocketMessage.fromClient() is true and towards the client otherwise, so a filter can answer a message itself (message.withFromClient(!fromClient)) or send extra ones either way. An empty list drops the message.

      The default returns what filterWebSocketMessage(org.microproxy.http.WebSocketMessage, boolean) returns, if anything; override this one instead to send several messages.

      Parameters:
      message - the whole message
      fromClient - true for client-to-server messages
      Returns:
      the messages to send