Commit 07c2fe8a authored by Metatronxl's avatar Metatronxl

update logback.xml

parent 58207f6d
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLSession;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
/**
* <p>
* Interface for receiving information about activity in the proxy.
* </p>
*
* <p>
* Sub-classes may wish to extend {@link ActivityTrackerAdapter} for sensible
* defaults.
* </p>
*/
public interface ActivityTracker {
/**
* Record that a client connected.
*
* @param clientAddress
*/
void clientConnected(InetSocketAddress clientAddress);
/**
* Record that a client's SSL handshake completed.
*
* @param clientAddress
* @param sslSession
*/
void clientSSLHandshakeSucceeded(InetSocketAddress clientAddress,
SSLSession sslSession);
/**
* Record that a client disconnected.
*
* @param clientAddress
* @param sslSession
*/
void clientDisconnected(InetSocketAddress clientAddress,
SSLSession sslSession);
/**
* Record that the proxy received bytes from the client.
*
* @param flowContext
* if full information is available, this will be a
* {@link FullFlowContext}.
* @param numberOfBytes
*/
void bytesReceivedFromClient(FlowContext flowContext,
int numberOfBytes);
/**
* <p>
* Record that proxy received an {@link HttpRequest} from the client.
* </p>
*
* <p>
* Note - on chunked transfers, this is only called once (for the initial
* HttpRequest object).
* </p>
*
* @param flowContext
* if full information is available, this will be a
* {@link FullFlowContext}.
* @param httpRequest
*/
void requestReceivedFromClient(FlowContext flowContext,
HttpRequest httpRequest);
/**
* Record that the proxy attempted to send bytes to the server.
*
* @param flowContext
* provides contextual information about the flow
* @param numberOfBytes
*/
void bytesSentToServer(FullFlowContext flowContext, int numberOfBytes);
/**
* <p>
* Record that proxy attempted to send a request to the server.
* </p>
*
* <p>
* Note - on chunked transfers, this is only called once (for the initial
* HttpRequest object).
* </p>
*
* @param flowContext
* provides contextual information about the flow
* @param httpRequest
*/
void requestSentToServer(FullFlowContext flowContext,
HttpRequest httpRequest);
/**
* Record that the proxy received bytes from the server.
*
* @param flowContext
* provides contextual information about the flow
* @param numberOfBytes
*/
void bytesReceivedFromServer(FullFlowContext flowContext, int numberOfBytes);
/**
* <p>
* Record that the proxy received an {@link HttpResponse} from the server.
* </p>
*
* <p>
* Note - on chunked transfers, this is only called once (for the initial
* HttpRequest object).
* </p>
*
* @param flowContext
* provides contextual information about the flow
* @param httpResponse
*/
void responseReceivedFromServer(FullFlowContext flowContext,
HttpResponse httpResponse);
/**
* Record that the proxy sent bytes to the client.
*
* @param flowContext
* if full information is available, this will be a
* {@link FullFlowContext}.
* @param numberOfBytes
*/
void bytesSentToClient(FlowContext flowContext, int numberOfBytes);
/**
* <p>
* Record that the proxy sent a response to the client.
* </p>
*
* <p>
* Note - on chunked transfers, this is only called once (for the initial
* HttpRequest object).
* </p>
*
* @param flowContext
* if full information is available, this will be a
* {@link FullFlowContext}.
* @param httpResponse
*/
void responseSentToClient(FlowContext flowContext,
HttpResponse httpResponse);
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLSession;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
/**
* Adapter of {@link ActivityTracker} interface that provides default no-op
* implementations of all methods.
*/
public class ActivityTrackerAdapter implements ActivityTracker {
@Override
public void bytesReceivedFromClient(FlowContext flowContext,
int numberOfBytes) {
}
@Override
public void requestReceivedFromClient(FlowContext flowContext,
HttpRequest httpRequest) {
}
@Override
public void bytesSentToServer(FullFlowContext flowContext, int numberOfBytes) {
}
@Override
public void requestSentToServer(FullFlowContext flowContext,
HttpRequest httpRequest) {
}
@Override
public void bytesReceivedFromServer(FullFlowContext flowContext,
int numberOfBytes) {
}
@Override
public void responseReceivedFromServer(FullFlowContext flowContext,
HttpResponse httpResponse) {
}
@Override
public void bytesSentToClient(FlowContext flowContext,
int numberOfBytes) {
}
@Override
public void responseSentToClient(FlowContext flowContext,
HttpResponse httpResponse) {
}
@Override
public void clientConnected(InetSocketAddress clientAddress) {
}
@Override
public void clientSSLHandshakeSucceeded(InetSocketAddress clientAddress,
SSLSession sslSession) {
}
@Override
public void clientDisconnected(InetSocketAddress clientAddress,
SSLSession sslSession) {
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import java.net.InetSocketAddress;
import io.netty.handler.codec.http.HttpObject;
/**
* <p>
* Encapsulates information needed to connect to a chained proxy.
* </p>
*
* <p>
* Sub-classes may wish to extend {@link ChainedProxyAdapter} for sensible
* defaults.
* </p>
*/
public interface ChainedProxy extends SslEngineSource {
/**
* Return the {@link InetSocketAddress} for connecting to the chained proxy.
* Returning null indicates that we won't chain.
*
* @return The Chain Proxy with Host and Port.
*/
InetSocketAddress getChainedProxyAddress();
/**
* (Optional) ensure that the connection is opened from a specific local
* address (useful when doing NAT traversal).
*
* @return
*/
InetSocketAddress getLocalAddress();
/**
* Tell LittleProxy what kind of TransportProtocol to use to communicate
* with the chained proxy.
*
* @return
*/
TransportProtocol getTransportProtocol();
/**
* Implement this method to tell LittleProxy whether or not to encrypt
* connections to the chained proxy for the given request. If true,
* LittleProxy will call {@link SslEngineSource#newSslEngine()} to obtain an
* SSLContext used by the downstream proxy.
*
* @return true of the connection to the chained proxy should be encrypted
*/
boolean requiresEncryption();
/**
* Filters requests on their way to the chained proxy.
*
* @param httpObject
*/
void filterRequest(HttpObject httpObject);
/**
* Called to let us know that connecting to this proxy succeeded.
*/
void connectionSucceeded();
/**
* Called to let us know that connecting to this proxy failed.
*
* @param cause
* exception that caused this failure (may be null)
*/
void connectionFailed(Throwable cause);
/**
* Called to let us know that we were disconnected.
*/
void disconnected();
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLEngine;
import io.netty.handler.codec.http.HttpObject;
/**
* Convenience base class for implementations of {@link ChainedProxy}.
*/
public class ChainedProxyAdapter implements ChainedProxy {
/**
* {@link ChainedProxy} that simply has the downstream proxy make a direct
* connection to the upstream server.
*/
public static ChainedProxy FALLBACK_TO_DIRECT_CONNECTION = new ChainedProxyAdapter();
@Override
public InetSocketAddress getChainedProxyAddress() {
return null;
}
@Override
public InetSocketAddress getLocalAddress() {
return null;
}
@Override
public TransportProtocol getTransportProtocol() {
return TransportProtocol.TCP;
}
@Override
public boolean requiresEncryption() {
return false;
}
@Override
public SSLEngine newSslEngine() {
return null;
}
@Override
public void filterRequest(HttpObject httpObject) {
}
@Override
public void connectionSucceeded() {
}
@Override
public void connectionFailed(Throwable cause) {
}
@Override
public void disconnected() {
}
@Override
public SSLEngine newSslEngine(String peerHost, int peerPort) {
return null;
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import java.util.Queue;
import io.netty.handler.codec.http.HttpRequest;
/**
* <p>
* Interface for classes that manage chained proxies.
* </p>
*/
public interface ChainedProxyManager {
/**
* <p>
* Based on the given httpRequest, add any {@link ChainedProxy}s to the list
* that should be used to process the request. The downstream proxy will
* attempt to connect to each of these in the order that they appear until
* it successfully connects to one.
* </p>
*
* <p>
* To allow the proxy to fall back to a direct connection, you can add
* {@link ChainedProxyAdapter#FALLBACK_TO_DIRECT_CONNECTION} to the end of
* the list.
* </p>
*
* <p>
* To keep the proxy from attempting any connection, leave the list blank.
* This will cause the proxy to return a 502 response.
* </p>
*
* @param httpRequest
* @param chainedProxies
*/
void lookupChainedProxies(HttpRequest httpRequest,
Queue<ChainedProxy> chainedProxies);
}
\ No newline at end of file
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
/**
* Default implementation of {@link HostResolver} that just uses
* {@link InetAddress#getByName(String)}.
*/
public class DefaultHostResolver implements HostResolver {
@Override
public InetSocketAddress resolve(String host, int port)
throws UnknownHostException {
InetAddress addr = InetAddress.getByName(host);
return new InetSocketAddress(addr, port);
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import org.littleshoot.dnssec4j.VerifiedAddressFactory;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
public class DnsSecServerResolver implements HostResolver {
@Override
public InetSocketAddress resolve(String host, int port)
throws UnknownHostException {
return VerifiedAddressFactory.newInetSocketAddress(host, port, true);
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl.ClientToProxyConnection;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
/**
* <p>
* Encapsulates contextual information for flow information that's being
* reported to a {@link ActivityTracker}.
* </p>
*/
public class FlowContext {
private final InetSocketAddress clientAddress;
private final SSLSession clientSslSession;
public FlowContext(ClientToProxyConnection clientConnection) {
super();
this.clientAddress = clientConnection.getClientAddress();
SSLEngine sslEngine = clientConnection.getSslEngine();
this.clientSslSession = sslEngine != null ? sslEngine.getSession()
: null;
}
/**
* The address of the client.
*
* @return
*/
public InetSocketAddress getClientAddress() {
return clientAddress;
}
/**
* If using SSL, this returns the {@link SSLSession} on the client
* connection.
*
* @return
*/
public SSLSession getClientSslSession() {
return clientSslSession;
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl.ClientToProxyConnection;
import org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl.ProxyToServerConnection;
/**
* Extension of {@link FlowContext} that provides additional information (which
* we know after actually processing the request from the client).
*/
public class FullFlowContext extends FlowContext {
private final String serverHostAndPort;
private final ChainedProxy chainedProxy;
public FullFlowContext(ClientToProxyConnection clientConnection,
ProxyToServerConnection serverConnection) {
super(clientConnection);
this.serverHostAndPort = serverConnection.getServerHostAndPort();
this.chainedProxy = serverConnection.getChainedProxy();
}
/**
* The host and port for the server (i.e. the ultimate endpoint).
*
* @return
*/
public String getServerHostAndPort() {
return serverHostAndPort;
}
/**
* The chained proxy (if proxy chaining).
*
* @return
*/
public ChainedProxy getChainedProxy() {
return chainedProxy;
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
/**
* Resolves host and port into an InetSocketAddress.
*/
public interface HostResolver {
public InetSocketAddress resolve(String host, int port)
throws UnknownHostException;
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl.ProxyUtils;
import java.net.InetSocketAddress;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
/**
* <p>
* Interface for objects that filter {@link HttpObject}s, including both
* requests and responses, and informs of different steps in request/response.
* </p>
*
* <p>
* Multiple methods are defined, corresponding to different steps in the request
* processing lifecycle. Some of these methods is given the current object
* (request, response or chunk) and is allowed to modify it in place. Others
* provide a notification of when specific operations happen (i.e. connection in
* queue, DNS resolution, SSL handshaking and so forth).
* </p>
*
* <p>
* Because HTTP transfers can be chunked, for any given request or response, the
* filter methods that can modify request/response in place may be called
* multiple times, once for the initial {@link HttpRequest} or
* {@link HttpResponse}, and once for each subsequent {@link HttpContent}. The
* last chunk will always be a {@link LastHttpContent} and can be checked for
* being last using {@link ProxyUtils#isLastChunk(HttpObject)}.
* </p>
*
* <p>
* {@link HttpFiltersSource#getMaximumRequestBufferSizeInBytes()} and
* {@link HttpFiltersSource#getMaximumResponseBufferSizeInBytes()} can be used
* to instruct the proxy to buffer the {@link HttpObject}s sent to all of its
* request/response filters, in which case it will buffer up to the specified
* limit and then send either complete {@link HttpRequest}s or
* {@link HttpResponse}s to the filter methods. When buffering, if the proxy
* receives more data than fits in the specified maximum bytes to buffer, the
* proxy will stop processing the request and respond with a 502 Bad Gateway
* error.
* </p>
*
* <p>
* A new instance of {@link HttpFilters} is created for each request, so these
* objects can be stateful.
* </p>
*
* <p>
* To monitor (and time measure?) the different steps the request/response goes
* through, many informative methods are provided. Those steps are reported in
* the following order:
* <ol>
* <li>clientToProxyRequest</li>
* <li>proxyToServerConnectionQueued</li>
* <li>proxyToServerResolutionStarted</li>
* <li>proxyToServerResolutionSucceeded</li>
* <li>proxyToServerRequest (can be multiple if chunked)</li>
* <li>proxyToServerConnectionStarted</li>
* <li>proxyToServerConnectionFailed (if connection couldn't be established)</li>
* <li>proxyToServerConnectionSSLHandshakeStarted (only if HTTPS required)</li>
* <li>proxyToServerConnectionSucceeded</li>
* <li>proxyToServerRequestSending</li>
* <li>proxyToServerRequestSent</li>
* <li>serverToProxyResponseReceiving</li>
* <li>serverToProxyResponse (can be multiple if chuncked)</li>
* <li>serverToProxyResponseReceived</li>
* <li>proxyToClientResponse</li>
* </ol>
*/
public interface HttpFilters {
/**
* Filters requests on their way from the client to the proxy. To interrupt processing of this request and return a
* response to the client immediately, return an HttpResponse here. Otherwise, return null to continue processing as
* usual.
* <p>
* <b>Important:</b> When returning a response, you must include a mechanism to allow the client to determine the length
* of the message (see RFC 7230, section 3.3.3: https://tools.ietf.org/html/rfc7230#section-3.3.3 ). For messages that
* may contain a body, you may do this by setting the Transfer-Encoding to chunked, setting an appropriate
* Content-Length, or by adding a "Connection: close" header to the response (which will instruct LittleProxy to close
* the connection). If the short-circuit response contains body content, it is recommended that you return a
* FullHttpResponse.
*
* @param httpObject Client to Proxy HttpRequest (and HttpContent, if chunked)
* @return a short-circuit response, or null to continue processing as usual
*/
HttpResponse clientToProxyRequest(HttpObject httpObject);
/**
* Filters requests on their way from the proxy to the server. To interrupt processing of this request and return a
* response to the client immediately, return an HttpResponse here. Otherwise, return null to continue processing as
* usual.
* <p>
* <b>Important:</b> When returning a response, you must include a mechanism to allow the client to determine the length
* of the message (see RFC 7230, section 3.3.3: https://tools.ietf.org/html/rfc7230#section-3.3.3 ). For messages that
* may contain a body, you may do this by setting the Transfer-Encoding to chunked, setting an appropriate
* Content-Length, or by adding a "Connection: close" header to the response. (which will instruct LittleProxy to close
* the connection). If the short-circuit response contains body content, it is recommended that you return a
* FullHttpResponse.
*
* @param httpObject Proxy to Server HttpRequest (and HttpContent, if chunked)
* @return a short-circuit response, or null to continue processing as usual
*/
HttpResponse proxyToServerRequest(HttpObject httpObject);
/**
* Informs filter that proxy to server request is being sent.
*/
void proxyToServerRequestSending();
/**
* Informs filter that the HTTP request, including any content, has been sent.
*/
void proxyToServerRequestSent();
/**
* Filters responses on their way from the server to the proxy.
*
* @param httpObject
* Server to Proxy HttpResponse (and HttpContent, if chunked)
* @return the modified (or unmodified) HttpObject. Returning null will
* force a disconnect.
*/
HttpObject serverToProxyResponse(HttpObject httpObject);
/**
* Informs filter that a timeout occurred before the server response was received by the client. The timeout may have
* occurred while the client was sending the request, waiting for a response, or after the client started receiving
* a response (i.e. if the response from the server "stalls").
*
* See {@link HttpProxyServerBootstrap#withIdleConnectionTimeout(int)} for information on setting the timeout.
*/
void serverToProxyResponseTimedOut();
/**
* Informs filter that server to proxy response is being received.
*/
void serverToProxyResponseReceiving();
/**
* Informs filter that server to proxy response has been received.
*/
void serverToProxyResponseReceived();
/**
* Filters responses on their way from the proxy to the client.
*
* @param httpObject
* Proxy to Client HttpResponse (and HttpContent, if chunked)
* @return the modified (or unmodified) HttpObject. Returning null will
* force a disconnect.
*/
HttpObject proxyToClientResponse(HttpObject httpObject);
/**
* Informs filter that proxy to server connection is in queue.
*/
void proxyToServerConnectionQueued();
/**
* Filter DNS resolution from proxy to server.
*
* @param resolvingServerHostAndPort
* Server "HOST:PORT"
* @return alternative address resolution. Returning null will let normal
* DNS resolution continue.
*/
InetSocketAddress proxyToServerResolutionStarted(
String resolvingServerHostAndPort);
/**
* Informs filter that proxy to server DNS resolution failed for the specified host and port.
*
* @param hostAndPort hostname and port the proxy failed to resolve
*/
void proxyToServerResolutionFailed(String hostAndPort);
/**
* Informs filter that proxy to server DNS resolution has happened.
*
* @param serverHostAndPort
* Server "HOST:PORT"
* @param resolvedRemoteAddress
* Address it was proxyToServerResolutionSucceeded to
*/
void proxyToServerResolutionSucceeded(String serverHostAndPort,
InetSocketAddress resolvedRemoteAddress);
/**
* Informs filter that proxy to server connection is initiating.
*/
void proxyToServerConnectionStarted();
/**
* Informs filter that proxy to server ssl handshake is initiating.
*/
void proxyToServerConnectionSSLHandshakeStarted();
/**
* Informs filter that proxy to server connection has failed.
*/
void proxyToServerConnectionFailed();
/**
* Informs filter that proxy to server connection has succeeded.
*
* @param serverCtx the {@link ChannelHandlerContext} used to connect to the server
*/
void proxyToServerConnectionSucceeded(ChannelHandlerContext serverCtx);
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import java.net.InetSocketAddress;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
/**
* Convenience base class for implementations of {@link HttpFilters}.
*/
public class HttpFiltersAdapter implements HttpFilters {
/**
* A default, stateless, no-op {@link HttpFilters} instance.
*/
public static final HttpFiltersAdapter NOOP_FILTER = new HttpFiltersAdapter(null);
protected final HttpRequest originalRequest;
protected final ChannelHandlerContext ctx;
public HttpFiltersAdapter(HttpRequest originalRequest,
ChannelHandlerContext ctx) {
this.originalRequest = originalRequest;
this.ctx = ctx;
}
public HttpFiltersAdapter(HttpRequest originalRequest) {
this(originalRequest, null);
}
@Override
public HttpResponse clientToProxyRequest(HttpObject httpObject) {
return null;
}
@Override
public HttpResponse proxyToServerRequest(HttpObject httpObject) {
return null;
}
@Override
public void proxyToServerRequestSending() {
}
@Override
public void proxyToServerRequestSent() {
}
@Override
public HttpObject serverToProxyResponse(HttpObject httpObject) {
return httpObject;
}
@Override
public void serverToProxyResponseTimedOut() {
}
@Override
public void serverToProxyResponseReceiving() {
}
@Override
public void serverToProxyResponseReceived() {
}
@Override
public HttpObject proxyToClientResponse(HttpObject httpObject) {
return httpObject;
}
@Override
public void proxyToServerConnectionQueued() {
}
@Override
public InetSocketAddress proxyToServerResolutionStarted(
String resolvingServerHostAndPort) {
return null;
}
@Override
public void proxyToServerResolutionFailed(String hostAndPort) {
}
@Override
public void proxyToServerResolutionSucceeded(String serverHostAndPort,
InetSocketAddress resolvedRemoteAddress) {
}
@Override
public void proxyToServerConnectionStarted() {
}
@Override
public void proxyToServerConnectionSSLHandshakeStarted() {
}
@Override
public void proxyToServerConnectionFailed() {
}
@Override
public void proxyToServerConnectionSucceeded(ChannelHandlerContext serverCtx) {
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
/**
* Factory for {@link HttpFilters}.
*/
public interface HttpFiltersSource {
/**
* Return an {@link HttpFilters} object for this request if and only if we
* want to filter the request and/or its responses.
*
* @param originalRequest
* @return
*/
HttpFilters filterRequest(HttpRequest originalRequest,
ChannelHandlerContext ctx);
/**
* Indicate how many (if any) bytes to buffer for incoming
* {@link HttpRequest}s. A value of 0 or less indicates that no buffering
* should happen and that messages will be passed to the {@link HttpFilters}
* request filtering methods chunk by chunk. A positive value will cause
* LittleProxy to try an create a {@link FullHttpRequest} using the data
* received from the client, with its content already decompressed (in case
* the client was compressing it). If the request size exceeds the maximum
* buffer size, the request will fail.
*
* @return
*/
int getMaximumRequestBufferSizeInBytes();
/**
* Indicate how many (if any) bytes to buffer for incoming
* {@link HttpResponse}s. A value of 0 or less indicates that no buffering
* should happen and that messages will be passed to the {@link HttpFilters}
* response filtering methods chunk by chunk. A positive value will cause
* LittleProxy to try an create a {@link FullHttpResponse} using the data
* received from the server, with its content already decompressed (in case
* the server was compressing it). If the response size exceeds the maximum
* buffer size, the response will fail.
*
* @return
*/
int getMaximumResponseBufferSizeInBytes();
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpRequest;
/**
* Convenience base class for implementations of {@link HttpFiltersSource}.
*/
public class HttpFiltersSourceAdapter implements HttpFiltersSource {
public HttpFilters filterRequest(HttpRequest originalRequest) {
return new HttpFiltersAdapter(originalRequest, null);
}
@Override
public HttpFilters filterRequest(HttpRequest originalRequest,
ChannelHandlerContext ctx) {
return filterRequest(originalRequest);
}
@Override
public int getMaximumRequestBufferSizeInBytes() {
return 0;
}
@Override
public int getMaximumResponseBufferSizeInBytes() {
return 0;
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import java.net.InetSocketAddress;
/**
* Interface for the top-level proxy server class.
*/
public interface HttpProxyServer {
int getIdleConnectionTimeout();
void setIdleConnectionTimeout(int idleConnectionTimeout);
/**
* Returns the maximum time to wait, in milliseconds, to connect to a server.
*/
int getConnectTimeout();
/**
* Sets the maximum time to wait, in milliseconds, to connect to a server.
*/
void setConnectTimeout(int connectTimeoutMs);
/**
* <p>
* Clone the existing server, with a port 1 higher and everything else the
* same. If the proxy was started with port 0 (JVM-assigned port), the cloned proxy will also use a JVM-assigned
* port.
* </p>
*
* <p>
* The new server will share event loops with the original server. The event
* loops will use whatever name was given to the first server in the clone
* group. The server group will not terminate until the original server and all clones terminate.
* </p>
*
* @return a bootstrap that allows customizing and starting the cloned
* server
*/
HttpProxyServerBootstrap clone();
/**
* Stops the server and all related clones. Waits for traffic to stop before shutting down.
*/
void stop();
/**
* Stops the server and all related clones immediately, without waiting for traffic to stop.
*/
void abort();
/**
* Return the address on which this proxy is listening.
*
* @return
*/
InetSocketAddress getListenAddress();
/**
* <p>
* Set the read/write throttle bandwidths (in bytes/second) for this proxy.
* </p>
* @param readThrottleBytesPerSecond
* @param writeThrottleBytesPerSecond
*/
void setThrottle(long readThrottleBytesPerSecond, long writeThrottleBytesPerSecond);
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl.ThreadPoolConfiguration;
import java.net.InetSocketAddress;
/**
* Configures and starts an {@link HttpProxyServer}. The HttpProxyServer is
* built using {@link #start()}. Sensible defaults are available for all
* parameters such that {@link #start()} could be called immediately if you
* wish.
*/
public interface HttpProxyServerBootstrap {
/**
* <p>
* Give the server a name (used for naming threads, useful for logging).
* </p>
*
* <p>
* Default = LittleProxy
* </p>
*
* @param name
* @return
*/
HttpProxyServerBootstrap withName(String name);
/**
* <p>
* Specify the {@link TransportProtocol} to use for incoming connections.
* </p>
*
* <p>
* Default = TCP
* </p>
*
* @param transportProtocol
* @return
*/
HttpProxyServerBootstrap withTransportProtocol(
TransportProtocol transportProtocol);
/**
* <p>
* Listen for incoming connections on the given address.
* </p>
*
* <p>
* Default = [bound ip]:8080
* </p>
*
* @param address
* @return
*/
HttpProxyServerBootstrap withAddress(InetSocketAddress address);
/**
* <p>
* Listen for incoming connections on the given port.
* </p>
*
* <p>
* Default = 8080
* </p>
*
* @param port
* @return
*/
HttpProxyServerBootstrap withPort(int port);
/**
* <p>
* Specify whether or not to only allow local connections.
* </p>
*
* <p>
* Default = true
* </p>
*
* @param allowLocalOnly
* @return
*/
HttpProxyServerBootstrap withAllowLocalOnly(boolean allowLocalOnly);
/**
* This method has no effect and will be removed in a future release.
* @deprecated use {@link #withNetworkInterface(InetSocketAddress)} to avoid listening on all local addresses
*/
@Deprecated
HttpProxyServerBootstrap withListenOnAllAddresses(boolean listenOnAllAddresses);
/**
* <p>
* Specify an {@link SslEngineSource} to use for encrypting inbound
* connections. Enabling this will enable SSL client authentication
* by default (see {@link #withAuthenticateSslClients(boolean)})
* </p>
*
* <p>
* Default = null
* </p>
*
* <p>
* Note - This and {@link #withManInTheMiddle(MitmManager)} are
* mutually exclusive.
* </p>
*
* @param sslEngineSource
* @return
*/
HttpProxyServerBootstrap withSslEngineSource(
SslEngineSource sslEngineSource);
/**
* <p>
* Specify whether or not to authenticate inbound SSL clients (only applies
* if {@link #withSslEngineSource(SslEngineSource)} has been set).
* </p>
*
* <p>
* Default = true
* </p>
*
* @param authenticateSslClients
* @return
*/
HttpProxyServerBootstrap withAuthenticateSslClients(
boolean authenticateSslClients);
/**
* <p>
* Specify a {@link ProxyAuthenticator} to use for doing basic HTTP
* authentication of clients.
* </p>
*
* <p>
* Default = null
* </p>
*
* @param proxyAuthenticator
* @return
*/
HttpProxyServerBootstrap withProxyAuthenticator(
ProxyAuthenticator proxyAuthenticator);
/**
* <p>
* Specify a {@link ChainedProxyManager} to use for chaining requests to
* another proxy.
* </p>
*
* <p>
* Default = null
* </p>
*
* @param chainProxyManager
* @return
*/
HttpProxyServerBootstrap withChainProxyManager(
ChainedProxyManager chainProxyManager);
/**
* <p>
* Specify an {@link MitmManager} to use for making this proxy act as an SSL
* man in the middle
* </p>
*
* <p>
* Default = null
* </p>
*
* <p>
* Note - This and {@link #withSslEngineSource(SslEngineSource)} are
* mutually exclusive.
* </p>
*
* @param mitmManager
* @return
*/
HttpProxyServerBootstrap withManInTheMiddle(
MitmManager mitmManager);
/**
* <p>
* Specify a {@link HttpFiltersSource} to use for filtering requests and/or
* responses through this proxy.
* </p>
*
* <p>
* Default = null
* </p>
*
* @param filtersSource
* @return
*/
HttpProxyServerBootstrap withFiltersSource(
HttpFiltersSource filtersSource);
/**
* <p>
* Specify whether or not to use secure DNS lookups for outbound
* connections.
* </p>
*
* <p>
* Default = false
* </p>
*
* @param useDnsSec
* @return
*/
HttpProxyServerBootstrap withUseDnsSec(
boolean useDnsSec);
/**
* <p>
* Specify whether or not to run this proxy as a transparent proxy.
* </p>
*
* <p>
* Default = false
* </p>
*
* @param transparent
* @return
*/
HttpProxyServerBootstrap withTransparent(
boolean transparent);
/**
* <p>
* Specify the timeout after which to disconnect idle connections, in
* seconds.
* </p>
*
* <p>
* Default = 70
* </p>
*
* @param idleConnectionTimeout
* @return
*/
HttpProxyServerBootstrap withIdleConnectionTimeout(
int idleConnectionTimeout);
/**
* <p>
* Specify the timeout for connecting to the upstream server on a new
* connection, in milliseconds.
* </p>
*
* <p>
* Default = 40000
* </p>
*
* @param connectTimeout
* @return
*/
HttpProxyServerBootstrap withConnectTimeout(
int connectTimeout);
/**
* Specify a custom {@link HostResolver} for resolving server addresses.
*
* @param serverResolver
* @return
*/
HttpProxyServerBootstrap withServerResolver(HostResolver serverResolver);
/**
* <p>
* Add an {@link ActivityTracker} for tracking activity in this proxy.
* </p>
*
* @param activityTracker
* @return
*/
HttpProxyServerBootstrap plusActivityTracker(ActivityTracker activityTracker);
/**
* <p>
* Specify the read and/or write bandwidth throttles for this proxy server. 0 indicates not throttling.
* </p>
* @param readThrottleBytesPerSecond
* @param writeThrottleBytesPerSecond
* @return
*/
HttpProxyServerBootstrap withThrottling(long readThrottleBytesPerSecond, long writeThrottleBytesPerSecond);
/**
* All outgoing-communication of the proxy-instance is goin' to be routed via the given network-interface
*
* @param inetSocketAddress to be used for outgoing communication
*/
HttpProxyServerBootstrap withNetworkInterface(InetSocketAddress inetSocketAddress);
HttpProxyServerBootstrap withMaxInitialLineLength(int maxInitialLineLength);
HttpProxyServerBootstrap withMaxHeaderSize(int maxHeaderSize);
HttpProxyServerBootstrap withMaxChunkSize(int maxChunkSize);
/**
* When true, the proxy will accept requests that appear to be directed at an origin server (i.e. the URI in the HTTP
* request will contain an origin-form, rather than an absolute-form, as specified in RFC 7230, section 5.3).
* This is useful when the proxy is acting as a gateway/reverse proxy. <b>Note:</b> This feature should not be
* enabled when running as a forward proxy; doing so may cause an infinite loop if the client requests the URI of the proxy.
*
* @param allowRequestToOriginServer when true, the proxy will accept origin-form HTTP requests
*/
HttpProxyServerBootstrap withAllowRequestToOriginServer(boolean allowRequestToOriginServer);
/**
* Sets the alias to use when adding Via headers to incoming and outgoing HTTP messages. The alias may be any
* pseudonym, or if not specified, defaults to the hostname of the local machine. See RFC 7230, section 5.7.1.
*
* @param alias the pseudonym to add to Via headers
*/
HttpProxyServerBootstrap withProxyAlias(String alias);
/**
* <p>
* Build and starts the server.
* </p>
*
* @return the newly built and started server
*/
HttpProxyServer start();
/**
* Set the configuration parameters for the proxy's thread pools.
*
* @param configuration thread pool configuration
* @return proxy server bootstrap for chaining
*/
HttpProxyServerBootstrap withThreadPoolConfiguration(ThreadPoolConfiguration configuration);
}
\ No newline at end of file
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import org.apache.log4j.xml.DOMConfigurator;
import org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl.DefaultHttpProxyServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
/**
* Launches a new HTTP proxy.
*/
public class Launcher {
private static final Logger LOG = LoggerFactory.getLogger(Launcher.class);
private static final String OPTION_DNSSEC = "dnssec";
private static final String OPTION_PORT = "port";
private static final String OPTION_HELP = "help";
private static final String OPTION_MITM = "mitm";
private static final String OPTION_NIC = "nic";
/**
* Starts the proxy from the command line.
*
* @param port Any command line arguments.
*/
public static void startHttpProxyService(int port) {
pollLog4JConfigurationFileIfAvailable();
System.out.println("About to start server on port: " + port);
HttpProxyServerBootstrap bootstrap = DefaultHttpProxyServer
.bootstrapFromFile("./littleproxy.properties")
.withPort(port)
.withAllowLocalOnly(false);
System.out.println("About to start...");
bootstrap.start();
}
private static void pollLog4JConfigurationFileIfAvailable() {
File log4jConfigurationFile = new File("src/test/resources/log4j.xml");
if (log4jConfigurationFile.exists()) {
DOMConfigurator.configureAndWatch(
log4jConfigurationFile.getAbsolutePath(), 15);
}
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import io.netty.handler.codec.http.HttpRequest;
/**
* MITMManagers encapsulate the logic required for letting LittleProxy act as a
* man in the middle for HTTPS requests.
*/
public interface MitmManager {
/**
* Creates an {@link SSLEngine} for encrypting the server connection. The SSLEngine created by this method
* may use the given peer information to send SNI information when connecting to the upstream host.
*
* @param peerHost to start a client connection to the server.
* @param peerPort to start a client connection to the server.
*
* @return an SSLEngine used to connect to an upstream server
*/
SSLEngine serverSslEngine(String peerHost, int peerPort);
/**
* Creates an {@link SSLEngine} for encrypting the server connection.
*
* @return an SSLEngine used to connect to an upstream server
*/
SSLEngine serverSslEngine();
/**
* <p>
* Creates an {@link SSLEngine} for encrypting the client connection based
* on the given serverSslSession.
* </p>
*
* <p>
* The serverSslSession is provided in case this method needs to inspect the
* server's certificates or something else about the encryption on the way
* to the server.
* </p>
*
* <p>
* This is the place where one would implement impersonation of the server
* by issuing replacement certificates signed by the proxy's own
* certificate.
* </p>
*
* @param httpRequest the HTTP CONNECT request that is being man-in-the-middled
* @param serverSslSession the {@link SSLSession} that's been established with the server
* @return the SSLEngine used to connect to the client
*/
SSLEngine clientSslEngineFor(HttpRequest httpRequest, SSLSession serverSslSession);
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
/**
* Interface for objects that can authenticate someone for using our Proxy on
* the basis of a username and password.
*/
public interface ProxyAuthenticator {
/**
* Authenticates the user using the specified userName and password.
*
* @param userName
* The user name.
* @param password
* The password.
* @return <code>true</code> if the credentials are acceptable, otherwise
* <code>false</code>.
*/
boolean authenticate(String userName, String password);
/**
* The realm value to be used in the request for proxy authentication
* ("Proxy-Authenticate" header). Returning null will cause the string
* "Restricted Files" to be used by default.
*
* @return
*/
String getRealm();
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
import javax.net.ssl.SSLEngine;
/**
* Source for {@link SSLEngine}s.
*/
public interface SslEngineSource {
/**
* Returns an {@link SSLEngine} to use for a server connection from
* LittleProxy to the client.
*
* @return
*/
SSLEngine newSslEngine();
/**
* Returns an {@link SSLEngine} to use for a client connection from
* LittleProxy to the upstream server. *
*
* Note: Peer information is needed to send the server_name extension in
* handshake with Server Name Indication (SNI).
*
* @param peerHost
* to start a client connection to the server.
* @param peerPort
* to start a client connection to the server.
* @return
*/
SSLEngine newSslEngine(String peerHost, int peerPort);
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
/**
* Enumeration of transport protocols supported by LittleProxy.
*/
public enum TransportProtocol {
TCP, UDT
}
\ No newline at end of file
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy;
/**
* This exception indicates that the system was asked to use a TransportProtocol that it didn't know how to handle.
*/
public class UnknownTransportProtocolException extends RuntimeException {
private static final long serialVersionUID = 1L;
public UnknownTransportProtocolException(TransportProtocol transportProtocol) {
super(String.format("Unknown TransportProtocol: %1$s", transportProtocol));
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.extras;
import org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.MitmManager;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import io.netty.handler.codec.http.HttpRequest;
/**
* {@link MitmManager} that uses self-signed certs for everything.
*/
public class SelfSignedMitmManager implements MitmManager {
SelfSignedSslEngineSource selfSignedSslEngineSource =
new SelfSignedSslEngineSource(true);
@Override
public SSLEngine serverSslEngine(String peerHost, int peerPort) {
return selfSignedSslEngineSource.newSslEngine(peerHost, peerPort);
}
@Override
public SSLEngine serverSslEngine() {
return selfSignedSslEngineSource.newSslEngine();
}
@Override
public SSLEngine clientSslEngineFor(HttpRequest httpRequest, SSLSession serverSslSession) {
return selfSignedSslEngineSource.newSslEngine();
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.extras;
import com.google.common.io.ByteStreams;
import org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.SslEngineSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* Basic {@link SslEngineSource} for testing. The {@link SSLContext} uses
* self-signed certificates that are generated lazily if the given key store
* file doesn't yet exist.
*/
public class SelfSignedSslEngineSource implements SslEngineSource {
private static final Logger LOG = LoggerFactory
.getLogger(SelfSignedSslEngineSource.class);
private static final String ALIAS = "littleproxy";
private static final String PASSWORD = "Be Your Own Lantern";
private static final String PROTOCOL = "TLS";
private final File keyStoreFile;
private final boolean trustAllServers;
private final boolean sendCerts;
private SSLContext sslContext;
public SelfSignedSslEngineSource(String keyStorePath,
boolean trustAllServers, boolean sendCerts) {
this.trustAllServers = trustAllServers;
this.sendCerts = sendCerts;
this.keyStoreFile = new File(keyStorePath);
initializeKeyStore();
initializeSSLContext();
}
public SelfSignedSslEngineSource(String keyStorePath) {
this(keyStorePath, false, true);
}
public SelfSignedSslEngineSource(boolean trustAllServers) {
this(trustAllServers, true);
}
public SelfSignedSslEngineSource(boolean trustAllServers, boolean sendCerts) {
this("littleproxy_keystore.jks", trustAllServers, sendCerts);
}
public SelfSignedSslEngineSource() {
this(false);
}
@Override
public SSLEngine newSslEngine() {
return sslContext.createSSLEngine();
}
@Override
public SSLEngine newSslEngine(String peerHost, int peerPort) {
return sslContext.createSSLEngine(peerHost, peerPort);
}
public SSLContext getSslContext() {
return sslContext;
}
private void initializeKeyStore() {
if (keyStoreFile.isFile()) {
LOG.info("Not deleting keystore");
return;
}
nativeCall("keytool", "-genkey", "-alias", ALIAS, "-keysize",
"4096", "-validity", "36500", "-keyalg", "RSA", "-dname",
"CN=littleproxy", "-keypass", PASSWORD, "-storepass",
PASSWORD, "-keystore", keyStoreFile.getName());
nativeCall("keytool", "-exportcert", "-alias", ALIAS, "-keystore",
keyStoreFile.getName(), "-storepass", PASSWORD, "-file",
"littleproxy_cert");
}
private void initializeSSLContext() {
String algorithm = Security
.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
}
try {
final KeyStore ks = KeyStore.getInstance("JKS");
// ks.load(new FileInputStream("keystore.jks"),
// "changeit".toCharArray());
ks.load(new FileInputStream(keyStoreFile), PASSWORD.toCharArray());
// Set up key manager factory to use our key store
final KeyManagerFactory kmf =
KeyManagerFactory.getInstance(algorithm);
kmf.init(ks, PASSWORD.toCharArray());
// Set up a trust manager factory to use our key store
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(algorithm);
tmf.init(ks);
TrustManager[] trustManagers = null;
if (!trustAllServers) {
trustManagers = tmf.getTrustManagers();
} else {
trustManagers = new TrustManager[] { new X509TrustManager() {
// TrustManager that trusts all servers
@Override
public void checkClientTrusted(X509Certificate[] arg0,
String arg1)
throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] arg0,
String arg1)
throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
} };
}
KeyManager[] keyManagers = null;
if (sendCerts) {
keyManagers = kmf.getKeyManagers();
} else {
keyManagers = new KeyManager[0];
}
// Initialize the SSLContext to work with our key managers.
sslContext = SSLContext.getInstance(PROTOCOL);
sslContext.init(keyManagers, trustManagers, null);
} catch (final Exception e) {
throw new Error(
"Failed to initialize the server-side SSLContext", e);
}
}
private String nativeCall(final String... commands) {
LOG.info("Running '{}'", Arrays.asList(commands));
final ProcessBuilder pb = new ProcessBuilder(commands);
try {
final Process process = pb.start();
final InputStream is = process.getInputStream();
byte[] data = ByteStreams.toByteArray(is);
String dataAsString = new String(data);
LOG.info("Completed native call: '{}'\nResponse: '" + dataAsString + "'",
Arrays.asList(commands));
return dataAsString;
} catch (final IOException e) {
LOG.error("Error running commands: " + Arrays.asList(commands), e);
return "";
}
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A ThreadFactory that adds LittleProxy-specific information to the threads' names.
*/
public class CategorizedThreadFactory implements ThreadFactory {
private static final Logger log = LoggerFactory.getLogger(CategorizedThreadFactory.class);
private final String name;
private final String category;
private final int uniqueServerGroupId;
private AtomicInteger threadCount = new AtomicInteger(0);
/**
* Exception handler for proxy threads. Logs the name of the thread and the exception that was caught.
*/
private static final Thread.UncaughtExceptionHandler UNCAUGHT_EXCEPTION_HANDLER = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("Uncaught throwable in thread: {}", t.getName(), e);
}
};
/**
* @param name the user-supplied name of this proxy
* @param category the type of threads this factory is creating (acceptor, client-to-proxy worker, proxy-to-server worker)
* @param uniqueServerGroupId a unique number for the server group creating this thread factory, to differentiate multiple proxy instances with the same name
*/
public CategorizedThreadFactory(String name, String category, int uniqueServerGroupId) {
this.category = category;
this.name = name;
this.uniqueServerGroupId = uniqueServerGroupId;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, name + "-" + uniqueServerGroupId + "-" + category + "-" + threadCount.getAndIncrement());
t.setUncaughtExceptionHandler(UNCAUGHT_EXCEPTION_HANDLER);
return t;
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
/**
* Coordinates the various steps involved in establishing a connection, such as
* establishing a socket connection, SSL handshaking, HTTP CONNECT request
* processing, and so on.
*/
class ConnectionFlow {
private Queue<ConnectionFlowStep> steps = new ConcurrentLinkedQueue<ConnectionFlowStep>();
private final ClientToProxyConnection clientConnection;
private final ProxyToServerConnection serverConnection;
private volatile ConnectionFlowStep currentStep;
private volatile boolean suppressInitialRequest = false;
private final Object connectLock;
/**
* Construct a new {@link ConnectionFlow} for the given client and server
* connections.
*
* @param clientConnection
* @param serverConnection
* @param connectLock
* an object that's shared by {@link ConnectionFlow} and
* {@link ProxyToServerConnection} and that is used for
* synchronizing the reader and writer threads that are both
* involved during the establishing of a connection.
*/
ConnectionFlow(
ClientToProxyConnection clientConnection,
ProxyToServerConnection serverConnection,
Object connectLock) {
super();
this.clientConnection = clientConnection;
this.serverConnection = serverConnection;
this.connectLock = connectLock;
}
/**
* Add a {@link ConnectionFlowStep} to this flow.
*
* @param step
* @return
*/
ConnectionFlow then(ConnectionFlowStep step) {
steps.add(step);
return this;
}
/**
* While we're in the process of connecting, any messages read by the
* {@link ProxyToServerConnection} are passed to this method, which passes
* it on to {@link ConnectionFlowStep#read(ConnectionFlow, Object)} for the
* current {@link ConnectionFlowStep}.
*
* @param msg
*/
void read(Object msg) {
if (this.currentStep != null) {
this.currentStep.read(this, msg);
}
}
/**
* Starts the connection flow, notifying the {@link ClientToProxyConnection}
* that we've started.
*/
void start() {
clientConnection.serverConnectionFlowStarted(serverConnection);
advance();
}
/**
* <p>
* Advances the flow. {@link #advance()} will be called until we're either
* out of steps, or a step has failed.
* </p>
*/
void advance() {
currentStep = steps.poll();
if (currentStep == null) {
succeed();
} else {
processCurrentStep();
}
}
/**
* <p>
* Process the current {@link ConnectionFlowStep}. With each step, we:
* </p>
*
* <ol>
* <li>Change the state of the associated {@link ProxyConnection} to the
* value of {@link ConnectionFlowStep#getState()}</li>
* <li>Call {@link ConnectionFlowStep#execute()}</li>
* <li>On completion of the {@link Future} returned by
* {@link ConnectionFlowStep#execute()}, check the success.</li>
* <li>If successful, we call back into
* {@link ConnectionFlowStep#onSuccess(ConnectionFlow)}.</li>
* <li>If unsuccessful, we call {@link #fail()}, stopping the connection
* flow</li>
* </ol>
*/
private void processCurrentStep() {
final ProxyConnection connection = currentStep.getConnection();
final ProxyConnectionLogger LOG = connection.getLOG();
LOG.debug("Processing connection flow step: {}", currentStep);
connection.become(currentStep.getState());
suppressInitialRequest = suppressInitialRequest
|| currentStep.shouldSuppressInitialRequest();
if (currentStep.shouldExecuteOnEventLoop()) {
connection.ctx.executor().submit(new Runnable() {
@Override
public void run() {
doProcessCurrentStep(LOG);
}
});
} else {
doProcessCurrentStep(LOG);
}
}
/**
* Does the work of processing the current step, checking the result and
* handling success/failure.
*
* @param LOG
*/
@SuppressWarnings("unchecked")
private void doProcessCurrentStep(final ProxyConnectionLogger LOG) {
currentStep.execute().addListener(
new GenericFutureListener<Future<?>>() {
public void operationComplete(
Future<?> future)
throws Exception {
synchronized (connectLock) {
if (future.isSuccess()) {
LOG.debug("ConnectionFlowStep succeeded");
currentStep
.onSuccess(ConnectionFlow.this);
} else {
LOG.debug("ConnectionFlowStep failed",
future.cause());
fail(future.cause());
}
}
};
});
}
/**
* Called when the flow is complete and successful. Notifies the
* {@link ProxyToServerConnection} that we succeeded.
*/
void succeed() {
synchronized (connectLock) {
serverConnection.getLOG().debug(
"Connection flow completed successfully: {}", currentStep);
serverConnection.connectionSucceeded(!suppressInitialRequest);
notifyThreadsWaitingForConnection();
}
}
/**
* Called when the flow fails at some {@link ConnectionFlowStep}.
* Disconnects the {@link ProxyToServerConnection} and informs the
* {@link ClientToProxyConnection} that our connection failed.
*/
@SuppressWarnings("unchecked")
void fail(final Throwable cause) {
final ConnectionState lastStateBeforeFailure = serverConnection
.getCurrentState();
serverConnection.disconnect().addListener(
new GenericFutureListener() {
@Override
public void operationComplete(Future future)
throws Exception {
synchronized (connectLock) {
if (!clientConnection.serverConnectionFailed(
serverConnection,
lastStateBeforeFailure,
cause)) {
// the connection to the server failed and we are not retrying, so transition to the
// DISCONNECTED state
serverConnection.become(ConnectionState.DISCONNECTED);
// We are not retrying our connection, let anyone waiting for a connection know that we're done
notifyThreadsWaitingForConnection();
}
}
}
});
}
/**
* Like {@link #fail(Throwable)} but with no cause.
*/
void fail() {
fail(null);
}
/**
* Once we've finished recording our connection and written our initial
* request, we can notify anyone who is waiting on the connection that it's
* okay to proceed.
*/
private void notifyThreadsWaitingForConnection() {
connectLock.notifyAll();
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl;
import io.netty.util.concurrent.Future;
/**
* Represents a phase in a {@link ConnectionFlow}.
*/
abstract class ConnectionFlowStep {
private final ProxyConnectionLogger LOG;
private final ProxyConnection connection;
private final ConnectionState state;
/**
* Construct a new step in a connection flow.
*
* @param connection
* the connection that we're working on
* @param state
* the state that the connection will show while we're processing
* this step
*/
ConnectionFlowStep(ProxyConnection connection,
ConnectionState state) {
super();
this.connection = connection;
this.state = state;
this.LOG = connection.getLOG();
}
ProxyConnection getConnection() {
return connection;
}
ConnectionState getState() {
return state;
}
/**
* Indicates whether or not to suppress the initial request. Defaults to
* false, can be overridden.
*
* @return
*/
boolean shouldSuppressInitialRequest() {
return false;
}
/**
* <p>
* Indicates whether or not this step should be executed on the channel's
* event loop. Defaults to true, can be overridden.
* </p>
*
* <p>
* If this step modifies the pipeline, for example by adding/removing
* handlers, it's best to make it execute on the event loop.
* </p>
*
*
* @return
*/
boolean shouldExecuteOnEventLoop() {
return true;
}
/**
* Implement this method to actually do the work involved in this step of
* the flow.
*
* @return
*/
protected abstract Future execute();
/**
* When the flow determines that this step was successful, it calls into
* this method. The default implementation simply continues with the flow.
* Other implementations may choose to not continue and instead wait for a
* message or something like that.
*
* @param flow
*/
void onSuccess(ConnectionFlow flow) {
flow.advance();
}
/**
* <p>
* Any messages that are read from the underlying connection while we're at
* this step of the connection flow are passed to this method.
* </p>
*
* <p>
* The default implementation ignores the message and logs this, since we
* weren't really expecting a message here.
* </p>
*
* <p>
* Some {@link ConnectionFlowStep}s do need to read the messages, so they
* override this method as appropriate.
* </p>
*
* @param flow
* our {@link ConnectionFlow}
* @param msg
* the message read from the underlying connection
*/
void read(ConnectionFlow flow, Object msg) {
LOG.debug("Received message while in the middle of connecting: {}", msg);
}
@Override
public String toString() {
return state.toString();
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl;
enum ConnectionState {
/**
* Connection attempting to connect.
*/
CONNECTING(true),
/**
* In the middle of doing an SSL handshake.
*/
HANDSHAKING(true),
/**
* In the process of negotiating an HTTP CONNECT from the client.
*/
NEGOTIATING_CONNECT(true),
/**
* When forwarding a CONNECT to a chained proxy, we await the CONNECTION_OK
* message from the proxy.
*/
AWAITING_CONNECT_OK(true),
/**
* Connected but waiting for proxy authentication.
*/
AWAITING_PROXY_AUTHENTICATION,
/**
* Connected and awaiting initial message (e.g. HttpRequest or
* HttpResponse).
*/
AWAITING_INITIAL,
/**
* Connected and awaiting HttpContent chunk.
*/
AWAITING_CHUNK,
/**
* We've asked the client to disconnect, but it hasn't yet.
*/
DISCONNECT_REQUESTED(),
/**
* Disconnected
*/
DISCONNECTED();
private final boolean partOfConnectionFlow;
ConnectionState(boolean partOfConnectionFlow) {
this.partOfConnectionFlow = partOfConnectionFlow;
}
ConnectionState() {
this(false);
}
/**
* Indicates whether this ConnectionState corresponds to a step in a
* {@link ConnectionFlow}. This is useful to distinguish so that we know
* whether or not we're in the process of establishing a connection.
*
* @return true if part of connection flow, otherwise false
*/
public boolean isPartOfConnectionFlow() {
return partOfConnectionFlow;
}
/**
* Indicates whether this ConnectionState is no longer waiting for messages and is either in the process of disconnecting
* or is already disconnected.
*
* @return true if the connection state is {@link #DISCONNECT_REQUESTED} or {@link #DISCONNECTED}, otherwise false
*/
public boolean isDisconnectingOrDisconnected() {
return this == DISCONNECT_REQUESTED || this == DISCONNECTED;
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
/**
* @deprecated This class is no longer used by LittleProxy and may be removed in a future release.
*/
@Deprecated
public class NetworkUtils {
/**
* @deprecated This method is no longer used by LittleProxy and may be removed in a future release.
*/
@Deprecated
public static InetAddress getLocalHost() throws UnknownHostException {
return InetAddress.getLocalHost();
}
/**
* @deprecated This method is no longer used by LittleProxy and may be removed in a future release.
*/
@Deprecated
public static InetAddress firstLocalNonLoopbackIpv4Address() {
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface
.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces
.nextElement();
if (networkInterface.isUp()) {
for (InterfaceAddress ifAddress : networkInterface
.getInterfaceAddresses()) {
if (ifAddress.getNetworkPrefixLength() > 0
&& ifAddress.getNetworkPrefixLength() <= 32
&& !ifAddress.getAddress().isLoopbackAddress()) {
return ifAddress.getAddress();
}
}
}
}
return null;
} catch (SocketException se) {
return null;
}
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.helpers.MessageFormatter;
import org.slf4j.spi.LocationAwareLogger;
import java.util.Arrays;
/**
* <p>
* A helper class that logs messages for ProxyConnections. All it does is make
* sure that the Channel and current state are always included in the log
* messages (if available).
* </p>
*
* <p>
* Note that this depends on us using a LocationAwareLogger so that we can
* report the line numbers of the caller rather than this helper class.
* If the SLF4J binding does not provide a LocationAwareLogger, then a fallback
* to Logger is provided.
* </p>
*/
class ProxyConnectionLogger {
private final ProxyConnection connection;
private final LogDispatch dispatch;
private final Logger logger;
private final String fqcn = this.getClass().getCanonicalName();
public ProxyConnectionLogger(ProxyConnection connection) {
this.connection = connection;
final Logger lg = LoggerFactory.getLogger(connection
.getClass());
if (lg instanceof LocationAwareLogger) {
dispatch = new LocationAwareLogggerDispatch((LocationAwareLogger) lg);
}
else {
dispatch = new LoggerDispatch();
}
logger = lg;
}
protected void error(String message, Object... params) {
if (logger.isErrorEnabled()) {
dispatch.doLog(LocationAwareLogger.ERROR_INT, message, params, null);
}
}
protected void error(String message, Throwable t) {
if (logger.isErrorEnabled()) {
dispatch.doLog(LocationAwareLogger.ERROR_INT, message, null, t);
}
}
protected void warn(String message, Object... params) {
if (logger.isWarnEnabled()) {
dispatch.doLog(LocationAwareLogger.WARN_INT, message, params, null);
}
}
protected void warn(String message, Throwable t) {
if (logger.isWarnEnabled()) {
dispatch.doLog(LocationAwareLogger.WARN_INT, message, null, t);
}
}
protected void info(String message, Object... params) {
if (logger.isInfoEnabled()) {
dispatch.doLog(LocationAwareLogger.INFO_INT, message, params, null);
}
}
protected void info(String message, Throwable t) {
if (logger.isInfoEnabled()) {
dispatch.doLog(LocationAwareLogger.INFO_INT, message, null, t);
}
}
protected void debug(String message, Object... params) {
if (logger.isDebugEnabled()) {
dispatch.doLog(LocationAwareLogger.DEBUG_INT, message, params, null);
}
}
protected void debug(String message, Throwable t) {
if (logger.isDebugEnabled()) {
dispatch.doLog(LocationAwareLogger.DEBUG_INT, message, null, t);
}
}
protected void log(int level, String message, Object... params) {
if (level != LocationAwareLogger.DEBUG_INT || logger.isDebugEnabled()) {
dispatch.doLog(level, message, params, null);
}
}
protected void log(int level, String message, Throwable t) {
if (level != LocationAwareLogger.DEBUG_INT || logger.isDebugEnabled()) {
dispatch.doLog(level, message, null, t);
}
}
private interface LogDispatch {
void doLog(int level, String message, Object[] params, Throwable t);
}
private String fullMessage(String message) {
String stateMessage = connection.getCurrentState().toString();
if (connection.isTunneling()) {
stateMessage += " {tunneling}";
}
String messagePrefix = "(" + stateMessage + ")";
if (connection.channel != null) {
messagePrefix = messagePrefix + " " + connection.channel;
}
return messagePrefix + ": " + message;
}
/**
* Fallback dispatch if a LocationAwareLogger is not available from
* the SLF4J LoggerFactory.
*/
private class LoggerDispatch implements LogDispatch {
@Override
public void doLog(int level, String message, Object[] params, Throwable t) {
String formattedMessage = fullMessage(message);
final Object[] paramsWithThrowable;
if (t != null) {
if (params == null) {
paramsWithThrowable = new Object[1];
paramsWithThrowable[0] = t;
} else {
paramsWithThrowable = Arrays.copyOf(params, params.length + 1);
paramsWithThrowable[params.length] = t;
}
}
else {
paramsWithThrowable = params;
}
switch (level) {
case LocationAwareLogger.TRACE_INT:
logger.trace(formattedMessage, paramsWithThrowable);
break;
case LocationAwareLogger.DEBUG_INT:
logger.debug(formattedMessage, paramsWithThrowable);
break;
case LocationAwareLogger.INFO_INT:
logger.info(formattedMessage, paramsWithThrowable);
break;
case LocationAwareLogger.WARN_INT:
logger.warn(formattedMessage, paramsWithThrowable);
break;
case LocationAwareLogger.ERROR_INT:
default:
logger.error(formattedMessage, paramsWithThrowable);
break;
}
}
}
/**
* Dispatcher for a LocationAwareLogger.
*/
private class LocationAwareLogggerDispatch implements LogDispatch {
private LocationAwareLogger log;
public LocationAwareLogggerDispatch(LocationAwareLogger log) {
this.log = log;
}
@Override
public void doLog(int level, String message, Object[] params, Throwable t) {
String formattedMessage = fullMessage(message);
if (params != null && params.length > 0) {
formattedMessage = MessageFormatter.arrayFormat(formattedMessage,
params).getMessage();
}
log.log(null, fqcn, level, formattedMessage, null, t);
}
}
}
\ No newline at end of file
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl;
import com.google.common.collect.ImmutableList;
import java.nio.channels.spi.SelectorProvider;
import java.util.List;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
/**
* Encapsulates the thread pools used by the proxy. Contains the acceptor thread pool as well as the client-to-proxy and
* proxy-to-server thread pools.
*/
public class ProxyThreadPools {
/**
* These {@link EventLoopGroup}s accept incoming connections to the
* proxies. A different EventLoopGroup is used for each
* TransportProtocol, since these have to be configured differently.
*/
private final NioEventLoopGroup clientToProxyAcceptorPool;
/**
* These {@link EventLoopGroup}s process incoming requests to the
* proxies. A different EventLoopGroup is used for each
* TransportProtocol, since these have to be configured differently.
*/
private final NioEventLoopGroup clientToProxyWorkerPool;
/**
* These {@link EventLoopGroup}s are used for making outgoing
* connections to servers. A different EventLoopGroup is used for each
* TransportProtocol, since these have to be configured differently.
*/
private final NioEventLoopGroup proxyToServerWorkerPool;
public ProxyThreadPools(SelectorProvider selectorProvider, int incomingAcceptorThreads, int incomingWorkerThreads, int outgoingWorkerThreads, String serverGroupName, int serverGroupId) {
clientToProxyAcceptorPool = new NioEventLoopGroup(incomingAcceptorThreads, new CategorizedThreadFactory(serverGroupName, "ClientToProxyAcceptor", serverGroupId), selectorProvider);
clientToProxyWorkerPool = new NioEventLoopGroup(incomingWorkerThreads, new CategorizedThreadFactory(serverGroupName, "ClientToProxyWorker", serverGroupId), selectorProvider);
clientToProxyWorkerPool.setIoRatio(90);
proxyToServerWorkerPool = new NioEventLoopGroup(outgoingWorkerThreads, new CategorizedThreadFactory(serverGroupName, "ProxyToServerWorker", serverGroupId), selectorProvider);
proxyToServerWorkerPool.setIoRatio(90);
}
/**
* Returns all event loops (acceptor and worker thread pools) in this pool.
*/
public List<EventLoopGroup> getAllEventLoops() {
return ImmutableList.<EventLoopGroup>of(clientToProxyAcceptorPool, clientToProxyWorkerPool, proxyToServerWorkerPool);
}
public NioEventLoopGroup getClientToProxyAcceptorPool() {
return clientToProxyAcceptorPool;
}
public NioEventLoopGroup getClientToProxyWorkerPool() {
return clientToProxyWorkerPool;
}
public NioEventLoopGroup getProxyToServerWorkerPool() {
return proxyToServerWorkerPool;
}
}
package org.littleshoot.proxy.src.main.java.org.littleshoot.proxy.impl;
/**
* Configuration object for the proxy's thread pools. Controls the number of acceptor and worker threads in the Netty
* {@link io.netty.channel.EventLoopGroup} used by the proxy.
*/
public class ThreadPoolConfiguration {
private int acceptorThreads = ServerGroup.DEFAULT_INCOMING_ACCEPTOR_THREADS;
private int clientToProxyWorkerThreads = ServerGroup.DEFAULT_INCOMING_WORKER_THREADS;
private int proxyToServerWorkerThreads = ServerGroup.DEFAULT_OUTGOING_WORKER_THREADS;
public int getClientToProxyWorkerThreads() {
return clientToProxyWorkerThreads;
}
/**
* Set the number of client-to-proxy worker threads to create. Worker threads perform the actual processing of
* client requests. The default value is {@link ServerGroup#DEFAULT_INCOMING_WORKER_THREADS}.
*
* @param clientToProxyWorkerThreads number of client-to-proxy worker threads to create
* @return this thread pool configuration instance, for chaining
*/
public ThreadPoolConfiguration withClientToProxyWorkerThreads(int clientToProxyWorkerThreads) {
this.clientToProxyWorkerThreads = clientToProxyWorkerThreads;
return this;
}
public int getAcceptorThreads() {
return acceptorThreads;
}
/**
* Set the number of acceptor threads to create. Acceptor threads accept HTTP connections from the client and queue
* them for processing by client-to-proxy worker threads. The default value is
* {@link ServerGroup#DEFAULT_INCOMING_ACCEPTOR_THREADS}.
*
* @param acceptorThreads number of acceptor threads to create
* @return this thread pool configuration instance, for chaining
*/
public ThreadPoolConfiguration withAcceptorThreads(int acceptorThreads) {
this.acceptorThreads = acceptorThreads;
return this;
}
public int getProxyToServerWorkerThreads() {
return proxyToServerWorkerThreads;
}
/**
* Set the number of proxy-to-server worker threads to create. Proxy-to-server worker threads make requests to
* upstream servers and process responses from the server. The default value is
* {@link ServerGroup#DEFAULT_OUTGOING_WORKER_THREADS}.
*
* @param proxyToServerWorkerThreads number of proxy-to-server worker threads to create
* @return this thread pool configuration instance, for chaining
*/
public ThreadPoolConfiguration withProxyToServerWorkerThreads(int proxyToServerWorkerThreads) {
this.proxyToServerWorkerThreads = proxyToServerWorkerThreads;
return this;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment