Force use new ProxyToServer connection after set new upstream proxy.
in my back-end app, i need to change the upstream proxy dynamically due to different chrome driver request task. but when i set new upstream proxy, new request still use some old proxy, more specifically, some existed ProxyToServerConnection is reused, which may lead some curious consequence.
in ClientToProxyConnection.java (little-proxy project), i compared currentServerConnection.chainedProxy.getChainedProxyAddress() and the ChainedProxy.getChainedProxyAddress() returned by chainedProxyManager, and close server connection if detect proxy changed as the following code:
if(currentServerConnection != null){
Queue<ChainedProxy> chainedProxies = new ConcurrentLinkedQueue<ChainedProxy>();
ChainedProxyManager chainedProxyManager = proxyServer
.getChainProxyManager();
if (chainedProxyManager != null) {
chainedProxyManager.lookupChainedProxies(httpRequest,
chainedProxies);
if (chainedProxies.size() == 0) {
// ChainedProxyManager returned no proxies, can't connect
return null;
}
}
ChainedProxy cp = chainedProxies.peek();
if(cp.getChainedProxyAddress() != currentServerConnection.chainedProxy.getChainedProxyAddress()) {
this.disconnect();
}
}
and comment the code related to serverConnectionsByHostAndPort, then new request will use new set proxy with no errors.
the above is a little hack and not good for maintenance, is there any official change proxy implementations or better ways to archive my use case, please give some hit, thanks.
I've been struggling with this lately, as I need a way to quickly switch upstream proxies during testing. If the upstream host and port doesn't change, browsermob won't switch to the new proxy.
To fix this, I created a drop in RequestFilter. It absolutely abuses reflection, but it works for now. This appears to be an easy fix, and I hope to see it baked in soon.
`package proxy;
import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Supplier;
import org.littleshoot.proxy.ChainedProxy; import org.littleshoot.proxy.ChainedProxyManager; import org.littleshoot.proxy.impl.ClientToProxyConnection; import org.littleshoot.proxy.impl.ProxyToServerConnection;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import net.lightbody.bmp.filters.RequestFilter; import net.lightbody.bmp.util.HttpMessageContents; import net.lightbody.bmp.util.HttpMessageInfo;
public class ProxyDisconnectRequestFilter implements RequestFilter {
public static ProxyDisconnectRequestFilter createWithChainedProxyManager(ChainedProxyManager chainedProxyManager) {
Function<HttpRequest, InetSocketAddress> currentChainedProxySupplier;
if (chainedProxyManager == null)
currentChainedProxySupplier = null;
else
currentChainedProxySupplier = request -> {
if (request == null)
return null;
Queue<ChainedProxy> chainedProxies = new ConcurrentLinkedQueue<ChainedProxy>();
chainedProxyManager.lookupChainedProxies(request, chainedProxies);
ChainedProxy cp = chainedProxies.peek();
return cp == null ? null : cp.getChainedProxyAddress();
};
return new ProxyDisconnectRequestFilter(currentChainedProxySupplier);
}
public static ProxyDisconnectRequestFilter createWithChainedProxyAddressSupplier(
Supplier<InetSocketAddress> chainedProxyAddressSupplier) {
Function<HttpRequest, InetSocketAddress> currentChainedProxySupplier;
if (chainedProxyAddressSupplier == null)
currentChainedProxySupplier = null;
else
currentChainedProxySupplier = nil -> chainedProxyAddressSupplier.get();
return new ProxyDisconnectRequestFilter(currentChainedProxySupplier);
}
private static final AtomicReference<Field> PROXY_TO_SERVER_CONNECTION_FIELD_REF = new AtomicReference<>();
private static final AtomicReference<Method> DISCONNECT_METHOD_REF = new AtomicReference<>();
private Function<HttpRequest, InetSocketAddress> requestToChainedProxyAddress;
public ProxyDisconnectRequestFilter(Function<HttpRequest, InetSocketAddress> requestToChainedProxyAddress) {
this.requestToChainedProxyAddress = Objects.requireNonNull(requestToChainedProxyAddress,
"requestToChainedProxyAddress required");
}
@Override
public HttpResponse filterRequest(HttpRequest request, HttpMessageContents contents, HttpMessageInfo messageInfo) {
ClientToProxyConnection clientToProxyConnection = getClientToProxyConnection(
messageInfo.getChannelHandlerContext());
ProxyToServerConnection proxyToServerConnection = getProxyToServerConnection(clientToProxyConnection);
InetSocketAddress currentChainedProxyAddress = proxyToServerConnection == null ? null
: proxyToServerConnection.getChainedProxyAddress();
if (currentChainedProxyAddress == null)
// we aren't connected to a proxy yet
return null;
InetSocketAddress requestChainedProxyAddress = requestToChainedProxyAddress.apply(request);
if (Objects.equals(requestChainedProxyAddress, currentChainedProxyAddress))
return null;
this.disconnect(proxyToServerConnection);
return null;
}
private ClientToProxyConnection getClientToProxyConnection(ChannelHandlerContext ctx) {
ChannelHandler handler = ctx == null ? null : ctx.handler();
if (handler == null || !(handler instanceof ClientToProxyConnection))
return null;
return (ClientToProxyConnection) handler;
}
private ProxyToServerConnection getProxyToServerConnection(ClientToProxyConnection clientToProxyConnection) {
if (clientToProxyConnection == null)
return null;
if (PROXY_TO_SERVER_CONNECTION_FIELD_REF.get() == null)
synchronized (PROXY_TO_SERVER_CONNECTION_FIELD_REF) {
if (PROXY_TO_SERVER_CONNECTION_FIELD_REF.get() == null) {
// hax
String fieldName = "currentServerConnection";
Field field;
try {
field = ClientToProxyConnection.class.getDeclaredField(fieldName);
} catch (NoSuchFieldException | SecurityException e) {
throw new java.lang.RuntimeException(e);
}
if (field != null && !ProxyToServerConnection.class.isAssignableFrom(field.getType()))
field = null;
Objects.requireNonNull(field, "unable to use hax to get field:" + fieldName);
field.setAccessible(true);
PROXY_TO_SERVER_CONNECTION_FIELD_REF.set(field);
}
}
try {
return (ProxyToServerConnection) PROXY_TO_SERVER_CONNECTION_FIELD_REF.get().get(clientToProxyConnection);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new java.lang.RuntimeException(e);
}
}
private Future<Void> disconnect(ClientToProxyConnection clientToProxyConnection) {
return this._disconnectProxyConnection(clientToProxyConnection);
}
private Future<Void> disconnect(ProxyToServerConnection clientToProxyConnection) {
return this._disconnectProxyConnection(clientToProxyConnection);
}
@SuppressWarnings("unchecked")
private Future<Void> _disconnectProxyConnection(Object object) {
if (object == null)
return null;
if (DISCONNECT_METHOD_REF.get() == null)
synchronized (DISCONNECT_METHOD_REF) {
if (DISCONNECT_METHOD_REF.get() == null) {
// hax
String methodName = "disconnect";
Method method;
try {
Class<?> proxyConnectionClassType = Class.forName("org.littleshoot.proxy.impl.ProxyConnection",
true, this.getClass().getClassLoader());
method = proxyConnectionClassType.getDeclaredMethod(methodName);
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
throw new java.lang.RuntimeException(e);
}
if (method != null
&& !io.netty.util.concurrent.Future.class.isAssignableFrom(method.getReturnType()))
method = null;
Objects.requireNonNull(method, "unable to use hax to get method:" + methodName);
method.setAccessible(true);
DISCONNECT_METHOD_REF.set(method);
}
}
try {
return (Future<Void>) DISCONNECT_METHOD_REF.get().invoke(object);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new java.lang.RuntimeException(e);
}
}
} `