8265123: Add static factory methods to com.sun.net.httpserver.Filter

Co-authored-by: Michael McMahon <michaelm@openjdk.org>
Reviewed-by: chegar, michaelm, dfuchs
This commit is contained in:
Julia Boes 2021-04-29 09:07:46 +00:00
parent 39abac98f9
commit 115a413ee4
3 changed files with 409 additions and 3 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 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
@ -30,6 +30,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.function.Consumer;
/**
* A filter used to pre- and post-process incoming requests. Pre-processing occurs
@ -140,4 +142,111 @@ public abstract class Filter {
*/
public abstract String description ();
/**
* Returns a pre-processing {@code Filter} with the given description and
* operation.
*
* <p>The {@link Consumer operation} is the effective implementation of the
* filter. It is executed for each {@code HttpExchange} before invoking
* either the next filter in the chain or the exchange handler (if this is
* the final filter in the chain).
*
* @apiNote
* A beforeHandler filter is typically used to examine or modify the
* exchange state before it is handled. The filter {@code operation} is
* executed before {@link Filter.Chain#doFilter(HttpExchange)} is invoked,
* so before any subsequent filters in the chain and the exchange handler
* are executed. The filter {@code operation} is not expected to handle the
* request or {@linkplain HttpExchange#sendResponseHeaders(int, long) send response headers},
* since this is commonly done by the exchange handler.
*
* <p> Example of adding the {@code "Foo"} response header to all responses:
* <pre>{@code
* var filter = Filter.beforeHandler("Add response header Foo",
* e -> e.getResponseHeaders().set("Foo", "Bar"));
* httpContext.getFilters().add(filter);
* }</pre>
*
* @param description the string to be returned from {@link #description()}
* @param operation the operation of the returned filter
* @return a filter whose operation is invoked before the exchange is handled
* @throws NullPointerException if any argument is null
* @since 17
*/
public static Filter beforeHandler(String description,
Consumer<HttpExchange> operation) {
Objects.requireNonNull(description);
Objects.requireNonNull(operation);
return new Filter() {
@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
operation.accept(exchange);
chain.doFilter(exchange);
}
@Override
public String description() {
return description;
}
};
}
/**
* Returns a post-processing {@code Filter} with the given description and
* operation.
*
* <p>The {@link Consumer operation} is the effective implementation of the
* filter. It is executed for each {@code HttpExchange} after invoking
* either the next filter in the chain or the exchange handler (if this
* filter is the final filter in the chain).
*
* @apiNote
* An afterHandler filter is typically used to examine the exchange state
* rather than modifying it. The filter {@code operation} is executed after
* {@link Filter.Chain#doFilter(HttpExchange)} is invoked, this means any
* subsequent filters in the chain and the exchange handler have been
* executed. The filter {@code operation} is not expected to handle the
* exchange or {@linkplain HttpExchange#sendResponseHeaders(int, long) send the response headers}.
* Doing so is likely to fail, since the exchange has commonly been handled
* before the operation is invoked.
*
* <p> Example of adding a filter that logs the response code of all exchanges:
* <pre>{@code
* var filter = Filter.afterHandler("Log response code", e -> log(e.getResponseCode());
* httpContext.getFilters().add(filter);
* }</pre>
*
* <p> Example of adding a sequence of afterHandler filters to a context:<br>
* The order in which the filter operations are invoked is reverse to the
* order in which the filters are added to the context's filter-list.
*
* <pre>{@code
* var a1Set = Filter.afterHandler("Set a1", e -> e.setAttribute("a1", "some value"));
* var a1Get = Filter.afterHandler("Get a1", e -> doSomething(e.getAttribute("a1")));
* httpContext.getFilters().addAll(List.of(a1Get, a1Set));
* }</pre>
* <p>The operation of {@code a1Get} will be invoked after the operation of
* {@code a1Set} because {@code a1Get} was added before {@code a1Set}.
*
* @param description the string to be returned from {@link #description()}
* @param operation the operation of the returned filter
* @return a filter whose operation is invoked after the exchange is handled
* @throws NullPointerException if any argument is null
* @since 17
*/
public static Filter afterHandler(String description,
Consumer<HttpExchange> operation) {
Objects.requireNonNull(description);
Objects.requireNonNull(operation);
return new Filter() {
@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
chain.doFilter(exchange);
operation.accept(exchange);
}
@Override
public String description() {
return description;
}
};
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 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
@ -659,7 +659,9 @@ class SSLStreams {
r = wrapper.wrapAndSend (buf);
stat = r.result.getHandshakeStatus();
}
assert r.result.getStatus() == Status.CLOSED;
assert r.result.getStatus() == Status.CLOSED
: "status is: " + r.result.getStatus()
+ ", handshakeStatus is: " + stat;
}
}
}

View File

@ -0,0 +1,295 @@
/*
* 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
* @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.atomic.AtomicInteger;
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 static java.net.http.HttpClient.Builder.NO_PROXY;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeTest;
import static org.testng.Assert.*;
public class FilterTest {
static final Class<NullPointerException> NPE = NullPointerException.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(null, HttpExchange::getResponseCode));
expectThrows(NPE, () -> Filter.afterHandler("Some description", null));
}
@Test
public void testDescription() {
var desc = "Some description";
var beforeFilter = Filter.beforeHandler(desc, HttpExchange::getRequestBody);
var afterFilter = Filter.afterHandler(desc, HttpExchange::getResponseCode);
assertEquals(desc, beforeFilter.description());
assertEquals(desc, afterFilter.description());
}
@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 AtomicInteger();
var filter = Filter.afterHandler("Log response code",
e -> respCode.set(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(), respCode.get());
} finally {
server.stop(0);
}
}
@Test
public void testAfterHandlerRepeated() throws Exception {
var handler = new EchoHandler();
var attr = new AtomicReference<String>();
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.set((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 AtomicInteger();
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.set(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(), respCode.get());
} finally {
server.stop(0);
}
}
@Test
public void testBeforeAndAfterHandler() throws Exception {
var handler = new EchoHandler();
var respCode = new AtomicInteger();
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.set(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.statusCode(), respCode.get());
assertEquals(response.headers().map().size(), 3);
assertEquals(response.headers().firstValue("x-foo").orElseThrow(), "bar");
} finally {
server.stop(0);
}
}
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 test handler that does nothing
*/
static class NoResponseHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException { }
}
}