| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.proxy; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.charset.StandardCharsets; |
| |
| import org.eclipse.jetty.client.HttpClient; |
| import org.eclipse.jetty.client.HttpProxy; |
| import org.eclipse.jetty.client.api.ContentResponse; |
| import org.eclipse.jetty.http.HttpMethod; |
| import org.eclipse.jetty.http.HttpStatus; |
| import org.eclipse.jetty.io.AbstractConnection; |
| import org.eclipse.jetty.io.Connection; |
| import org.eclipse.jetty.io.EndPoint; |
| import org.eclipse.jetty.server.AbstractConnectionFactory; |
| import org.eclipse.jetty.server.ConnectionFactory; |
| import org.eclipse.jetty.server.Connector; |
| import org.eclipse.jetty.server.Server; |
| import org.eclipse.jetty.server.ServerConnector; |
| import org.eclipse.jetty.servlet.ServletContextHandler; |
| import org.eclipse.jetty.toolchain.test.MavenTestingUtils; |
| import org.eclipse.jetty.toolchain.test.TestTracker; |
| import org.eclipse.jetty.util.BufferUtil; |
| import org.eclipse.jetty.util.Callback; |
| import org.eclipse.jetty.util.Utf8StringBuilder; |
| import org.eclipse.jetty.util.ssl.SslContextFactory; |
| import org.eclipse.jetty.util.thread.QueuedThreadPool; |
| import org.hamcrest.Matchers; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @RunWith(Parameterized.class) |
| public class ForwardProxyServerTest |
| { |
| @Parameterized.Parameters |
| public static Object[] parameters() |
| { |
| return new Object[]{null, newSslContextFactory()}; |
| } |
| |
| @Rule |
| public final TestTracker tracker = new TestTracker(); |
| private final SslContextFactory serverSslContextFactory; |
| private Server server; |
| private ServerConnector serverConnector; |
| private Server proxy; |
| private ServerConnector proxyConnector; |
| |
| public ForwardProxyServerTest(SslContextFactory serverSslContextFactory) |
| { |
| this.serverSslContextFactory = serverSslContextFactory; |
| } |
| |
| protected void startServer(ConnectionFactory connectionFactory) throws Exception |
| { |
| QueuedThreadPool serverThreads = new QueuedThreadPool(); |
| serverThreads.setName("server"); |
| server = new Server(serverThreads); |
| serverConnector = new ServerConnector(server, serverSslContextFactory, connectionFactory); |
| server.addConnector(serverConnector); |
| server.start(); |
| } |
| |
| protected void startProxy() throws Exception |
| { |
| QueuedThreadPool proxyThreads = new QueuedThreadPool(); |
| proxyThreads.setName("proxy"); |
| proxy = new Server(proxyThreads); |
| proxyConnector = new ServerConnector(proxy); |
| proxy.addConnector(proxyConnector); |
| // Under Windows, it takes a while to detect that a connection |
| // attempt fails, so use an explicit timeout |
| ConnectHandler connectHandler = new ConnectHandler(); |
| connectHandler.setConnectTimeout(1000); |
| proxy.setHandler(connectHandler); |
| |
| ServletContextHandler proxyHandler = new ServletContextHandler(connectHandler, "/"); |
| proxyHandler.addServlet(ProxyServlet.class, "/*"); |
| |
| proxy.start(); |
| } |
| |
| protected HttpProxy newHttpProxy() |
| { |
| return new HttpProxy("localhost", proxyConnector.getLocalPort()); |
| } |
| |
| private static SslContextFactory newSslContextFactory() |
| { |
| SslContextFactory sslContextFactory = new SslContextFactory(); |
| String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); |
| sslContextFactory.setKeyStorePath(keyStorePath); |
| sslContextFactory.setKeyStorePassword("storepwd"); |
| sslContextFactory.setKeyManagerPassword("keypwd"); |
| return sslContextFactory; |
| } |
| |
| @After |
| public void stop() throws Exception |
| { |
| stopProxy(); |
| stopServer(); |
| } |
| |
| protected void stopServer() throws Exception |
| { |
| if (server != null) |
| { |
| server.stop(); |
| server.join(); |
| } |
| } |
| |
| protected void stopProxy() throws Exception |
| { |
| if (proxy != null) |
| { |
| proxy.stop(); |
| proxy.join(); |
| } |
| } |
| |
| @Test |
| public void testRequestTarget() throws Exception |
| { |
| startServer(new AbstractConnectionFactory("http/1.1") |
| { |
| @Override |
| public Connection newConnection(Connector connector, EndPoint endPoint) |
| { |
| return new AbstractConnection(endPoint, connector.getExecutor()) |
| { |
| @Override |
| public void onOpen() |
| { |
| super.onOpen(); |
| fillInterested(); |
| } |
| |
| @Override |
| public void onFillable() |
| { |
| try |
| { |
| // When using TLS, multiple reads are required. |
| ByteBuffer buffer = BufferUtil.allocate(1024); |
| int filled = 0; |
| while (filled == 0) |
| filled = getEndPoint().fill(buffer); |
| Utf8StringBuilder builder = new Utf8StringBuilder(); |
| builder.append(buffer); |
| String request = builder.toString(); |
| |
| // ProxyServlet will receive an absolute URI from |
| // the client, and convert it to a relative URI. |
| // The ConnectHandler won't modify what the client |
| // sent, which must be a relative URI. |
| Assert.assertThat(request.length(), Matchers.greaterThan(0)); |
| if (serverSslContextFactory == null) |
| Assert.assertFalse(request.contains("http://")); |
| else |
| Assert.assertFalse(request.contains("https://")); |
| |
| String response = "" + |
| "HTTP/1.1 200 OK\r\n" + |
| "Content-Length: 0\r\n" + |
| "\r\n"; |
| getEndPoint().write(Callback.NOOP, ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8))); |
| } |
| catch (Throwable x) |
| { |
| x.printStackTrace(); |
| close(); |
| } |
| } |
| }; |
| } |
| }); |
| startProxy(); |
| |
| HttpClient httpClient = new HttpClient(newSslContextFactory()); |
| httpClient.getProxyConfiguration().getProxies().add(newHttpProxy()); |
| httpClient.start(); |
| |
| try |
| { |
| ContentResponse response = httpClient.newRequest("localhost", serverConnector.getLocalPort()) |
| .scheme(serverSslContextFactory == null ? "http" : "https") |
| .method(HttpMethod.GET) |
| .path("/test") |
| .send(); |
| |
| Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); |
| } |
| finally |
| { |
| httpClient.stop(); |
| } |
| } |
| } |