mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-14 18:03:44 +00:00
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:
parent
39abac98f9
commit
115a413ee4
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
295
test/jdk/com/sun/net/httpserver/FilterTest.java
Normal file
295
test/jdk/com/sun/net/httpserver/FilterTest.java
Normal 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 { }
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user