Cleaned up test case and implementation for #360665 (Proxying HTTPS request to HTTP port causes exception loop).

Class SelectConnector.ProxySelectChannelEndPoint did not follow the latest changes to interface EndPoint, and was
returning wrong values for isInputShutdown() and other few methods.
Also suppressed expected exceptions in the test case.
This commit is contained in:
Simone Bordet 2011-11-04 15:31:00 +01:00
parent 9aee1affe5
commit 0a40c3d750
3 changed files with 303 additions and 149 deletions

View File

@ -15,7 +15,7 @@ package org.eclipse.jetty.client;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.ConnectException;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@ -692,12 +692,13 @@ public class HttpDestination implements Dumpable
{
proxyEndPoint.upgrade();
}
else if(responseStatus == HttpStatus.GATEWAY_TIMEOUT_504){
else if(responseStatus == HttpStatus.GATEWAY_TIMEOUT_504)
{
onExpire();
}
else
{
onException(new ConnectException("Proxy: " + proxyEndPoint.getRemoteAddr() +":" + proxyEndPoint.getRemotePort() + " didn't return http return code 200, but " + responseStatus + " while trying to request: " + exchange.getAddress().toString()));
onException(new ProtocolException("Proxy: " + proxyEndPoint.getRemoteAddr() +":" + proxyEndPoint.getRemotePort() + " didn't return http return code 200, but " + responseStatus + " while trying to request: " + exchange.getAddress().toString()));
}
}

View File

