Made handling of refused tunnel exception consistent between the classic and async protocol handler implementations; Proxy response body no longer gets buffered in memory in order to avoid excessive resource allocation in case of a proxy misbehavior

This commit is contained in:
Oleg Kalnichevski 2024-04-25 10:51:56 +02:00
parent 5eb15a1be4
commit a0a38afbee
4 changed files with 9 additions and 15 deletions

View File

@ -122,6 +122,7 @@ public final class AsyncConnectExec implements AsyncExecChainHandler {
volatile boolean challenged; volatile boolean challenged;
volatile boolean tunnelRefused; volatile boolean tunnelRefused;
volatile HttpResponse tunnelResponse;
} }
@ -294,7 +295,7 @@ public final class AsyncConnectExec implements AsyncExecChainHandler {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("{} tunnel refused", exchangeId); LOG.debug("{} tunnel refused", exchangeId);
} }
asyncExecCallback.failed(new TunnelRefusedException("Tunnel refused", null)); asyncExecCallback.failed(new TunnelRefusedException("CONNECT refused by proxy: " + new StatusLine(state.tunnelResponse), null));
} else { } else {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("{} tunnel to target created", exchangeId); LOG.debug("{} tunnel to target created", exchangeId);
@ -455,7 +456,8 @@ public final class AsyncConnectExec implements AsyncExecChainHandler {
state.challenged = false; state.challenged = false;
if (status >= HttpStatus.SC_REDIRECTION) { if (status >= HttpStatus.SC_REDIRECTION) {
state.tunnelRefused = true; state.tunnelRefused = true;
entityConsumerRef.set(asyncExecCallback.handleResponse(response, entityDetails)); state.tunnelResponse = response;
entityConsumerRef.set(null);
} else if (status == HttpStatus.SC_OK) { } else if (status == HttpStatus.SC_OK) {
asyncExecCallback.completed(); asyncExecCallback.completed();
} else { } else {

View File

@ -282,12 +282,9 @@ public final class ConnectExec implements ExecChainHandler {
final int status = response.getCode(); final int status = response.getCode();
if (status != HttpStatus.SC_OK) { if (status != HttpStatus.SC_OK) {
EntityUtils.consume(response.getEntity());
// Buffer response content
final HttpEntity entity = response.getEntity();
final String responseMessage = entity != null ? EntityUtils.toString(entity) : null;
execRuntime.disconnectEndpoint(); execRuntime.disconnectEndpoint();
throw new TunnelRefusedException("CONNECT refused by proxy: " + new StatusLine(response), responseMessage); throw new TunnelRefusedException("CONNECT refused by proxy: " + new StatusLine(response), null);
} }
// How to decide on security of the tunnelled connection? // How to decide on security of the tunnelled connection?

View File

@ -200,12 +200,9 @@ public class ProxyClient {
final int status = response.getCode(); final int status = response.getCode();
if (status > 299) { if (status > 299) {
EntityUtils.consume(response.getEntity());
// Buffer response content
final HttpEntity entity = response.getEntity();
final String responseMessage = entity != null ? EntityUtils.toString(entity) : null;
conn.close(); conn.close();
throw new TunnelRefusedException("CONNECT refused by proxy: " + new StatusLine(response), responseMessage); throw new TunnelRefusedException("CONNECT refused by proxy: " + new StatusLine(response), null);
} }
return conn.getSocket(); return conn.getSocket();
} }

View File

@ -218,9 +218,7 @@ public class TestConnectExec {
Mockito.any())).thenReturn(response); Mockito.any())).thenReturn(response);
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context); final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
final TunnelRefusedException exception = Assertions.assertThrows(TunnelRefusedException.class, () -> Assertions.assertThrows(TunnelRefusedException.class, () -> exec.execute(request, scope, execChain));
exec.execute(request, scope, execChain));
Assertions.assertEquals("Ka-boom", exception.getResponseMessage());
Mockito.verify(execRuntime, Mockito.atLeastOnce()).disconnectEndpoint(); Mockito.verify(execRuntime, Mockito.atLeastOnce()).disconnectEndpoint();
Mockito.verify(execRuntime).discardEndpoint(); Mockito.verify(execRuntime).discardEndpoint();
} }