/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8267262
* @summary Tests for Filter static factory methods
* @run testng/othervm FilterTest
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeTest;
import static java.net.http.HttpClient.Builder.NO_PROXY;
import static org.testng.Assert.*;
public class FilterTest {
static final Class NPE = NullPointerException.class;
static final Class IOE = IOException.class;
static final InetAddress LOOPBACK_ADDR = InetAddress.getLoopbackAddress();
static final boolean ENABLE_LOGGING = true;
static final Logger logger = Logger.getLogger("com.sun.net.httpserver");
@BeforeTest
public void setup() {
if (ENABLE_LOGGING) {
ConsoleHandler ch = new ConsoleHandler();
logger.setLevel(Level.ALL);
ch.setLevel(Level.ALL);
logger.addHandler(ch);
}
}
@Test
public void testNull() {
expectThrows(NPE, () -> Filter.beforeHandler(null, e -> e.getResponseHeaders().set("X-Foo", "Bar")));
expectThrows(NPE, () -> Filter.beforeHandler("Some description", null));
expectThrows(NPE, () -> Filter.afterHandler("Some description", null));
expectThrows(NPE, () -> Filter.afterHandler(null, HttpExchange::getResponseCode));
expectThrows(NPE, () -> Filter.adaptRequest("Some description", null));
expectThrows(NPE, () -> Filter.adaptRequest(null, r -> r.with("Foo", List.of("Bar"))));
}
@Test
public void testDescription() {
var desc = "Some description";
var beforeFilter = Filter.beforeHandler(desc, HttpExchange::getRequestBody);
assertEquals(desc, beforeFilter.description());
var afterFilter = Filter.afterHandler(desc, HttpExchange::getResponseCode);
assertEquals(desc, afterFilter.description());
var adaptFilter = Filter.adaptRequest(desc, r -> r.with("Foo", List.of("Bar")));
assertEquals(desc, adaptFilter.description());
}
@DataProvider
public static Object[][] throwingFilters() {
return new Object[][] {
{Filter.beforeHandler("before RE", e -> { throw new RuntimeException(); }), IOE},
{Filter.beforeHandler("before AE", e -> { throw new AssertionError(); }), IOE},
{Filter.afterHandler( "after RE", e -> { throw new RuntimeException(); }), null},
{Filter.afterHandler( "after AE", e -> { throw new AssertionError(); }), null},
};
}
@Test(dataProvider = "throwingFilters")
public void testException(Filter filter, Class exception)
throws Exception
{
var handler = new EchoHandler();
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR,0), 10);
server.createContext("/", handler).getFilters().add(filter);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "")).build();
if (exception != null) {
expectThrows(exception, () -> client.send(request, HttpResponse.BodyHandlers.ofString()));
} else {
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(response.statusCode(), 200);
assertEquals(response.body(), "hello world");
}
} finally {
server.stop(0);
}
}
@Test
public void testBeforeHandler() throws Exception {
var handler = new EchoHandler();
var filter = Filter.beforeHandler("Add x-foo response header",
e -> e.getResponseHeaders().set("x-foo", "bar"));
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR,0), 10);
server.createContext("/", handler).getFilters().add(filter);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "")).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(response.statusCode(), 200);
assertEquals(response.headers().map().size(), 3);
assertEquals(response.headers().firstValue("x-foo").orElseThrow(), "bar");
} finally {
server.stop(0);
}
}
@Test
public void testBeforeHandlerRepeated() throws Exception {
var handler = new EchoHandler();
var filter1 = Filter.beforeHandler("Add x-foo response header",
e -> e.getResponseHeaders().set("x-foo", "bar"));
var filter2 = Filter.beforeHandler("Update x-foo response header",
e -> e.getResponseHeaders().set("x-foo", "barbar"));
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 10);
var context = server.createContext("/", handler);
context.getFilters().add(filter1);
context.getFilters().add(filter2);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "")).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(response.statusCode(), 200);
assertEquals(response.headers().map().size(), 3);
assertEquals(response.headers().firstValue("x-foo").orElseThrow(), "barbar");
} finally {
server.stop(0);
}
}
@Test
public void testBeforeHandlerSendResponse() throws Exception {
var handler = new NoResponseHandler();
var filter = Filter.beforeHandler("Add x-foo response header and send response",
e -> {
try (InputStream is = e.getRequestBody();
OutputStream os = e.getResponseBody()) {
is.readAllBytes();
e.getResponseHeaders().set("x-foo", "bar");
var resp = "hello world".getBytes(StandardCharsets.UTF_8);
e.sendResponseHeaders(200, resp.length);
os.write(resp);
} catch (IOException ioe) {
ioe.printStackTrace(System.out);
throw new UncheckedIOException(ioe);
}
});
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 10);
server.createContext("/", handler).getFilters().add(filter);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "")).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(response.statusCode(), 200);
assertEquals(response.headers().map().size(), 3);
assertEquals(response.headers().firstValue("x-foo").orElseThrow(), "bar");
} finally {
server.stop(0);
}
}
@Test
public void testAfterHandler() throws Exception {
var handler = new EchoHandler();
var respCode = new CompletableFuture();
var filter = Filter.afterHandler("Log response code",
e -> respCode.complete(e.getResponseCode()));
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 10);
server.createContext("/", handler).getFilters().add(filter);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "")).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(response.statusCode(), 200);
assertEquals(response.statusCode(), (int)respCode.get());
} finally {
server.stop(0);
}
}
@Test
public void testAfterHandlerRepeated() throws Exception {
var handler = new EchoHandler();
var attr = new CompletableFuture();
final var value = "some value";
var filter1 = Filter.afterHandler("Set attribute",
e -> e.setAttribute("test-attr", value));
var filter2 = Filter.afterHandler("Read attribute",
e -> attr.complete((String) e.getAttribute("test-attr")));
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 10);
var context = server.createContext("/", handler);
context.getFilters().add(filter2);
context.getFilters().add(filter1);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "")).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(response.statusCode(), 200);
assertEquals(attr.get(), value);
} finally {
server.stop(0);
}
}
@Test
public void testAfterHandlerSendResponse() throws Exception {
var handler = new NoResponseHandler();
var respCode = new CompletableFuture();
var filter = Filter.afterHandler("Log response code and send response",
e -> {
try (InputStream is = e.getRequestBody();
OutputStream os = e.getResponseBody()) {
is.readAllBytes();
var resp = "hello world".getBytes(StandardCharsets.UTF_8);
e.sendResponseHeaders(200, resp.length);
os.write(resp);
respCode.complete(e.getResponseCode());
} catch (IOException ioe) {
ioe.printStackTrace(System.out);
throw new UncheckedIOException(ioe);
}
});
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 10);
server.createContext("/", handler).getFilters().add(filter);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "")).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(response.statusCode(), 200);
assertEquals(response.statusCode(), (int)respCode.get());
} finally {
server.stop(0);
}
}
@Test
public void testBeforeAndAfterHandler() throws Exception {
var handler = new EchoHandler();
var respCode = new CompletableFuture();
var beforeFilter = Filter.beforeHandler("Add x-foo response header",
e -> e.getResponseHeaders().set("x-foo", "bar"));
var afterFilter = Filter.afterHandler("Log response code",
e -> respCode.complete(e.getResponseCode()));
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 10);
var context = server.createContext("/", handler);
context.getFilters().add(beforeFilter);
context.getFilters().add(afterFilter);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "")).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(response.statusCode(), 200);
assertEquals(response.headers().map().size(), 3);
assertEquals(response.headers().firstValue("x-foo").orElseThrow(), "bar");
assertEquals(response.statusCode(), (int)respCode.get());
} finally {
server.stop(0);
}
}
@Test
public void testInspectRequest() throws Exception {
var handler = new EchoHandler();
var inspectedURI = new AtomicReference();
var filter = Filter.adaptRequest("Inspect request URI",
r -> {inspectedURI.set(r.getRequestURI()); return r;});
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR,0), 10);
server.createContext("/", handler).getFilters().add(filter);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "foo/bar")).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(response.statusCode(), 200);
assertEquals(inspectedURI.get(), URI.create("/foo/bar"));
} finally {
server.stop(0);
}
}
private static HttpExchange originalExchange;
/**
* Confirms that adaptRequest changes only the expected request state and
* all other exchange state remains unchanged.
*/
@Test
public void testAdaptRequest() throws Exception {
var handler = new CompareStateAndEchoHandler();
var captureFilter = Filter.beforeHandler("capture exchange", e -> {
e.setAttribute("foo", "bar");
originalExchange = e;
});
var adaptFilter = Filter.adaptRequest("Add x-foo request header", r -> {
// Confirm request state is unchanged
assertEquals(r.getRequestHeaders(), originalExchange.getRequestHeaders());
assertEquals(r.getRequestURI(), originalExchange.getRequestURI());
assertEquals(r.getRequestMethod(), originalExchange.getRequestMethod());
return r.with("x-foo", List.of("bar"));
});
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR,0), 10);
var context = server.createContext("/", handler);
context.getFilters().add(captureFilter);
context.getFilters().add(adaptFilter);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "")).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(response.statusCode(), 200);
assertEquals(response.body(), "bar");
} finally {
server.stop(0);
}
}
// --- infra ---
static URI uri(HttpServer server, String path) {
return URI.create("http://localhost:%s/%s".formatted(server.getAddress().getPort(), path));
}
/**
* A test handler that discards the request and sends a response
*/
static class EchoHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
try (InputStream is = exchange.getRequestBody();
OutputStream os = exchange.getResponseBody()) {
is.readAllBytes();
var resp = "hello world".getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, resp.length);
os.write(resp);
}
}
}
/**
* A handler that compares the adapted exchange with the original exchange,
* before discarding the request and returning the test request header value.
*/
static class CompareStateAndEchoHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
assertEquals(exchange.getLocalAddress(), originalExchange.getLocalAddress());
assertEquals(exchange.getRemoteAddress(), originalExchange.getRemoteAddress());
assertEquals(exchange.getProtocol(), originalExchange.getProtocol());
assertEquals(exchange.getPrincipal(), originalExchange.getPrincipal());
assertEquals(exchange.getHttpContext(), originalExchange.getHttpContext());
assertEquals(exchange.getRequestMethod(), originalExchange.getRequestMethod());
assertEquals(exchange.getRequestURI(), originalExchange.getRequestURI());
assertEquals(exchange.getRequestBody(), originalExchange.getRequestBody());
assertEquals(exchange.getResponseHeaders(), originalExchange.getResponseHeaders());
assertEquals(exchange.getResponseCode(), originalExchange.getResponseCode());
assertEquals(exchange.getResponseBody(), originalExchange.getResponseBody());
assertEquals(exchange.getAttribute("foo"), originalExchange.getAttribute("foo"));
assertFalse(exchange.getRequestHeaders().equals(originalExchange.getRequestHeaders()));
exchange.setAttribute("foo", "barbar");
assertEquals(exchange.getAttribute("foo"), originalExchange.getAttribute("foo"));
try (InputStream is = exchange.getRequestBody();
OutputStream os = exchange.getResponseBody()) {
is.readAllBytes();
var resp = exchange.getRequestHeaders().get("x-foo")
.get(0)
.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, resp.length);
os.write(resp);
}
}
}
/**
* A test handler that does nothing
*/
static class NoResponseHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException { }
}
}