diff options
Diffstat (limited to 'jdisc_http_service/src/test')
5 files changed, 503 insertions, 0 deletions
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/ChunkReader.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/ChunkReader.java new file mode 100644 index 00000000000..a550a013a3b --- /dev/null +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/ChunkReader.java @@ -0,0 +1,124 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + */ +public class ChunkReader { + + private static final Pattern CONTENT_LENGTH = Pattern.compile(".+^content-length: (\\d+)$.*", + Pattern.CASE_INSENSITIVE | + Pattern.MULTILINE | + Pattern.DOTALL); + private static final Pattern CHUNKED_ENCODING = Pattern.compile(".+^transfer-encoding: chunked$.*", + Pattern.CASE_INSENSITIVE | + Pattern.MULTILINE | + Pattern.DOTALL); + private final InputStream in; + private StringBuilder reading = new StringBuilder(); + private boolean readingHeader = true; + + public ChunkReader(InputStream in) { + this.in = in; + } + + public boolean isEndOfContent() throws IOException { + if (in.available() != 0) { + StringBuilder sb = new StringBuilder(); + sb.append(in.available()).append(": "); + for(int c = in.read(); c != -1; c = in.read()) { + sb.append('\''); + sb.append(c); + sb.append("' "); + } + throw new IllegalStateException("This is not the end '" + sb.toString()); + } + return in.available() == 0; + } + + public String readChunk() throws IOException { + while (true) { + String ret = removeNextChunk(); + if (ret != null) { + return ret; + } + readFromStream(); + } + } + + private String readContent(int length) throws IOException { + while (reading.length() < length) { + readFromStream(); + } + return splitReadBuffer(length); + } + + private void readFromStream() throws IOException { + byte[] buf = new byte[4096]; + try { + while (!Thread.currentThread().isInterrupted()) { + int len = in.read(buf, 0, buf.length); + if (len < 0) { + throw new IOException("Socket is closed."); + } + if (len > 0) { + reading.append(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buf, 0, len))); + break; + } + Thread.sleep(10); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private String removeNextChunk() throws IOException { + if (readingHeader) { + int pos = reading.indexOf("\r\n\r\n"); + if (pos < 0) { + return null; + } + String ret = splitReadBuffer(pos + 4); + Matcher m = CONTENT_LENGTH.matcher(ret); + if (m.matches()) { + ret += readContent(Integer.valueOf(m.group(1))); + } + readingHeader = !CHUNKED_ENCODING.matcher(ret).matches(); + return ret; + } else if (reading.indexOf("0\r\n") == 0) { + int pos = reading.indexOf("\r\n\r\n", 1); + if (pos < 0) { + return null; + } + readingHeader = true; + return splitReadBuffer(pos + 4); + } else { + int pos = reading.indexOf("\r\n"); + if (pos < 0) { + return null; + } + pos = reading.indexOf("\r\n", pos + 2); + if (pos < 0) { + return null; + } + return splitReadBuffer(pos + 2); + } + } + + private String splitReadBuffer(int pos) { + String ret = reading.substring(0, pos); + if (pos < reading.length()) { + reading = new StringBuilder(reading.substring(pos)); + } else { + reading = new StringBuilder(); + } + return ret; + } +} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/FilterTestDriver.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/FilterTestDriver.java new file mode 100644 index 00000000000..1532bc65bdf --- /dev/null +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/FilterTestDriver.java @@ -0,0 +1,70 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.test; + +import com.yahoo.jdisc.Request; +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.handler.AbstractRequestHandler; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.jdisc.handler.ResponseDispatch; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.HttpRequest; +import com.yahoo.jdisc.http.filter.RequestFilter; +import com.yahoo.jdisc.http.filter.ResponseFilter; + +import java.io.IOException; +import java.util.concurrent.Exchanger; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static com.yahoo.jdisc.http.test.ServerTestDriver.newFilterModule; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * + * TODO: dead code? + */ +public class FilterTestDriver { + + private final ServerTestDriver driver; + private final MyRequestHandler requestHandler; + + private FilterTestDriver(ServerTestDriver driver, MyRequestHandler requestHandler) { + this.driver = driver; + this.requestHandler = requestHandler; + } + + public boolean close() throws IOException { + return driver.close(); + } + + public HttpRequest filterRequest(String request) throws IOException, TimeoutException, InterruptedException { + driver.client().writeRequest(request); + return (HttpRequest)requestHandler.exchanger.exchange(null, 60, TimeUnit.SECONDS); + } + + public static FilterTestDriver newInstance(final BindingRepository<RequestFilter> requestFilters, + final BindingRepository<ResponseFilter> responseFilters) + throws IOException { + MyRequestHandler handler = new MyRequestHandler(); + return new FilterTestDriver(ServerTestDriver.newInstance(handler, + newFilterModule(requestFilters, responseFilters)), + handler); + } + + private static class MyRequestHandler extends AbstractRequestHandler { + + final Exchanger<Request> exchanger = new Exchanger<>(); + + @Override + public ContentChannel handleRequest(Request request, ResponseHandler handler) { + ResponseDispatch.newInstance(Response.Status.OK).dispatch(handler); + try { + exchanger.exchange(request); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return null; + } + } +} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/RemoteClient.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/RemoteClient.java new file mode 100644 index 00000000000..dd6033c9975 --- /dev/null +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/RemoteClient.java @@ -0,0 +1,53 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.test; + +import com.yahoo.jdisc.http.server.jetty.JettyHttpServer; +import com.yahoo.jdisc.http.ssl.SslContextFactory; +import com.yahoo.jdisc.http.ssl.SslKeyStore; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +public class RemoteClient extends ChunkReader { + + private final Socket socket; + + private RemoteClient(Socket socket) throws IOException { + super(socket.getInputStream()); + this.socket = socket; + } + + public void close() throws IOException { + socket.close(); + } + + public void writeRequest(String request) throws IOException { + socket.getOutputStream().write(request.getBytes(StandardCharsets.UTF_8)); + } + + public static RemoteClient newInstance(JettyHttpServer server) throws IOException { + return newInstance(server.getListenPort()); + } + + public static RemoteClient newInstance(int listenPort) throws IOException { + return new RemoteClient(new Socket("localhost", listenPort)); + } + + public static RemoteClient newSslInstance(int listenPort, SslKeyStore sslKeyStore) throws IOException { + SSLContext ctx = SslContextFactory.newInstanceFromTrustStore(sslKeyStore).getServerSSLContext(); + if (ctx == null) { + throw new RuntimeException("Failed to create socket with SSLContext."); + } + return new RemoteClient(ctx.getSocketFactory().createSocket("localhost", listenPort)); + } + + public static RemoteClient newSslInstance(JettyHttpServer server, SslKeyStore keyStore) throws IOException { + return newSslInstance(server.getListenPort(), keyStore); + } + +} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/RemoteServer.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/RemoteServer.java new file mode 100644 index 00000000000..62b4bb306ed --- /dev/null +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/RemoteServer.java @@ -0,0 +1,110 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.test; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + */ +public class RemoteServer implements Runnable { + + private final Thread thread = new Thread(this, "RemoteServer@" + System.identityHashCode(this)); + private final LinkedBlockingQueue<Socket> clients = new LinkedBlockingQueue<>(); + private final ServerSocket server; + + private RemoteServer(int listenPort) throws IOException { + this.server = new ServerSocket(listenPort); + } + + @Override + public void run() { + try { + while (!Thread.interrupted()) { + Socket client = server.accept(); + if (client != null) { + clients.add(client); + } + } + } catch (IOException e) { + if (!server.isClosed()) { + e.printStackTrace(); + } + } + } + + public URI newRequestUri(String uri) { + return newRequestUri(URI.create(uri)); + } + + public URI newRequestUri(URI uri) { + URI serverUri = connectionSpec(); + try { + return new URI(serverUri.getScheme(), serverUri.getUserInfo(), serverUri.getHost(), + serverUri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } + + public URI connectionSpec() { + return URI.create("http://localhost:" + server.getLocalPort() + "/"); + } + + public Connection awaitConnection(int timeout, TimeUnit unit) throws InterruptedException, IOException { + Socket client = clients.poll(timeout, unit); + if (client == null) { + return null; + } + return new Connection(client); + } + + public boolean close(int timeout, TimeUnit unit) { + try { + server.close(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + try { + thread.join(unit.toMillis(timeout)); + } catch (InterruptedException e) { + return false; + } + return !thread.isAlive(); + } + + public static RemoteServer newInstance() throws IOException { + RemoteServer ret = new RemoteServer(0); + ret.thread.start(); + return ret; + } + + public static class Connection extends ChunkReader { + + private final Socket socket; + private final PrintWriter out; + + private Connection(Socket socket) throws IOException { + super(socket.getInputStream()); + this.socket = socket; + this.out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); + } + + public void writeChunk(String chunk) { + out.print(chunk); + } + + public void close() throws IOException { + out.close(); + socket.close(); + } + } +} diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/ServerTestDriver.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/ServerTestDriver.java new file mode 100644 index 00000000000..03e2257ce70 --- /dev/null +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/test/ServerTestDriver.java @@ -0,0 +1,146 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.test; + +import com.google.inject.AbstractModule; +import com.google.inject.Module; +import com.google.inject.TypeLiteral; +import com.yahoo.jdisc.application.BindingRepository; +import com.yahoo.jdisc.application.ContainerActivator; +import com.yahoo.jdisc.application.ContainerBuilder; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.http.HttpRequest; +import com.yahoo.jdisc.http.filter.RequestFilter; +import com.yahoo.jdisc.http.filter.ResponseFilter; +import com.yahoo.jdisc.http.server.jetty.JettyHttpServer; +import com.yahoo.jdisc.http.ssl.SslKeyStore; +import com.yahoo.jdisc.test.TestDriver; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +public class ServerTestDriver { + + private final TestDriver driver; + private final JettyHttpServer server; + private final RemoteClient client; + + private ServerTestDriver(TestDriver driver, JettyHttpServer server, RemoteClient client) { + this.driver = driver; + this.server = server; + this.client = client; + } + + public boolean close() throws IOException { + client.close(); + server.close(); + server.release(); + return driver.close(); + } + + public TestDriver parent() { + return driver; + } + + public ContainerActivator containerActivator() { + return driver; + } + + public JettyHttpServer server() { + return server; + } + + public RemoteClient client() { + return client; + } + + public HttpRequest newRequest(HttpRequest.Method method, String uri, HttpRequest.Version version) { + return HttpRequest.newServerRequest(driver, newRequestUri(uri), method, version); + } + + public URI newRequestUri(String uri) { + return newRequestUri(URI.create(uri)); + } + + public URI newRequestUri(URI uri) { + try { + return new URI("http", null, "locahost", + server.getListenPort(), uri.getPath(), uri.getQuery(), uri.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } + + public static ServerTestDriver newInstance(RequestHandler requestHandler, Module... guiceModules) throws IOException { + return newInstance(requestHandler, Arrays.asList(guiceModules)); + } + + public static ServerTestDriver newInstance(RequestHandler requestHandler, Iterable<Module> guiceModules) + throws IOException { + List<Module> lst = new LinkedList<>(); + lst.add(newDefaultModule()); + for (Module module : guiceModules) { + lst.add(module); + } + TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(lst.toArray(new Module[lst.size()])); + ContainerBuilder builder = driver.newContainerBuilder(); + builder.serverBindings().bind("*://*/*", requestHandler); + JettyHttpServer server = builder.guiceModules().getInstance(JettyHttpServer.class); + return newInstance(null, driver, builder, server); + } + + private static ServerTestDriver newInstance(SslKeyStore clientTrustStore, TestDriver driver, ContainerBuilder builder, + JettyHttpServer server) throws IOException { + builder.serverProviders().install(server); + driver.activateContainer(builder); + try { + server.start(); + } catch (RuntimeException e) { + server.release(); + driver.close(); + throw e; + } + RemoteClient client; + if (clientTrustStore == null) { + client = RemoteClient.newInstance(server); + } else { + client = RemoteClient.newSslInstance(server, clientTrustStore); + } + return new ServerTestDriver(driver, server, client); + } + + public static Module newDefaultModule() { + return new AbstractModule() { + + @Override + protected void configure() { + bind(new TypeLiteral<BindingRepository<RequestFilter>>() { }) + .toInstance(new BindingRepository<>()); + bind(new TypeLiteral<BindingRepository<ResponseFilter>>() { }) + .toInstance(new BindingRepository<>()); + } + }; + } + + public static Module newFilterModule(final BindingRepository<RequestFilter> requestFilters, + final BindingRepository<ResponseFilter> responseFilters) { + return new AbstractModule() { + + @Override + protected void configure() { + if (requestFilters != null) { + bind(new TypeLiteral<BindingRepository<RequestFilter>>() { }).toInstance(requestFilters); + } + if (responseFilters != null) { + bind(new TypeLiteral<BindingRepository<ResponseFilter>>() { }).toInstance(responseFilters); + } + } + }; + } +} |