@ -15,14 +15,11 @@ package org.eclipse.jetty.client;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.channels.UnresolvedAddressException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
@ -107,7 +104,7 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector
else
{
channel.configureBlocking(false);
channel.connect(address.toSocketAddress());
channel.connect(address.toSocketAddress());
_selectorManager.register(channel,destination);
ConnectTimeout connectTimeout = new ConnectTimeout(channel,destination);
_httpClient.schedule(connectTimeout,_httpClient.getConnectTimeout());
@ -232,7 +229,7 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector
Timeout.Task connectTimeout = _connectingChannels.remove(channel);
if (connectTimeout != null)
connectTimeout.cancel();
if (attachment instanceof HttpDestination)
((HttpDestination)attachment).onConnectionFailed(ex);
else
@ -280,12 +277,14 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector
public static class ProxySelectChannelEndPoint extends SslSelectChannelEndPoint
{
private final SelectChannelEndPoint plainEndPoint;
private final EnforceOverrideEndPointMethods enforcer;
private volatile boolean upgraded = false;
public ProxySelectChannelEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, Buffers sslBuffers, SSLEngine engine, int maxIdleTimeout) throws IOException
{
super(sslBuffers, channel, selectSet, key, engine, maxIdleTimeout);
this.plainEndPoint = new SelectChannelEndPoint(channel, selectSet, key, maxIdleTimeout);
this.enforcer = new EnforceOverrideEndPointMethods();
}
public void upgrade()
@ -295,179 +294,338 @@ class SelectConnector extends AbstractLifeCycle implements HttpClient.Connector
public void shutdownOutput() throws IOException
{
if (upgraded)
super.shutdownOutput();
else
plainEndPoint.shutdownOutput();
enforcer.shutdownOutput();
}
public boolean isOutputShutdown()
{
return enforcer.isOutputShutdown();
}
public void shutdownInput() throws IOException
{
enforcer.shutdownInput();
}
public boolean isInputShutdown()
{
return enforcer.isInputShutdown();
}
public void close() throws IOException
{
if (upgraded)
super.close();
else
plainEndPoint.close();
enforcer.close();
}
public int fill(Buffer buffer) throws IOException
{
if (upgraded)
return super.fill(buffer);
else
return plainEndPoint.fill(buffer);
return enforcer.fill(buffer);
}
public int flush(Buffer buffer) throws IOException
{
if (upgraded)
return super.flush(buffer);
else
return plainEndPoint.flush(buffer);
return enforcer.flush(buffer);
}
public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
{
if (upgraded)
return super.flush(header, buffer, trailer);
else
return plainEndPoint.flush(header, buffer, trailer);
return enforcer.flush(header, buffer, trailer);
}
public String getLocalAddr()
{
if (upgraded)
return super.getLocalAddr();
else
return plainEndPoint.getLocalAddr();
return enforcer.getLocalAddr();
}
public String getLocalHost()
{
if (upgraded)
return super.getLocalHost();
else
return plainEndPoint.getLocalHost();
return enforcer.getLocalHost();
}
public int getLocalPort()
{
if (upgraded)
return super.getLocalPort();
else
return plainEndPoint.getLocalPort();
return enforcer.getLocalPort();
}
public String getRemoteAddr()
{
if (upgraded)
return super.getRemoteAddr();
else
return plainEndPoint.getRemoteAddr();
return enforcer.getRemoteAddr();
}
public String getRemoteHost()
{
if (upgraded)
return super.getRemoteHost();
else
return plainEndPoint.getRemoteHost();
return enforcer.getRemoteHost();
}
public int getRemotePort()
{
if (upgraded)
return super.getRemotePort();
else
return plainEndPoint.getRemotePort();
return enforcer.getRemotePort();
}
public boolean isBlocking()
{
if (upgraded)
return super.isBlocking();
else
return plainEndPoint.isBlocking();
return enforcer.isBlocking();
}
public boolean isBufferred()
{
if (upgraded)
return super.isBufferred();
else
return plainEndPoint.isBufferred();
return enforcer.isBufferred();
}
public boolean blockReadable(long millisecs) throws IOException
{
if (upgraded)
return super.blockReadable(millisecs);
else
return plainEndPoint.blockReadable(millisecs);
return enforcer.blockReadable(millisecs);
}
public boolean blockWritable(long millisecs) throws IOException
{
if (upgraded)
return super.blockWritable(millisecs);
else
return plainEndPoint.blockWritable(millisecs);
return enforcer.blockWritable(millisecs);
}
public boolean isOpen()
{
if (upgraded)
return super.isOpen();
else
return plainEndPoint.isOpen();
return enforcer.isOpen();
}
public Object getTransport()
{
if (upgraded)
return super.getTransport();
else
return plainEndPoint.getTransport();
return enforcer.getTransport();
}
public boolean isBufferingInput()
{
if (upgraded)
return super.isBufferingInput();
else
return plainEndPoint.isBufferingInput();
return enforcer.isBufferingInput();
}
public boolean isBufferingOutput()
{
if (upgraded)
return super.isBufferingOutput();
else
return plainEndPoint.isBufferingOutput();
return enforcer.isBufferingOutput();
}
public void flush() throws IOException
{
if (upgraded)
super.flush();
else
plainEndPoint.flush();
enforcer.flush();
}
public int getMaxIdleTime()
{
if (upgraded)
return super.getMaxIdleTime();
else
return plainEndPoint.getMaxIdleTime();
return enforcer.getMaxIdleTime();
}
public void setMaxIdleTime(int timeMs) throws IOException
{
if (upgraded)
super.setMaxIdleTime(timeMs);
else
plainEndPoint.setMaxIdleTime(timeMs);
enforcer.setMaxIdleTime(timeMs);
}
/**
* The only reason this class exist is to enforce that
* {@link ProxySelectChannelEndPoint} overrides all methods of {@link EndPoint}.
* Therefore, if a method is added to {@link EndPoint}, this class
* won't compile anymore, will need an implementation, and one must remember
* to override the correspondent method in {@link ProxySelectChannelEndPoint}.
*/
private class EnforceOverrideEndPointMethods implements EndPoint
{
public void shutdownOutput() throws IOException
{
if (upgraded)
ProxySelectChannelEndPoint.super.shutdownOutput();
else
plainEndPoint.shutdownOutput();
}
public boolean isOutputShutdown()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.isOutputShutdown();
else
return plainEndPoint.isOutputShutdown();
}
public void shutdownInput() throws IOException
{
if (upgraded)
ProxySelectChannelEndPoint.super.shutdownInput();
else
plainEndPoint.shutdownInput();
}
public boolean isInputShutdown()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.isInputShutdown();
else
return plainEndPoint.isInputShutdown();
}
public void close() throws IOException
{
if (upgraded)
ProxySelectChannelEndPoint.super.close();
else
plainEndPoint.close();
}
public int fill(Buffer buffer) throws IOException
{
if (upgraded)
return ProxySelectChannelEndPoint.super.fill(buffer);
else
return plainEndPoint.fill(buffer);
}
public int flush(Buffer buffer) throws IOException
{
if (upgraded)
return ProxySelectChannelEndPoint.super.flush(buffer);
else
return plainEndPoint.flush(buffer);
}
public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
{
if (upgraded)
return ProxySelectChannelEndPoint.super.flush(header, buffer, trailer);
else
return plainEndPoint.flush(header, buffer, trailer);
}
public String getLocalAddr()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.getLocalAddr();
else
return plainEndPoint.getLocalAddr();
}
public String getLocalHost()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.getLocalHost();
else
return plainEndPoint.getLocalHost();
}
public int getLocalPort()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.getLocalPort();
else
return plainEndPoint.getLocalPort();
}
public String getRemoteAddr()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.getRemoteAddr();
else
return plainEndPoint.getRemoteAddr();
}
public String getRemoteHost()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.getRemoteHost();
else
return plainEndPoint.getRemoteHost();
}
public int getRemotePort()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.getRemotePort();
else
return plainEndPoint.getRemotePort();
}
public boolean isBlocking()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.isBlocking();
else
return plainEndPoint.isBlocking();
}
public boolean isBufferred()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.isBufferred();
else
return plainEndPoint.isBufferred();
}
public boolean blockReadable(long millisecs) throws IOException
{
if (upgraded)
return ProxySelectChannelEndPoint.super.blockReadable(millisecs);
else
return plainEndPoint.blockReadable(millisecs);
}
public boolean blockWritable(long millisecs) throws IOException
{
if (upgraded)
return ProxySelectChannelEndPoint.super.blockWritable(millisecs);
else
return plainEndPoint.blockWritable(millisecs);
}
public boolean isOpen()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.isOpen();
else
return plainEndPoint.isOpen();
}
public Object getTransport()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.getTransport();
else
return plainEndPoint.getTransport();
}
public boolean isBufferingInput()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.isBufferingInput();
else
return plainEndPoint.isBufferingInput();
}
public boolean isBufferingOutput()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.isBufferingOutput();
else
return plainEndPoint.isBufferingOutput();
}
public void flush() throws IOException
{
if (upgraded)
ProxySelectChannelEndPoint.super.flush();
else
plainEndPoint.flush();
}
public int getMaxIdleTime()
{
if (upgraded)
return ProxySelectChannelEndPoint.super.getMaxIdleTime();
else
return plainEndPoint.getMaxIdleTime();
}
public void setMaxIdleTime(int timeMs) throws IOException
{
if (upgraded)
ProxySelectChannelEndPoint.super.setMaxIdleTime(timeMs);
else
plainEndPoint.setMaxIdleTime(timeMs);
}
}
}
}

