Skip to content

8235786: Javadoc for com/sun/net/httpserver/HttpExchange.java#setAttribute is unclear #22454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2024, 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
@@ -233,22 +233,29 @@ protected HttpExchange() {
public abstract String getProtocol();

/**
* {@link Filter} modules may store arbitrary objects with {@code HttpExchange}
* instances as an out-of-band communication mechanism. Other filters
* Returns the attribute's value from this exchange's
* {@linkplain HttpContext#getAttributes() context attributes}.
*
* @apiNote {@link Filter} modules may store arbitrary objects as attributes through
* {@code HttpExchange} instances as an out-of-band communication mechanism. Other filters
* or the exchange handler may then access these objects.
*
* <p> Each {@code Filter} class will document the attributes which they make
* available.
*
* @param name the name of the attribute to retrieve
* @return the attribute object, or {@code null} if it does not exist
* @return the attribute's value or {@code null} if either the attribute isn't set
* or the attribute value is {@code null}
* @throws NullPointerException if name is {@code null}
*/
public abstract Object getAttribute(String name);

/**
* {@link Filter} modules may store arbitrary objects with {@code HttpExchange}
* instances as an out-of-band communication mechanism. Other filters
* Sets an attribute with the given {@code name} and {@code value} in this exchange's
* {@linkplain HttpContext#getAttributes() context attributes}.
*
* @apiNote {@link Filter} modules may store arbitrary objects as attributes through
* {@code HttpExchange} instances as an out-of-band communication mechanism. Other filters
* or the exchange handler may then access these objects.
*
* <p> Each {@code Filter} class will document the attributes which they make
130 changes: 113 additions & 17 deletions test/jdk/com/sun/net/httpserver/ExchangeAttributeTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, 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
@@ -23,16 +23,19 @@

/*
* @test
* @bug 8288109
* @bug 8288109 8235786
* @summary Tests for HttpExchange set/getAttribute
* @library /test/lib
* @run junit/othervm ExchangeAttributeTest
*/

import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import jdk.test.lib.net.URIBuilder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

@@ -53,44 +56,87 @@

public class ExchangeAttributeTest {

static final InetAddress LOOPBACK_ADDR = InetAddress.getLoopbackAddress();
static final boolean ENABLE_LOGGING = true;
static final Logger logger = Logger.getLogger("com.sun.net.httpserver");
private static final InetAddress LOOPBACK_ADDR = InetAddress.getLoopbackAddress();
private static final boolean ENABLE_LOGGING = true;
private static final Logger logger = Logger.getLogger("com.sun.net.httpserver");

private static HttpServer server;

@BeforeAll
public static void setup() {
public static void setup() throws Exception {
if (ENABLE_LOGGING) {
ConsoleHandler ch = new ConsoleHandler();
logger.setLevel(Level.ALL);
ch.setLevel(Level.ALL);
logger.addHandler(ch);
}
server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 10);
server.createContext("/normal", new AttribHandler());
final HttpContext filteredCtx = server.createContext("/filtered", new AttribHandler());
filteredCtx.getFilters().add(new AttributeAddingFilter());
server.start();
System.out.println("Server started at " + server.getAddress());
}

@AfterAll
public static void afterAll() {
if (server != null) {
System.out.println("Stopping server " + server.getAddress());
server.stop(0);
}
}

/*
* Verifies that HttpExchange.setAttribute() allows for null value.
*/
@Test
public void testExchangeAttributes() throws Exception {
var handler = new AttribHandler();
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR,0), 10);
server.createContext("/", handler);
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
var request = HttpRequest.newBuilder(uri(server, "")).build();
public void testNullAttributeValue() throws Exception {
try (var client = HttpClient.newBuilder().proxy(NO_PROXY).build()) {
var request = HttpRequest.newBuilder(uri(server, "/normal", null)).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(200, response.statusCode());
} finally {
server.stop(0);
}
}

/*
* Verifies that an attribute set on one exchange is accessible to another exchange that
* belongs to the same HttpContext.
*/
@Test
public void testSharedAttribute() throws Exception {
try (var client = HttpClient.newBuilder().proxy(NO_PROXY).build()) {
final var firstReq = HttpRequest.newBuilder(uri(server, "/filtered", "firstreq"))
.build();
System.out.println("issuing request " + firstReq);
final var firstResp = client.send(firstReq, HttpResponse.BodyHandlers.ofString());
assertEquals(200, firstResp.statusCode());

// issue the second request
final var secondReq = HttpRequest.newBuilder(uri(server, "/filtered", "secondreq"))
.build();
System.out.println("issuing request " + secondReq);
final var secondResp = client.send(secondReq, HttpResponse.BodyHandlers.ofString());
assertEquals(200, secondResp.statusCode());

// verify that the filter was invoked for both the requests. the filter internally
// does the setAttribute() and getAttribute() and asserts that the attribute value
// set by the first exchange was available through the second exchange.
assertTrue(AttributeAddingFilter.filteredFirstReq, "Filter wasn't invoked for "
+ firstReq.uri());
assertTrue(AttributeAddingFilter.filteredSecondReq, "Filter wasn't invoked for "
+ secondReq.uri());
}
}

// --- infra ---

static URI uri(HttpServer server, String path) throws URISyntaxException {
static URI uri(HttpServer server, String path, String query) throws URISyntaxException {
return URIBuilder.newBuilder()
.scheme("http")
.loopback()
.port(server.getAddress().getPort())
.path(path)
.query(query)
.build();
}

@@ -112,4 +158,54 @@ public void handle(HttpExchange exchange) throws IOException {
}
}
}

private static final class AttributeAddingFilter extends Filter {

private static final String ATTR_NAME ="foo-bar";
private static final String ATTR_VAL ="hello-world";
private static volatile boolean filteredFirstReq;
private static volatile boolean filteredSecondReq;

@Override
public void doFilter(final HttpExchange exchange, final Chain chain) throws IOException {
if (exchange.getRequestURI().getQuery().contains("firstreq")) {
filteredFirstReq = true;
// add a request attribute through the exchange, for this first request
// and at the same time verify that the attribute doesn't already exist
final Object attrVal = exchange.getAttribute(ATTR_NAME);
if (attrVal != null) {
throw new IOException("attribute " + ATTR_NAME + " with value: " + attrVal
+ " unexpectedly present in exchange: " + exchange.getRequestURI());
}
// set the value
exchange.setAttribute(ATTR_NAME, ATTR_VAL);
System.out.println(exchange.getRequestURI() + " set attribute "
+ ATTR_NAME + "=" + ATTR_VAL);
} else if (exchange.getRequestURI().getQuery().contains("secondreq")) {
filteredSecondReq = true;
// verify the attribute is already set and the value is the expected one.
final Object attrVal = exchange.getAttribute(ATTR_NAME);
if (attrVal == null) {
throw new IOException("attribute " + ATTR_NAME + " is missing in exchange: "
+ exchange.getRequestURI());
}
if (!ATTR_VAL.equals(attrVal)) {
throw new IOException("unexpected value: " + attrVal + " for attribute "
+ ATTR_NAME + " in exchange: " + exchange.getRequestURI());
}
System.out.println(exchange.getRequestURI() + " found attribute "
+ ATTR_NAME + "=" + attrVal);
} else {
// unexpected request
throw new IOException("unexpected request " + exchange.getRequestURI());
}
// let the request proceed
chain.doFilter(exchange);
}

@Override
public String description() {
return "AttributeAddingFilter";
}
}
}