| // |
| // ======================================================================== |
| // 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.http2.client; |
| |
| import java.io.IOException; |
| import java.net.InetSocketAddress; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.eclipse.jetty.client.api.Request; |
| import org.eclipse.jetty.http.HostPortHttpField; |
| import org.eclipse.jetty.http.HttpFields; |
| import org.eclipse.jetty.http.HttpScheme; |
| import org.eclipse.jetty.http.HttpVersion; |
| import org.eclipse.jetty.http.MetaData; |
| import org.eclipse.jetty.http2.api.Session; |
| import org.eclipse.jetty.http2.api.Stream; |
| import org.eclipse.jetty.http2.frames.DataFrame; |
| import org.eclipse.jetty.http2.frames.HeadersFrame; |
| import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; |
| import org.eclipse.jetty.proxy.AsyncProxyServlet; |
| import org.eclipse.jetty.server.HttpConfiguration; |
| import org.eclipse.jetty.server.Server; |
| import org.eclipse.jetty.server.ServerConnector; |
| import org.eclipse.jetty.servlet.ServletContextHandler; |
| import org.eclipse.jetty.servlet.ServletHolder; |
| import org.eclipse.jetty.toolchain.test.TestTracker; |
| import org.eclipse.jetty.util.Callback; |
| import org.eclipse.jetty.util.FuturePromise; |
| import org.eclipse.jetty.util.Promise; |
| import org.eclipse.jetty.util.thread.QueuedThreadPool; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Rule; |
| import org.junit.Test; |
| |
| public class ProxyTest |
| { |
| @Rule |
| public final TestTracker tracker = new TestTracker(); |
| private HTTP2Client client; |
| private Server proxy; |
| private ServerConnector proxyConnector; |
| private Server server; |
| private ServerConnector serverConnector; |
| |
| private void startServer(HttpServlet servlet) throws Exception |
| { |
| QueuedThreadPool serverPool = new QueuedThreadPool(); |
| serverPool.setName("server"); |
| server = new Server(serverPool); |
| serverConnector = new ServerConnector(server); |
| server.addConnector(serverConnector); |
| |
| ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false); |
| ServletHolder appServletHolder = new ServletHolder(servlet); |
| appCtx.addServlet(appServletHolder, "/*"); |
| |
| server.start(); |
| } |
| |
| private void startProxy(HttpServlet proxyServlet, Map<String, String> initParams) throws Exception |
| { |
| QueuedThreadPool proxyPool = new QueuedThreadPool(); |
| proxyPool.setName("proxy"); |
| proxy = new Server(proxyPool); |
| |
| HttpConfiguration configuration = new HttpConfiguration(); |
| configuration.setSendDateHeader(false); |
| configuration.setSendServerVersion(false); |
| String value = initParams.get("outputBufferSize"); |
| if (value != null) |
| configuration.setOutputBufferSize(Integer.valueOf(value)); |
| proxyConnector = new ServerConnector(proxy, new HTTP2ServerConnectionFactory(configuration)); |
| proxy.addConnector(proxyConnector); |
| |
| ServletContextHandler proxyContext = new ServletContextHandler(proxy, "/", true, false); |
| ServletHolder proxyServletHolder = new ServletHolder(proxyServlet); |
| proxyServletHolder.setInitParameters(initParams); |
| proxyContext.addServlet(proxyServletHolder, "/*"); |
| |
| proxy.start(); |
| } |
| |
| private void startClient() throws Exception |
| { |
| QueuedThreadPool clientExecutor = new QueuedThreadPool(); |
| clientExecutor.setName("client"); |
| client = new HTTP2Client(); |
| client.setExecutor(clientExecutor); |
| client.start(); |
| } |
| |
| private Session newClient(Session.Listener listener) throws Exception |
| { |
| String host = "localhost"; |
| int port = proxyConnector.getLocalPort(); |
| InetSocketAddress address = new InetSocketAddress(host, port); |
| FuturePromise<Session> promise = new FuturePromise<>(); |
| client.connect(address, listener, promise); |
| return promise.get(5, TimeUnit.SECONDS); |
| } |
| |
| private MetaData.Request newRequest(String method, String path, HttpFields fields) |
| { |
| String host = "localhost"; |
| int port = proxyConnector.getLocalPort(); |
| String authority = host + ":" + port; |
| return new MetaData.Request(method, HttpScheme.HTTP, new HostPortHttpField(authority), path, HttpVersion.HTTP_2, fields); |
| } |
| |
| @After |
| public void dispose() throws Exception |
| { |
| client.stop(); |
| proxy.stop(); |
| server.stop(); |
| } |
| |
| @Test |
| public void testServerBigDownloadSlowClient() throws Exception |
| { |
| final CountDownLatch serverLatch = new CountDownLatch(1); |
| final byte[] content = new byte[1024 * 1024]; |
| startServer(new HttpServlet() |
| { |
| @Override |
| protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException |
| { |
| response.getOutputStream().write(content); |
| serverLatch.countDown(); |
| } |
| }); |
| Map<String, String> params = new HashMap<>(); |
| params.put("proxyTo", "http://localhost:" + serverConnector.getLocalPort()); |
| startProxy(new AsyncProxyServlet.Transparent() |
| { |
| @Override |
| protected void sendProxyRequest(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest) |
| { |
| proxyRequest.version(HttpVersion.HTTP_1_1); |
| super.sendProxyRequest(clientRequest, proxyResponse, proxyRequest); |
| } |
| }, params); |
| startClient(); |
| |
| final CountDownLatch clientLatch = new CountDownLatch(1); |
| Session session = newClient(new Session.Listener.Adapter()); |
| MetaData.Request metaData = newRequest("GET", "/", new HttpFields()); |
| HeadersFrame frame = new HeadersFrame(metaData, null, true); |
| session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() |
| { |
| @Override |
| public void onData(Stream stream, DataFrame frame, Callback callback) |
| { |
| try |
| { |
| TimeUnit.MILLISECONDS.sleep(1); |
| callback.succeeded(); |
| if (frame.isEndStream()) |
| clientLatch.countDown(); |
| } |
| catch (InterruptedException x) |
| { |
| callback.failed(x); |
| } |
| } |
| }); |
| |
| Assert.assertTrue(serverLatch.await(15, TimeUnit.SECONDS)); |
| Assert.assertTrue(clientLatch.await(15, TimeUnit.SECONDS)); |
| } |
| } |