View File

@ -13,10 +13,8 @@
package org.eclipse.jetty.client;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.net.ProtocolException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -31,27 +29,24 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/* ------------------------------------------------------------ */
/**
* This UnitTest class executes two tests. Both will send a http request to https://google.com through a misbehaving proxy server.
*
* <p/>
* The first test runs against a proxy which simply closes the connection (as nginx does) for a connect request. The second proxy server always responds with a
* 500 error.
*
* <p/>
* The expected result for both tests is an exception and the HttpExchange should have status HttpExchange.STATUS_EXCEPTED.
*/
public class HttpsViaBrokenHttpProxyTest
{
private Server _proxy = new Server();
private HttpClient _client = new HttpClient();
/* ------------------------------------------------------------ */
/**
* @throws java.lang.Exception
*/
@Before
public void setUpBeforeClass() throws Exception
public void init() throws Exception
{
// setup proxies with different behaviour
_proxy.addConnector(new SelectChannelConnector());
@ -59,21 +54,54 @@ public class HttpsViaBrokenHttpProxyTest
_proxy.start();
int proxyClosingConnectionPort = _proxy.getConnectors()[0].getLocalPort();
_client.setProxy(new Address("localhost",proxyClosingConnectionPort));
_client.setProxy(new Address("localhost", proxyClosingConnectionPort));
_client.start();
}
/* ------------------------------------------------------------ */
/**
* @throws java.lang.Exception
*/
@After
public void tearDownAfterClass() throws Exception
public void destroy() throws Exception
{
_client.stop();
_proxy.stop();
}
@Test
public void httpsViaProxyThatClosesConnectionOnConnectRequestTest() throws Exception
{
sendRequestThroughProxy(new ContentExchange(), "close", 9);
}
@Test
public void httpsViaProxyThatReturns500ErrorTest() throws Exception
{
HttpExchange exchange = new ContentExchange()
{
@Override
protected void onException(Throwable x)
{
// Suppress logging for expected exception
if (!(x instanceof ProtocolException))
super.onException(x);
}
};
sendRequestThroughProxy(exchange, "error500", 9);
}
@Test
public void httpsViaProxyThatReturns504ErrorTest() throws Exception
{
sendRequestThroughProxy(new ContentExchange(), "error504", 8);
}
private void sendRequestThroughProxy(HttpExchange exchange, String desiredBehaviour, int exptectedStatus) throws Exception
{
String url = "https://" + desiredBehaviour + ".com/";
exchange.setURL(url);
exchange.addRequestHeader("behaviour", desiredBehaviour);
_client.send(exchange);
assertEquals(HttpExchange.toState(exptectedStatus) + " status awaited", exptectedStatus, exchange.waitForDone());
}
private class BadBehavingConnectHandler extends ConnectHandler
{
@Override
@ -81,7 +109,9 @@ public class HttpsViaBrokenHttpProxyTest
throws ServletException, IOException
{
if (serverAddress.contains("close"))
{
HttpConnection.getCurrentConnection().getEndPoint().close();
}
else if (serverAddress.contains("error500"))
{
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
@ -93,39 +123,4 @@ public class HttpsViaBrokenHttpProxyTest
baseRequest.setHandled(true);
}
}
@Test
public void httpsViaProxyThatClosesConnectionOnConnectRequestTest()
{
sendRequestThroughProxy("close",9);
}
@Test
public void httpsViaProxyThatReturns500ErrorTest() throws Exception
{
sendRequestThroughProxy("error500",9);
}
@Test
public void httpsViaProxyThatReturns504ErrorTest() throws Exception
{
sendRequestThroughProxy("error504",8);
}
private void sendRequestThroughProxy(String desiredBehaviour, int exptectedStatus)
{
String url = "https://" + desiredBehaviour + ".com/";
try
{
ContentExchange exchange = new ContentExchange();
exchange.setURL(url);
exchange.addRequestHeader("behaviour",desiredBehaviour);
_client.send(exchange);
assertEquals(HttpExchange.toState(exptectedStatus) + " status awaited",exptectedStatus,exchange.waitForDone());
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
}
}