--- a/src/java.net.http/share/classes/java/net/http/HttpHeaders.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/java/net/http/HttpHeaders.java Fri May 25 16:13:11 2018 +0100
@@ -25,62 +25,68 @@
package java.net.http;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.unmodifiableList;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.function.BiPredicate;
+import static java.lang.String.CASE_INSENSITIVE_ORDER;
+import static java.util.Collections.unmodifiableMap;
import static java.util.Objects.requireNonNull;
/**
* A read-only view of a set of HTTP headers.
*
- * <p> An {@code HttpHeaders} is not created directly, but rather returned from
- * an {@link HttpResponse HttpResponse}. Specific HTTP headers can be set for
- * {@linkplain HttpRequest requests} through the one of the request builder's
- * {@link HttpRequest.Builder#header(String, String) headers} methods.
+ * <p> An {@code HttpHeaders} is not typically created directly, but rather
+ * returned from an {@link HttpRequest#headers() HttpRequest} or an
+ * {@link HttpResponse#headers() HttpResponse}. Specific HTTP headers can be
+ * set for a {@linkplain HttpRequest request} through one of the request
+ * builder's {@link HttpRequest.Builder#header(String, String) headers} methods.
*
* <p> The methods of this class ( that accept a String header name ), and the
- * Map returned by the {@link #map() map} method, operate without regard to
- * case when retrieving the header value.
+ * {@code Map} returned by the {@link #map() map} method, operate without regard
+ * to case when retrieving the header value(s).
+ *
+ * <p> An HTTP header name may appear more than once in the HTTP protocol. As
+ * such, headers are represented as a name and a list of values. Each occurrence
+ * of a header value is added verbatim, to the appropriate header name list,
+ * without interpreting its value. In particular, {@code HttpHeaders} does not
+ * perform any splitting or joining of comma separated header value strings. The
+ * order of elements in a header value list is preserved when {@link
+ * HttpRequest.Builder#header(String, String) building} a request. For
+ * responses, the order of elements in a header value list is the order in which
+ * they were received. The {@code Map} returned by the {@code map} method,
+ * however, does not provide any guarantee with regard to the ordering of its
+ * entries.
*
* <p> {@code HttpHeaders} instances are immutable.
*
* @since 11
*/
-public abstract class HttpHeaders {
-
- /**
- * Creates an HttpHeaders.
- */
- protected HttpHeaders() {}
+public final class HttpHeaders {
/**
- * Returns an {@link Optional} containing the first value of the given named
- * (and possibly multi-valued) header. If the header is not present, then
- * the returned {@code Optional} is empty.
- *
- * @implSpec
- * The default implementation invokes
- * {@code allValues(name).stream().findFirst()}
+ * Returns an {@link Optional} containing the first header string value of
+ * the given named (and possibly multi-valued) header. If the header is not
+ * present, then the returned {@code Optional} is empty.
*
* @param name the header name
- * @return an {@code Optional<String>} for the first named value
+ * @return an {@code Optional<String>} containing the first named header
+ * string value, if present
*/
public Optional<String> firstValue(String name) {
return allValues(name).stream().findFirst();
}
/**
- * Returns an {@link OptionalLong} containing the first value of the
- * named header field. If the header is not present, then the Optional is
- * empty. If the header is present but contains a value that does not parse
- * as a {@code Long} value, then an exception is thrown.
- *
- * @implSpec
- * The default implementation invokes
- * {@code allValues(name).stream().mapToLong(Long::valueOf).findFirst()}
+ * Returns an {@link OptionalLong} containing the first header string value
+ * of the named header field. If the header is not present, then the
+ * Optional is empty. If the header is present but contains a value that
+ * does not parse as a {@code Long} value, then an exception is thrown.
*
* @param name the header name
* @return an {@code OptionalLong}
@@ -92,23 +98,19 @@
}
/**
- * Returns an unmodifiable List of all of the values of the given named
- * header. Always returns a List, which may be empty if the header is not
- * present.
- *
- * @implSpec
- * The default implementation invokes, among other things, the
- * {@code map().get(name)} to retrieve the list of header values.
+ * Returns an unmodifiable List of all of the header string values of the
+ * given named header. Always returns a List, which may be empty if the
+ * header is not present.
*
* @param name the header name
- * @return a List of String values
+ * @return a List of headers string values
*/
public List<String> allValues(String name) {
requireNonNull(name);
List<String> values = map().get(name);
// Making unmodifiable list out of empty in order to make a list which
// throws UOE unconditionally
- return values != null ? values : unmodifiableList(emptyList());
+ return values != null ? values : List.of();
}
/**
@@ -116,7 +118,9 @@
*
* @return the Map
*/
- public abstract Map<String, List<String>> map();
+ public Map<String,List<String>> map() {
+ return headers;
+ }
/**
* Tests this HTTP headers instance for equality with the given object.
@@ -165,4 +169,84 @@
sb.append(" }");
return sb.toString();
}
+
+ /**
+ * Returns an HTTP headers from the given map. The given map's key
+ * represents the header name, and its value the list of string header
+ * values for that header name.
+ *
+ * <p> An HTTP header name may appear more than once in the HTTP protocol.
+ * Such, <i>multi-valued</i>, headers must be represented by a single entry
+ * in the given map, whose entry value is a list that represents the
+ * multiple header string values. Leading and trailing whitespaces are
+ * removed from all string values retrieved from the given map and its lists
+ * before processing. Only headers that, after filtering, contain at least
+ * one, possibly empty string, value will be added to the HTTP headers.
+ *
+ * @apiNote The primary purpose of this method is for testing frameworks.
+ * Per-request headers can be set through one of the {@code HttpRequest}
+ * {@link HttpRequest.Builder#header(String, String) headers} methods.
+ *
+ * @param headerMap the map containing the header names and values
+ * @param filter a filter that can be used to inspect each
+ * header-name-and-value pair in the given map to determine if
+ * it should, or should not, be added to the to the HTTP
+ * headers
+ * @return an HTTP headers instance containing the given headers
+ * @throws NullPointerException if any of: {@code headerMap}, a key or value
+ * in the given map, or an entry in the map's value list, or
+ * {@code filter}, is {@code null}
+ * @throws IllegalArgumentException if the given {@code headerMap} contains
+ * any two keys that are equal ( without regard to case ); or if the
+ * given map contains any key whose length, after trimming
+ * whitespaces, is {@code 0}
+ */
+ public static HttpHeaders of(Map<String,List<String>> headerMap,
+ BiPredicate<String,String> filter) {
+ requireNonNull(headerMap);
+ requireNonNull(filter);
+ return headersOf(headerMap, filter);
+ }
+
+ // --
+
+ private static final HttpHeaders NO_HEADERS = new HttpHeaders(Map.of());
+
+ private final Map<String,List<String>> headers;
+
+ private HttpHeaders(Map<String,List<String>> headers) {
+ this.headers = headers;
+ }
+
+ // Returns a new HTTP headers after performing a structural copy and filtering.
+ private static HttpHeaders headersOf(Map<String,List<String>> map,
+ BiPredicate<String,String> filter) {
+ TreeMap<String,List<String>> other = new TreeMap<>(CASE_INSENSITIVE_ORDER);
+ TreeSet<String> notAdded = new TreeSet<>(CASE_INSENSITIVE_ORDER);
+ ArrayList<String> tempList = new ArrayList<>();
+ map.forEach((key, value) -> {
+ String headerName = requireNonNull(key).trim();
+ if (headerName.isEmpty()) {
+ throw new IllegalArgumentException("empty key");
+ }
+ List<String> headerValues = requireNonNull(value);
+ headerValues.forEach(headerValue -> {
+ headerValue = requireNonNull(headerValue).trim();
+ if (filter.test(headerName, headerValue)) {
+ tempList.add(headerValue);
+ }
+ });
+
+ if (tempList.isEmpty()) {
+ if (other.containsKey(headerName)
+ || notAdded.contains(headerName.toLowerCase(Locale.ROOT)))
+ throw new IllegalArgumentException("duplicate key: " + headerName);
+ notAdded.add(headerName.toLowerCase(Locale.ROOT));
+ } else if (other.put(headerName, List.copyOf(tempList)) != null) {
+ throw new IllegalArgumentException("duplicate key: " + headerName);
+ }
+ tempList.clear();
+ });
+ return other.isEmpty() ? NO_HEADERS : new HttpHeaders(unmodifiableMap(other));
+ }
}
--- a/src/java.net.http/share/classes/java/net/http/HttpRequest.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/java/net/http/HttpRequest.java Fri May 25 16:13:11 2018 +0100
@@ -89,10 +89,12 @@
* <p> Instances of {@code HttpRequest.Builder} are created by calling {@link
* HttpRequest#newBuilder(URI)} or {@link HttpRequest#newBuilder()}.
*
- * <p> Each of the setter methods modifies the state of the builder
- * and returns the same instance. The methods are not synchronized and
- * should not be called from multiple threads without external
- * synchronization. The {@link #build() build} method returns a new
+ * <p> The builder can be used to configure per-request state, like: the
+ * request URI, the request method ( default is GET unless explicitly set ),
+ * specific request headers, etc. Each of the setter methods modifies the
+ * state of the builder and returns the same instance. The methods are not
+ * synchronized and should not be called from multiple threads without
+ * external synchronization. The {@link #build() build} method returns a new
* {@code HttpRequest} each time it is invoked. Once built an {@code
* HttpRequest} is immutable, and can be sent multiple times.
*
--- a/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java Fri May 25 16:13:11 2018 +0100
@@ -59,9 +59,9 @@
static final int UNAUTHORIZED = 401;
static final int PROXY_UNAUTHORIZED = 407;
- private static final List<String> BASIC_DUMMY =
- List.of("Basic " + Base64.getEncoder()
- .encodeToString("o:o".getBytes(ISO_8859_1)));
+ private static final String BASIC_DUMMY =
+ "Basic " + Base64.getEncoder()
+ .encodeToString("o:o".getBytes(ISO_8859_1));
// A public no-arg constructor is required by FilterFactory
public AuthenticationFilter() {}
@@ -182,14 +182,12 @@
String value = "Basic " + s;
if (proxy) {
if (r.isConnect()) {
- if (!Utils.PROXY_TUNNEL_FILTER
- .test(hdrname, List.of(value))) {
+ if (!Utils.PROXY_TUNNEL_FILTER.test(hdrname, value)) {
Log.logError("{0} disabled", hdrname);
return;
}
} else if (r.proxy() != null) {
- if (!Utils.PROXY_FILTER
- .test(hdrname, List.of(value))) {
+ if (!Utils.PROXY_FILTER.test(hdrname, value)) {
Log.logError("{0} disabled", hdrname);
return;
}
--- a/src/java.net.http/share/classes/jdk/internal/net/http/CookieFilter.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/CookieFilter.java Fri May 25 16:13:11 2018 +0100
@@ -31,7 +31,7 @@
import java.util.Map;
import java.util.Optional;
import java.net.http.HttpHeaders;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import jdk.internal.net.http.common.Log;
import jdk.internal.net.http.common.Utils;
@@ -50,7 +50,7 @@
Map<String,List<String>> cookies = cookieHandler.get(r.uri(), userheaders);
// add the returned cookies
- HttpHeadersImpl systemHeaders = r.getSystemHeaders();
+ HttpHeadersBuilder systemHeadersBuilder = r.getSystemHeadersBuilder();
if (cookies.isEmpty()) {
Log.logTrace("Request: no cookie to add for {0}", r.uri());
} else {
@@ -65,7 +65,7 @@
if (values == null || values.isEmpty()) continue;
for (String val : values) {
if (Utils.isValidValue(val)) {
- systemHeaders.addHeader(hdrname, val);
+ systemHeadersBuilder.addHeader(hdrname, val);
}
}
}
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java Fri May 25 16:13:11 2018 +0100
@@ -35,6 +35,7 @@
import java.net.http.HttpHeaders;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
+import static jdk.internal.net.http.common.Utils.ACCEPT_ALL;
class Http1HeaderParser {
@@ -194,7 +195,7 @@
assert sb.length() == 0;
char c = state == State.STATUS_LINE_END_LF ? LF : (char)input.get();
if (c == LF) {
- headers = ImmutableHeaders.of(privateMap);
+ headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
privateMap = null;
state = State.FINISHED; // no headers
} else {
@@ -268,7 +269,7 @@
state = State.HEADER_FOUND_CR_LF_CR;
} else {
state = State.FINISHED;
- headers = ImmutableHeaders.of(privateMap);
+ headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
privateMap = null;
}
} else if (c == SP || c == HT) {
@@ -293,7 +294,7 @@
char c = (char)input.get();
if (c == LF) {
state = State.FINISHED;
- headers = ImmutableHeaders.of(privateMap);
+ headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
privateMap = null;
} else {
throw protocolException("Unexpected \"%s\", after CR LF CR", c);
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java Fri May 25 16:13:11 2018 +0100
@@ -26,7 +26,6 @@
package jdk.internal.net.http;
import java.io.IOException;
-import java.lang.System.Logger.Level;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -39,7 +38,7 @@
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import jdk.internal.net.http.Http1Exchange.Http1BodySubscriber;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import jdk.internal.net.http.common.Log;
import jdk.internal.net.http.common.Logger;
import jdk.internal.net.http.common.Utils;
@@ -52,7 +51,7 @@
class Http1Request {
private static final String COOKIE_HEADER = "Cookie";
- private static final BiPredicate<String,List<String>> NOCOOKIES =
+ private static final BiPredicate<String,String> NOCOOKIES =
(k,v) -> !COOKIE_HEADER.equalsIgnoreCase(k);
private final HttpRequestImpl request;
@@ -60,7 +59,7 @@
private final HttpConnection connection;
private final HttpRequest.BodyPublisher requestPublisher;
private final HttpHeaders userHeaders;
- private final HttpHeadersImpl systemHeaders;
+ private final HttpHeadersBuilder systemHeadersBuilder;
private volatile boolean streaming;
private volatile long contentLength;
@@ -73,7 +72,7 @@
this.connection = http1Exchange.connection();
this.requestPublisher = request.requestPublisher; // may be null
this.userHeaders = request.getUserHeaders();
- this.systemHeaders = request.getSystemHeaders();
+ this.systemHeadersBuilder = request.getSystemHeadersBuilder();
}
private void logHeaders(String completeHeaders) {
@@ -92,12 +91,13 @@
private void collectHeaders0(StringBuilder sb) {
- BiPredicate<String,List<String>> filter =
+ BiPredicate<String,String> filter =
connection.headerFilter(request);
// Filter out 'Cookie:' headers, we will collect them at the end.
- BiPredicate<String,List<String>> nocookies =
- NOCOOKIES.and(filter);
+ BiPredicate<String,String> nocookies = NOCOOKIES.and(filter);
+
+ HttpHeaders systemHeaders = systemHeadersBuilder.build();
// If we're sending this request through a tunnel,
// then don't send any preemptive proxy-* headers that
@@ -112,8 +112,7 @@
// Gather all 'Cookie:' headers and concatenate their
// values in a single line.
- collectCookies(sb, COOKIE_HEADER,
- systemHeaders, userHeaders, filter);
+ collectCookies(sb, systemHeaders, userHeaders);
// terminate headers
sb.append('\r').append('\n');
@@ -142,20 +141,16 @@
// any illegal character for header field values.
//
private void collectCookies(StringBuilder sb,
- String key,
HttpHeaders system,
- HttpHeaders user,
- BiPredicate<String, List<String>> filter) {
- List<String> systemList = system.allValues(key);
- if (systemList != null && !filter.test(key, systemList)) systemList = null;
- List<String> userList = user.allValues(key);
- if (userList != null && !filter.test(key, userList)) userList = null;
+ HttpHeaders user) {
+ List<String> systemList = system.allValues(COOKIE_HEADER);
+ List<String> userList = user.allValues(COOKIE_HEADER);
boolean found = false;
if (systemList != null) {
for (String cookie : systemList) {
if (!found) {
found = true;
- sb.append(key).append(':').append(' ');
+ sb.append(COOKIE_HEADER).append(':').append(' ');
} else {
sb.append(';').append(' ');
}
@@ -166,7 +161,7 @@
for (String cookie : userList) {
if (!found) {
found = true;
- sb.append(key).append(':').append(' ');
+ sb.append(COOKIE_HEADER).append(':').append(' ');
} else {
sb.append(';').append(' ');
}
@@ -176,13 +171,15 @@
if (found) sb.append('\r').append('\n');
}
- private void collectHeaders1(StringBuilder sb, HttpHeaders headers,
- BiPredicate<String, List<String>> filter) {
+ private void collectHeaders1(StringBuilder sb,
+ HttpHeaders headers,
+ BiPredicate<String,String> filter) {
for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {
String key = entry.getKey();
List<String> values = entry.getValue();
- if (!filter.test(key, values)) continue;
for (String value : values) {
+ if (!filter.test(key, value))
+ continue;
sb.append(key).append(':').append(' ')
.append(value)
.append('\r').append('\n');
@@ -279,7 +276,7 @@
URI uri = request.uri();
if (uri != null) {
- systemHeaders.setHeader("Host", hostString());
+ systemHeadersBuilder.setHeader("Host", hostString());
}
if (requestPublisher == null) {
// Not a user request, or maybe a method, e.g. GET, with no body.
@@ -289,13 +286,13 @@
}
if (contentLength == 0) {
- systemHeaders.setHeader("Content-Length", "0");
+ systemHeadersBuilder.setHeader("Content-Length", "0");
} else if (contentLength > 0) {
- systemHeaders.setHeader("Content-Length", Long.toString(contentLength));
+ systemHeadersBuilder.setHeader("Content-Length", Long.toString(contentLength));
streaming = false;
} else {
streaming = true;
- systemHeaders.setHeader("Transfer-encoding", "chunked");
+ systemHeadersBuilder.setHeader("Transfer-encoding", "chunked");
}
collectHeaders0(sb);
String hs = sb.toString();
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java Fri May 25 16:13:11 2018 +0100
@@ -28,7 +28,6 @@
import java.io.EOFException;
import java.io.IOException;
import java.io.UncheckedIOException;
-import java.lang.System.Logger.Level;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
@@ -54,7 +53,7 @@
import jdk.internal.net.http.HttpConnection.HttpPublisher;
import jdk.internal.net.http.common.FlowTube;
import jdk.internal.net.http.common.FlowTube.TubeSubscriber;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import jdk.internal.net.http.common.Log;
import jdk.internal.net.http.common.Logger;
import jdk.internal.net.http.common.MinimalFuture;
@@ -82,7 +81,6 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static jdk.internal.net.http.frame.SettingsFrame.*;
-
/**
* An Http2Connection. Encapsulates the socket(channel) and any SSLEngine used
* over it. Contains an HttpConnection which hides the SocketChannel SSL stuff.
@@ -792,7 +790,7 @@
nextPushStream += 2;
}
- HttpHeadersImpl headers = decoder.headers();
+ HttpHeaders headers = decoder.headers();
HttpRequestImpl pushReq = HttpRequestImpl.createPushRequest(parentReq, headers);
Exchange<T> pushExch = new Exchange<>(pushReq, parent.exchange.multi);
Stream.PushedStream<T> pushStream = createPushStream(parent, pushExch);
@@ -1303,10 +1301,10 @@
static class HeaderDecoder extends ValidatingHeadersConsumer {
- HttpHeadersImpl headers;
+ HttpHeadersBuilder headersBuilder;
HeaderDecoder() {
- this.headers = new HttpHeadersImpl();
+ this.headersBuilder = new HttpHeadersBuilder();
}
@Override
@@ -1314,11 +1312,11 @@
String n = name.toString();
String v = value.toString();
super.onDecoded(n, v);
- headers.addHeader(n, v);
+ headersBuilder.addHeader(n, v);
}
- HttpHeadersImpl headers() {
- return headers;
+ HttpHeaders headers() {
+ return headersBuilder.build();
}
}
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java Fri May 25 16:13:11 2018 +0100
@@ -27,7 +27,6 @@
import java.io.Closeable;
import java.io.IOException;
-import java.lang.System.Logger.Level;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
@@ -250,7 +249,7 @@
* @param request
* @return
*/
- BiPredicate<String,List<String>> headerFilter(HttpRequestImpl request) {
+ BiPredicate<String,String> headerFilter(HttpRequestImpl request) {
if (isTunnel()) {
// talking to a server through a proxy tunnel
// don't send proxy-* headers to a plain server
@@ -280,12 +279,12 @@
// start with "proxy-"
private static HttpHeaders proxyTunnelHeaders(HttpRequestImpl request) {
Map<String, List<String>> combined = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- combined.putAll(request.getSystemHeaders().map());
+ combined.putAll(request.getSystemHeadersBuilder().map());
combined.putAll(request.headers().map()); // let user override system
// keep only proxy-* - and also strip authorization headers
// for disabled schemes
- return ImmutableHeaders.of(combined, Utils.PROXY_TUNNEL_FILTER);
+ return HttpHeaders.of(combined, Utils.PROXY_TUNNEL_FILTER);
}
/* Returns either a plain HTTP connection or a plain tunnelling connection
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java Fri May 25 16:13:11 2018 +0100
@@ -32,9 +32,9 @@
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import jdk.internal.net.http.common.Utils;
-import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static jdk.internal.net.http.common.Utils.isValidName;
import static jdk.internal.net.http.common.Utils.isValidValue;
@@ -42,7 +42,7 @@
public class HttpRequestBuilderImpl implements HttpRequest.Builder {
- private HttpHeadersImpl userHeaders;
+ private HttpHeadersBuilder headersBuilder;
private URI uri;
private String method;
private boolean expectContinue;
@@ -54,13 +54,13 @@
requireNonNull(uri, "uri must be non-null");
checkURI(uri);
this.uri = uri;
- this.userHeaders = new HttpHeadersImpl();
+ this.headersBuilder = new HttpHeadersBuilder();
this.method = "GET"; // default, as per spec
this.version = Optional.empty();
}
public HttpRequestBuilderImpl() {
- this.userHeaders = new HttpHeadersImpl();
+ this.headersBuilder = new HttpHeadersBuilder();
this.method = "GET"; // default, as per spec
this.version = Optional.empty();
}
@@ -90,7 +90,7 @@
public HttpRequestBuilderImpl copy() {
HttpRequestBuilderImpl b = new HttpRequestBuilderImpl();
b.uri = this.uri;
- b.userHeaders = this.userHeaders.deepCopy();
+ b.headersBuilder = this.headersBuilder.structuralCopy();
b.method = this.method;
b.expectContinue = this.expectContinue;
b.bodyPublisher = bodyPublisher;
@@ -106,7 +106,7 @@
if (!isValidName(name)) {
throw newIAE("invalid header name: \"%s\"", name);
}
- if (!Utils.ALLOWED_HEADERS.test(name)) {
+ if (!Utils.ALLOWED_HEADERS.test(name, null)) {
throw newIAE("restricted header name: \"%s\"", name);
}
if (!isValidValue(value)) {
@@ -117,14 +117,14 @@
@Override
public HttpRequestBuilderImpl setHeader(String name, String value) {
checkNameAndValue(name, value);
- userHeaders.setHeader(name, value);
+ headersBuilder.setHeader(name, value);
return this;
}
@Override
public HttpRequestBuilderImpl header(String name, String value) {
checkNameAndValue(name, value);
- userHeaders.addHeader(name, value);
+ headersBuilder.addHeader(name, value);
return this;
}
@@ -155,7 +155,7 @@
return this;
}
- HttpHeadersImpl headers() { return userHeaders; }
+ HttpHeadersBuilder headersBuilder() { return headersBuilder; }
URI uri() { return uri; }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java Fri May 25 16:13:11 2018 +0100
@@ -41,7 +41,7 @@
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import jdk.internal.net.http.common.Utils;
import jdk.internal.net.http.websocket.WebSocketRequest;
@@ -50,7 +50,7 @@
class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
private final HttpHeaders userHeaders;
- private final HttpHeadersImpl systemHeaders;
+ private final HttpHeadersBuilder systemHeadersBuilder;
private final URI uri;
private volatile Proxy proxy; // ensure safe publishing
private final InetSocketAddress authority; // only used when URI not specified
@@ -78,8 +78,8 @@
public HttpRequestImpl(HttpRequestBuilderImpl builder) {
String method = builder.method();
this.method = method == null ? "GET" : method;
- this.userHeaders = ImmutableHeaders.of(builder.headers().map(), ALLOWED_HEADERS);
- this.systemHeaders = new HttpHeadersImpl();
+ this.userHeaders = HttpHeaders.of(builder.headersBuilder().map(), ALLOWED_HEADERS);
+ this.systemHeadersBuilder = new HttpHeadersBuilder();
this.uri = builder.uri();
assert uri != null;
this.proxy = null;
@@ -106,21 +106,21 @@
"uri must be non null");
Duration timeout = request.timeout().orElse(null);
this.method = method == null ? "GET" : method;
- this.userHeaders = ImmutableHeaders.validate(request.headers());
+ this.userHeaders = HttpHeaders.of(request.headers().map(), Utils.VALIDATE_USER_HEADER);
if (request instanceof HttpRequestImpl) {
// all cases exception WebSocket should have a new system headers
this.isWebSocket = ((HttpRequestImpl) request).isWebSocket;
if (isWebSocket) {
- this.systemHeaders = ((HttpRequestImpl) request).systemHeaders;
+ this.systemHeadersBuilder = ((HttpRequestImpl)request).systemHeadersBuilder;
} else {
- this.systemHeaders = new HttpHeadersImpl();
+ this.systemHeadersBuilder = new HttpHeadersBuilder();
}
} else {
HttpRequestBuilderImpl.checkURI(requestURI);
checkTimeout(timeout);
- this.systemHeaders = new HttpHeadersImpl();
+ this.systemHeadersBuilder = new HttpHeadersBuilder();
}
- this.systemHeaders.setHeader("User-Agent", USER_AGENT);
+ this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT);
this.uri = requestURI;
if (isWebSocket) {
// WebSocket determines and sets the proxy itself
@@ -169,8 +169,8 @@
this.method = method == null? "GET" : method;
this.userHeaders = other.userHeaders;
this.isWebSocket = other.isWebSocket;
- this.systemHeaders = new HttpHeadersImpl();
- this.systemHeaders.setHeader("User-Agent", USER_AGENT);
+ this.systemHeadersBuilder = new HttpHeadersBuilder();
+ this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT);
this.uri = uri;
this.proxy = other.proxy;
this.expectContinue = other.expectContinue;
@@ -189,8 +189,8 @@
// to the connection pool (we might need to revisit this constructor later)
assert "CONNECT".equalsIgnoreCase(method);
this.method = method;
- this.systemHeaders = new HttpHeadersImpl();
- this.userHeaders = ImmutableHeaders.of(headers);
+ this.systemHeadersBuilder = new HttpHeadersBuilder();
+ this.userHeaders = headers;
this.uri = URI.create("socket://" + authority.getHostString() + ":"
+ Integer.toString(authority.getPort()) + "/");
this.proxy = null;
@@ -218,14 +218,14 @@
* parent.
*/
static HttpRequestImpl createPushRequest(HttpRequestImpl parent,
- HttpHeadersImpl headers)
+ HttpHeaders headers)
throws IOException
{
return new HttpRequestImpl(parent, headers);
}
// only used for push requests
- private HttpRequestImpl(HttpRequestImpl parent, HttpHeadersImpl headers)
+ private HttpRequestImpl(HttpRequestImpl parent, HttpHeaders headers)
throws IOException
{
this.method = headers.firstValue(":method")
@@ -240,8 +240,8 @@
sb.append(scheme).append("://").append(authority).append(path);
this.uri = URI.create(sb.toString());
this.proxy = null;
- this.userHeaders = ImmutableHeaders.of(headers.map(), ALLOWED_HEADERS);
- this.systemHeaders = parent.systemHeaders;
+ this.userHeaders = HttpHeaders.of(headers.map(), ALLOWED_HEADERS);
+ this.systemHeadersBuilder = parent.systemHeadersBuilder;
this.expectContinue = parent.expectContinue;
this.secure = parent.secure;
this.requestPublisher = parent.requestPublisher;
@@ -264,9 +264,9 @@
InetSocketAddress authority() { return authority; }
void setH2Upgrade(Http2ClientImpl h2client) {
- systemHeaders.setHeader("Connection", "Upgrade, HTTP2-Settings");
- systemHeaders.setHeader("Upgrade", "h2c");
- systemHeaders.setHeader("HTTP2-Settings", h2client.getSettingsString());
+ systemHeadersBuilder.setHeader("Connection", "Upgrade, HTTP2-Settings");
+ systemHeadersBuilder.setHeader("Upgrade", "h2c");
+ systemHeadersBuilder.setHeader("HTTP2-Settings", h2client.getSettingsString());
}
@Override
@@ -332,18 +332,18 @@
HttpHeaders getUserHeaders() { return userHeaders; }
- HttpHeadersImpl getSystemHeaders() { return systemHeaders; }
+ HttpHeadersBuilder getSystemHeadersBuilder() { return systemHeadersBuilder; }
@Override
public Optional<HttpClient.Version> version() { return version; }
void addSystemHeader(String name, String value) {
- systemHeaders.addHeader(name, value);
+ systemHeadersBuilder.addHeader(name, value);
}
@Override
public void setSystemHeader(String name, String value) {
- systemHeaders.setHeader(name, value);
+ systemHeadersBuilder.setHeader(name, value);
}
InetSocketAddress getAddress() {
--- a/src/java.net.http/share/classes/jdk/internal/net/http/ImmutableHeaders.java Mon May 28 17:22:37 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package jdk.internal.net.http;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.TreeMap;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
-import java.net.http.HttpHeaders;
-import jdk.internal.net.http.common.HttpHeadersImpl;
-import jdk.internal.net.http.common.Utils;
-import static java.util.Collections.emptyMap;
-import static java.util.Collections.unmodifiableList;
-import static java.util.Collections.unmodifiableMap;
-import static java.util.Objects.requireNonNull;
-
-final class ImmutableHeaders extends HttpHeaders {
-
- private final Map<String, List<String>> map;
-
- public static ImmutableHeaders empty() {
- return of(emptyMap());
- }
-
- public static ImmutableHeaders of(Map<String, List<String>> src) {
- return of(src, x -> true);
- }
-
- public static ImmutableHeaders of(HttpHeaders headers) {
- return (headers instanceof ImmutableHeaders)
- ? (ImmutableHeaders)headers
- : of(headers.map());
- }
-
- static ImmutableHeaders validate(HttpHeaders headers) {
- if (headers instanceof ImmutableHeaders) {
- return of(headers);
- }
- if (headers instanceof HttpHeadersImpl) {
- return of(headers);
- }
- Map<String, List<String>> map = headers.map();
- return new ImmutableHeaders(map, Utils.VALIDATE_USER_HEADER);
- }
-
- public static ImmutableHeaders of(Map<String, List<String>> src,
- Predicate<? super String> keyAllowed) {
- requireNonNull(src, "src");
- requireNonNull(keyAllowed, "keyAllowed");
- return new ImmutableHeaders(src, headerAllowed(keyAllowed));
- }
-
- public static ImmutableHeaders of(Map<String, List<String>> src,
- BiPredicate<? super String, ? super List<String>> headerAllowed) {
- requireNonNull(src, "src");
- requireNonNull(headerAllowed, "headerAllowed");
- return new ImmutableHeaders(src, headerAllowed);
- }
-
- private ImmutableHeaders(Map<String, List<String>> src,
- BiPredicate<? super String, ? super List<String>> headerAllowed) {
- Map<String, List<String>> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- src.entrySet().stream()
- .forEach(e -> addIfAllowed(e, headerAllowed, m));
- this.map = unmodifiableMap(m);
- }
-
- private static void addIfAllowed(Map.Entry<String, List<String>> e,
- BiPredicate<? super String, ? super List<String>> headerAllowed,
- Map<String, List<String>> map) {
- String key = e.getKey();
- List<String> values = unmodifiableValues(e.getValue());
- if (headerAllowed.test(key, values)) {
- map.put(key, values);
- }
- }
-
- private static List<String> unmodifiableValues(List<String> values) {
- return unmodifiableList(new ArrayList<>(Objects.requireNonNull(values)));
- }
-
- private static BiPredicate<String, List<String>> headerAllowed(Predicate<? super String> keyAllowed) {
- return (n,v) -> keyAllowed.test(n);
- }
-
- @Override
- public Map<String, List<String>> map() {
- return map;
- }
-}
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Response.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Response.java Fri May 25 16:13:11 2018 +0100
@@ -64,7 +64,7 @@
int statusCode,
HttpClient.Version version,
boolean isConnectResponse) {
- this.headers = ImmutableHeaders.of(headers);
+ this.headers = headers;
this.request = req;
this.version = version;
this.exchange = exchange;
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java Fri May 25 16:13:11 2018 +0100
@@ -115,8 +115,8 @@
final Http2Connection connection;
final HttpRequestImpl request;
final HeadersConsumer rspHeadersConsumer;
- final HttpHeadersImpl responseHeaders;
- final HttpHeadersImpl requestPseudoHeaders;
+ final HttpHeadersBuilder responseHeadersBuilder;
+ final HttpHeaders requestPseudoHeaders;
volatile HttpResponse.BodySubscriber<T> responseSubscriber;
final HttpRequest.BodyPublisher requestPublisher;
volatile RequestSubscriber requestSubscriber;
@@ -346,10 +346,9 @@
this.windowController = windowController;
this.request = e.request();
this.requestPublisher = request.requestPublisher; // may be null
- responseHeaders = new HttpHeadersImpl();
- rspHeadersConsumer = new HeadersConsumer();
- this.requestPseudoHeaders = new HttpHeadersImpl();
- // NEW
+ this.responseHeadersBuilder = new HttpHeadersBuilder();
+ this.rspHeadersConsumer = new HeadersConsumer();
+ this.requestPseudoHeaders = createPseudoHeaders(request);
this.windowUpdater = new StreamWindowUpdateSender(connection);
}
@@ -400,6 +399,7 @@
}
protected void handleResponse() throws IOException {
+ HttpHeaders responseHeaders = responseHeadersBuilder.build();
responseCode = (int)responseHeaders
.firstValueAsLong(":status")
.orElseThrow(() -> new IOException("no statuscode in response"));
@@ -576,13 +576,12 @@
}
private OutgoingHeaders<Stream<T>> headerFrame(long contentLength) {
- HttpHeadersImpl h = request.getSystemHeaders();
+ HttpHeadersBuilder h = request.getSystemHeadersBuilder();
if (contentLength > 0) {
h.setHeader("content-length", Long.toString(contentLength));
}
- setPseudoHeaderFields();
- HttpHeaders sysh = filter(h);
- HttpHeaders userh = filter(request.getUserHeaders());
+ HttpHeaders sysh = filterHeaders(h.build());
+ HttpHeaders userh = filterHeaders(request.getUserHeaders());
OutgoingHeaders<Stream<T>> f = new OutgoingHeaders<>(sysh, userh, this);
if (contentLength == 0) {
f.setFlag(HeadersFrame.END_STREAM);
@@ -606,7 +605,7 @@
// If nothing needs filtering then we can just use the
// original headers.
private boolean needsFiltering(HttpHeaders headers,
- BiPredicate<String, List<String>> filter) {
+ BiPredicate<String, String> filter) {
if (filter == Utils.PROXY_TUNNEL_FILTER || filter == Utils.PROXY_FILTER) {
// we're either connecting or proxying
// slight optimization: we only need to filter out
@@ -624,18 +623,17 @@
}
}
- private HttpHeaders filter(HttpHeaders headers) {
+ private HttpHeaders filterHeaders(HttpHeaders headers) {
HttpConnection conn = connection();
- BiPredicate<String, List<String>> filter =
- conn.headerFilter(request);
+ BiPredicate<String, String> filter = conn.headerFilter(request);
if (needsFiltering(headers, filter)) {
- return ImmutableHeaders.of(headers.map(), filter);
+ return HttpHeaders.of(headers.map(), filter);
}
return headers;
}
- private void setPseudoHeaderFields() {
- HttpHeadersImpl hdrs = requestPseudoHeaders;
+ private static HttpHeaders createPseudoHeaders(HttpRequest request) {
+ HttpHeadersBuilder hdrs = new HttpHeadersBuilder();
String method = request.method();
hdrs.setHeader(":method", method);
URI uri = request.uri();
@@ -656,9 +654,10 @@
path += "?" + query;
}
hdrs.setHeader(":path", Utils.encode(path));
+ return hdrs.build();
}
- HttpHeadersImpl getRequestPseudoHeaders() {
+ HttpHeaders getRequestPseudoHeaders() {
return requestPseudoHeaders;
}
@@ -1242,6 +1241,7 @@
// create and return the PushResponseImpl
@Override
protected void handleResponse() {
+ HttpHeaders responseHeaders = responseHeadersBuilder.build();
responseCode = (int)responseHeaders
.firstValueAsLong(":status")
.orElse(-1);
@@ -1310,17 +1310,17 @@
void reset() {
super.reset();
- responseHeaders.clear();
+ responseHeadersBuilder.clear();
}
@Override
public void onDecoded(CharSequence name, CharSequence value)
- throws UncheckedIOException
+ throws UncheckedIOException
{
String n = name.toString();
String v = value.toString();
super.onDecoded(n, v);
- responseHeaders.addHeader(n, v);
+ responseHeadersBuilder.addHeader(n, v);
if (Log.headers() && Log.trace()) {
Log.logTrace("RECEIVED HEADER (streamid={0}): {1}: {2}",
streamid, n, v);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/HttpHeadersBuilder.java Fri May 25 16:13:11 2018 +0100
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2018, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package jdk.internal.net.http.common;
+
+import java.net.http.HttpHeaders;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import static jdk.internal.net.http.common.Utils.ACCEPT_ALL;
+
+/** A mutable builder for collecting and building HTTP headers. */
+public class HttpHeadersBuilder {
+
+ private final TreeMap<String, List<String>> headersMap;
+
+ public HttpHeadersBuilder() {
+ headersMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+
+ public HttpHeadersBuilder structuralCopy() {
+ HttpHeadersBuilder builder = new HttpHeadersBuilder();
+ for (Map.Entry<String, List<String>> entry : headersMap.entrySet()) {
+ List<String> valuesCopy = new ArrayList<>(entry.getValue());
+ builder.headersMap.put(entry.getKey(), valuesCopy);
+ }
+ return builder;
+ }
+
+ public void addHeader(String name, String value) {
+ headersMap.computeIfAbsent(name, k -> new ArrayList<>(1))
+ .add(value);
+ }
+
+ public void setHeader(String name, String value) {
+ // headers typically have one value
+ List<String> values = new ArrayList<>(1);
+ values.add(value);
+ headersMap.put(name, values);
+ }
+
+ public void clear() {
+ headersMap.clear();
+ }
+
+ public Map<String, List<String>> map() {
+ return headersMap;
+ }
+
+ public HttpHeaders build() {
+ return HttpHeaders.of(headersMap, ACCEPT_ALL);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString()).append(" { ");
+ sb.append(map());
+ sb.append(" }");
+ return sb.toString();
+ }
+}
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/HttpHeadersImpl.java Mon May 28 17:22:37 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package jdk.internal.net.http.common;
-
-import java.net.http.HttpHeaders;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * Implementation of HttpHeaders.
- *
- * The public HttpHeaders API provides a read-only view, while the
- * non-HttpHeaders members allow for implementation specific mutation, e.g.
- * during creation, etc.
- */
-public class HttpHeadersImpl extends HttpHeaders {
-
- private final TreeMap<String, List<String>> headers;
-
- public HttpHeadersImpl() {
- headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- }
-
- @Override
- public Map<String, List<String>> map() {
- return Collections.unmodifiableMap(headersMap());
- }
-
- // non-HttpHeaders private mutators
-
- public HttpHeadersImpl deepCopy() {
- HttpHeadersImpl h1 = newDeepCopy();
- for (Map.Entry<String, List<String>> entry : headersMap().entrySet()) {
- List<String> valuesCopy = new ArrayList<>(entry.getValue());
- h1.headersMap().put(entry.getKey(), valuesCopy);
- }
- return h1;
- }
-
- public void addHeader(String name, String value) {
- headersMap().computeIfAbsent(name, k -> new ArrayList<>(1))
- .add(value);
- }
-
- public void setHeader(String name, String value) {
- // headers typically have one value
- List<String> values = new ArrayList<>(1);
- values.add(value);
- headersMap().put(name, values);
- }
-
- public void clear() {
- headersMap().clear();
- }
-
- protected HttpHeadersImpl newDeepCopy() {
- return new HttpHeadersImpl();
- }
-
- protected Map<String, List<String>> headersMap() {
- return headers;
- }
-}
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java Mon May 28 17:22:37 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java Fri May 25 16:13:11 2018 +0100
@@ -119,6 +119,8 @@
"jdk.httpclient.bufsize", DEFAULT_BUFSIZE
);
+ public static final BiPredicate<String,String> ACCEPT_ALL = (x,y) -> true;
+
private static final Set<String> DISALLOWED_HEADERS_SET;
static {
@@ -131,24 +133,21 @@
DISALLOWED_HEADERS_SET = Collections.unmodifiableSet(treeSet);
}
- public static final Predicate<String>
- ALLOWED_HEADERS = header -> !DISALLOWED_HEADERS_SET.contains(header);
+ public static final BiPredicate<String, String>
+ ALLOWED_HEADERS = (header, unused) -> !DISALLOWED_HEADERS_SET.contains(header);
- public static final BiPredicate<String, List<String>> VALIDATE_USER_HEADER =
- (name, lv) -> {
- requireNonNull(name, "header name");
- requireNonNull(lv, "header values");
+ public static final BiPredicate<String, String> VALIDATE_USER_HEADER =
+ (name, value) -> {
+ assert name != null : "null header name";
+ assert value != null : "null header value";
if (!isValidName(name)) {
throw newIAE("invalid header name: \"%s\"", name);
}
- if (!Utils.ALLOWED_HEADERS.test(name)) {
+ if (!Utils.ALLOWED_HEADERS.test(name, null)) {
throw newIAE("restricted header name: \"%s\"", name);
}
- for (String value : lv) {
- requireNonNull(value, "header value");
- if (!isValidValue(value)) {
- throw newIAE("invalid header value for %s: \"%s\"", name, value);
- }
+ if (!isValidValue(value)) {
+ throw newIAE("invalid header value for %s: \"%s\"", name, value);
}
return true;
};
@@ -182,7 +181,7 @@
private static final String WSPACES = " \t\r\n";
private static final boolean isAllowedForProxy(String name,
- List<String> value,
+ String value,
Set<String> disabledSchemes,
Predicate<String> allowedKeys) {
if (!allowedKeys.test(name)) return false;
@@ -191,21 +190,19 @@
if (value.isEmpty()) return false;
for (String scheme : disabledSchemes) {
int slen = scheme.length();
- for (String v : value) {
- int vlen = v.length();
- if (vlen == slen) {
- if (v.equalsIgnoreCase(scheme)) {
+ int vlen = value.length();
+ if (vlen == slen) {
+ if (value.equalsIgnoreCase(scheme)) {
+ return false;
+ }
+ } else if (vlen > slen) {
+ if (value.substring(0,slen).equalsIgnoreCase(scheme)) {
+ int c = value.codePointAt(slen);
+ if (WSPACES.indexOf(c) > -1
+ || Character.isSpaceChar(c)
+ || Character.isWhitespace(c)) {
return false;
}
- } else if (vlen > slen) {
- if (v.substring(0,slen).equalsIgnoreCase(scheme)) {
- int c = v.codePointAt(slen);
- if (WSPACES.indexOf(c) > -1
- || Character.isSpaceChar(c)
- || Character.isWhitespace(c)) {
- return false;
- }
- }
}
}
}
@@ -213,13 +210,13 @@
return true;
}
- public static final BiPredicate<String, List<String>> PROXY_TUNNEL_FILTER =
+ public static final BiPredicate<String, String> PROXY_TUNNEL_FILTER =
(s,v) -> isAllowedForProxy(s, v, PROXY_AUTH_TUNNEL_DISABLED_SCHEMES,
IS_PROXY_HEADER);
- public static final BiPredicate<String, List<String>> PROXY_FILTER =
+ public static final BiPredicate<String, String> PROXY_FILTER =
(s,v) -> isAllowedForProxy(s, v, PROXY_AUTH_DISABLED_SCHEMES,
ALL_HEADERS);
- public static final BiPredicate<String, List<String>> NO_PROXY_HEADERS_FILTER =
+ public static final BiPredicate<String, String> NO_PROXY_HEADERS_FILTER =
(n,v) -> Utils.NO_PROXY_HEADER.test(n);
--- a/test/jdk/java/net/httpclient/BodyProcessorInputStreamTest.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/BodyProcessorInputStreamTest.java Fri May 25 16:13:11 2018 +0100
@@ -41,7 +41,7 @@
* @test
* @bug 8187503
* @summary An example on how to read a response body with InputStream.
- * @run main/manual -Dtest.debug=true BodyProcessorInputStreamTest
+ * @run main/othervm/manual -Dtest.debug=true BodyProcessorInputStreamTest
* @author daniel fuchs
*/
public class BodyProcessorInputStreamTest {
--- a/test/jdk/java/net/httpclient/DependentPromiseActionsTest.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/DependentPromiseActionsTest.java Fri May 25 16:13:11 2018 +0100
@@ -40,7 +40,6 @@
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.StackWalker.StackFrame;
-import jdk.internal.net.http.common.HttpHeadersImpl;
import jdk.testlibrary.SimpleSSLContext;
import org.testng.annotations.AfterTest;
import org.testng.annotations.AfterClass;
@@ -79,6 +78,7 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -690,8 +690,13 @@
https2TestServer.stop();
}
- private static void pushPromiseFor(HttpTestExchange t, URI requestURI, String pushPath, boolean fixed)
- throws IOException
+ static final BiPredicate<String,String> ACCEPT_ALL = (x, y) -> true;
+
+ private static void pushPromiseFor(HttpTestExchange t,
+ URI requestURI,
+ String pushPath,
+ boolean fixed)
+ throws IOException
{
try {
URI promise = new URI(requestURI.getScheme(),
@@ -700,9 +705,13 @@
byte[] promiseBytes = promise.toASCIIString().getBytes(UTF_8);
out.printf("TestServer: %s Pushing promise: %s%n", now(), promise);
err.printf("TestServer: %s Pushing promise: %s%n", now(), promise);
- HttpTestHeaders headers = HttpTestHeaders.of(new HttpHeadersImpl());
+ HttpHeaders headers;
if (fixed) {
- headers.addHeader("Content-length", String.valueOf(promiseBytes.length));
+ String length = String.valueOf(promiseBytes.length);
+ headers = HttpHeaders.of(Map.of("Content-Length", List.of(length)),
+ ACCEPT_ALL);
+ } else {
+ headers = HttpHeaders.of(Map.of(), ACCEPT_ALL); // empty
}
t.serverPush(promise, headers, promiseBytes);
} catch (URISyntaxException x) {
--- a/test/jdk/java/net/httpclient/DigestEchoServer.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/DigestEchoServer.java Fri May 25 16:13:11 2018 +0100
@@ -170,7 +170,7 @@
}
}
- private static String toString(HttpTestHeaders headers) {
+ private static String toString(HttpTestRequestHeaders headers) {
return headers.entrySet().stream()
.map((e) -> e.getKey() + ": " + e.getValue())
.collect(Collectors.joining("\n"));
@@ -847,9 +847,9 @@
}
public static String computeDigest(boolean isRequest,
- String reqMethod,
- char[] password,
- DigestResponse params)
+ String reqMethod,
+ char[] password,
+ DigestResponse params)
throws NoSuchAlgorithmException
{
@@ -970,11 +970,13 @@
@Override
protected void requestAuthentication(HttpTestExchange he)
- throws IOException {
- he.getResponseHeaders().addHeader(getAuthenticate(),
- "Basic realm=\"" + auth.getRealm() + "\"");
- System.out.println(type + ": Requesting Basic Authentication "
- + he.getResponseHeaders().firstValue(getAuthenticate()));
+ throws IOException
+ {
+ String headerName = getAuthenticate();
+ String headerValue = "Basic realm=\"" + auth.getRealm() + "\"";
+ he.getResponseHeaders().addHeader(headerName, headerValue);
+ System.out.println(type + ": Requesting Basic Authentication, "
+ + headerName + " : "+ headerValue);
}
@Override
@@ -1061,14 +1063,13 @@
} else {
throw new InternalError(String.valueOf(v));
}
- he.getResponseHeaders().addHeader(getAuthenticate(),
- "Digest realm=\"" + auth.getRealm() + "\","
- + separator + "qop=\"auth\","
- + separator + "nonce=\"" + ns +"\"");
- System.out.println(type + ": Requesting Digest Authentication "
- + he.getResponseHeaders()
- .firstValue(getAuthenticate())
- .orElse("null"));
+ String headerName = getAuthenticate();
+ String headerValue = "Digest realm=\"" + auth.getRealm() + "\","
+ + separator + "qop=\"auth\","
+ + separator + "nonce=\"" + ns +"\"";
+ he.getResponseHeaders().addHeader(headerName, headerValue);
+ System.out.println(type + ": Requesting Digest Authentication, "
+ + headerName + " : " + headerValue);
}
@Override
--- a/test/jdk/java/net/httpclient/HeadersTest.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/HeadersTest.java Fri May 25 16:13:11 2018 +0100
@@ -32,6 +32,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.function.BiPredicate;
import static java.net.http.HttpClient.Builder.NO_PROXY;
/**
@@ -41,20 +42,11 @@
*/
public class HeadersTest {
+ static final BiPredicate<String,String> ACCEPT_ALL = (x, y) -> true;
+
static final URI TEST_URI = URI.create("http://www.foo.com/");
static final HttpClient client = HttpClient.newBuilder().proxy(NO_PROXY).build();
- static final class HttpHeadersStub extends HttpHeaders {
- Map<String, List<String>> map;
- HttpHeadersStub(Map<String, List<String>> map) {
- this.map = map;
- }
- @Override
- public Map<String, List<String>> map() {
- return map;
- }
- }
-
static void bad(String name) throws Exception {
HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
try {
@@ -85,7 +77,7 @@
}
@Override public HttpHeaders headers() {
Map<String, List<String>> map = Map.of(name, List.of("foo"));
- return new HttpHeadersStub(map);
+ return HttpHeaders.of(map, ACCEPT_ALL);
}
};
client.send(req, HttpResponse.BodyHandlers.ofString());
@@ -127,7 +119,7 @@
}
@Override public HttpHeaders headers() {
Map<String, List<String>> map = Map.of("x-bad", List.of(value));
- return new HttpHeadersStub(map);
+ return HttpHeaders.of(map, ACCEPT_ALL);
}
};
client.send(req, HttpResponse.BodyHandlers.ofString());
@@ -170,7 +162,7 @@
@Override public HttpHeaders headers() {
Map<String, List<String>> map = new HashMap<>();
map.put(null, List.of("foo"));
- return new HttpHeadersStub(map);
+ return HttpHeaders.of(map, ACCEPT_ALL);
}
};
client.send(req, HttpResponse.BodyHandlers.ofString());
@@ -211,7 +203,7 @@
@Override public HttpHeaders headers() {
Map<String, List<String>> map = new HashMap<>();
map.put("x-bar", null);
- return new HttpHeadersStub(map);
+ return HttpHeaders.of(map, ACCEPT_ALL);
}
};
client.send(req, HttpResponse.BodyHandlers.ofString());
@@ -243,11 +235,10 @@
List<String> values = new ArrayList<>();
values.add("foo");
values.add(null);
- return new HttpHeadersStub(Map.of("x-bar", values));
+ return HttpHeaders.of(Map.of("x-bar", values), ACCEPT_ALL);
}
};
- client
- .send(req, HttpResponse.BodyHandlers.ofString());
+ client.send(req, HttpResponse.BodyHandlers.ofString());
throw new RuntimeException("Expected NPE for null header value");
} catch (NullPointerException expected) {
System.out.println("Got expected NPE: " + expected);
@@ -276,7 +267,7 @@
return Optional.empty();
}
@Override public HttpHeaders headers() {
- return new HttpHeadersStub(null);
+ return HttpHeaders.of(null, ACCEPT_ALL);
}
};
client.send(req, HttpResponse.BodyHandlers.ofString());
@@ -370,7 +361,7 @@
}
@Override public HttpHeaders headers() {
Map<String, List<String>> map = Map.of("x-good", List.of("foo"));
- return new HttpHeadersStub(map);
+ return HttpHeaders.of(map, ACCEPT_ALL);
}
};
client.send(req, HttpResponse.BodyHandlers.ofString());
@@ -416,7 +407,7 @@
}
@Override public HttpHeaders headers() {
Map<String, List<String>> map = Map.of("x-good", List.of("foo"));
- return new HttpHeadersStub(map);
+ return HttpHeaders.of(map, ACCEPT_ALL);
}
};
client.send(req, HttpResponse.BodyHandlers.ofString());
@@ -472,7 +463,7 @@
@Override
public HttpHeaders headers() {
Map<String, List<String>> map = Map.of("x-good", List.of("foo"));
- return new HttpHeadersStub(map);
+ return HttpHeaders.of(map, ACCEPT_ALL);
}
};
client.send(req, HttpResponse.BodyHandlers.ofString());
@@ -526,7 +517,7 @@
@Override
public HttpHeaders headers() {
Map<String, List<String>> map = Map.of("x-good", List.of("foo"));
- return new HttpHeadersStub(map);
+ return HttpHeaders.of(map, ACCEPT_ALL);
}
};
client.send(req, HttpResponse.BodyHandlers.ofString());
@@ -538,12 +529,10 @@
public static void main(String[] args) throws Exception {
bad("bad:header");
- bad("Foo\n");
good("X-Foo!");
good("Bar~");
good("x");
bad(" ");
- bad("Bar\r\n");
good("Hello#world");
good("Qwer#ert");
badValue("blah\r\n blah");
--- a/test/jdk/java/net/httpclient/HttpClientBuilderTest.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/HttpClientBuilderTest.java Fri May 25 16:13:11 2018 +0100
@@ -275,16 +275,7 @@
@Override public boolean expectContinue() { return false; }
@Override public URI uri() { return URI.create("http://foo.com/"); }
@Override public Optional<Version> version() { return Optional.empty(); }
- private final FixedHttpHeaders headers = new FixedHttpHeaders();
- @Override public HttpHeaders headers() { return headers; }
- public class FixedHttpHeaders extends HttpHeaders {
- private final Map<String, List<String>> map =
- new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- @Override
- public Map<String, List<String>> map() {
- return map;
- }
- }
+ @Override public HttpHeaders headers() { return HttpHeaders.of(Map.of(), (x, y) -> true); }
}
// ---
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/HttpHeadersOf.java Fri May 25 16:13:11 2018 +0100
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2018, 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 HttpHeaders.of factory method
+ * @run testng HttpHeadersOf
+ */
+
+import java.net.http.HttpHeaders;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiPredicate;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class HttpHeadersOf {
+
+ static final Class<NullPointerException> NPE = NullPointerException.class;
+ static final Class<NumberFormatException> NFE = NumberFormatException.class;
+ static final Class<UnsupportedOperationException> UOE = UnsupportedOperationException.class;
+
+ static final BiPredicate<String,String> ACCEPT_ALL =
+ new BiPredicate<>() {
+ @Override public boolean test(String name, String value) { return true; }
+ @Override public String toString() { return "ACCEPT_ALL"; }
+ };
+
+ static final BiPredicate<String,String> REJECT_ALL =
+ new BiPredicate<>() {
+ @Override public boolean test(String name, String value) { return false; }
+ @Override public String toString() { return "REJECT_ALL"; }
+ };
+
+ @DataProvider(name = "predicates")
+ public Object[][] predicates() {
+ return new Object[][] { { ACCEPT_ALL }, { REJECT_ALL } };
+ }
+
+ @Test(dataProvider = "predicates")
+ public void testNull(BiPredicate<String,String> filter) {
+ assertThrows(NPE, () -> HttpHeaders.of(null, null));
+ assertThrows(NPE, () -> HttpHeaders.of(null, filter));
+ assertThrows(NPE, () -> HttpHeaders.of(Map.of(), null));
+ assertThrows(NPE, () -> HttpHeaders.of(Map.of("name", List.of("value")), null));
+
+ // nulls in the Map
+ assertThrows(NPE, () -> HttpHeaders.of(Map.of(null, List.of("value)")), filter));
+ assertThrows(NPE, () -> HttpHeaders.of(Map.of("name", null), filter));
+ assertThrows(NPE, () -> HttpHeaders.of(Map.of("name", List.of(null)), filter));
+ assertThrows(NPE, () -> HttpHeaders.of(Map.of("name", List.of("aValue", null)), filter));
+ assertThrows(NPE, () -> HttpHeaders.of(Map.of("name", List.of(null, "aValue")), filter));
+ }
+
+
+ @DataProvider(name = "filterMaps")
+ public Object[][] filterMaps() {
+ List<Map<String, List<String>>> maps = List.of(
+ Map.of("A", List.of("B"), "X", List.of("Y", "Z")),
+ Map.of("A", List.of("B", "C"), "X", List.of("Y", "Z")),
+ Map.of("A", List.of("B", "C", "D"), "X", List.of("Y", "Z"))
+ );
+ return maps.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+ }
+
+ @Test(dataProvider = "filterMaps")
+ public void testFilter(Map<String,List<String>> map) {
+ HttpHeaders headers = HttpHeaders.of(map, REJECT_ALL);
+ assertEquals(headers.map().size(), 0);
+ assertFalse(headers.firstValue("A").isPresent());
+ assertEquals(headers.allValues("A").size(), 0);
+
+ headers = HttpHeaders.of(map, (name, value) -> {
+ if (name.equals("A")) return true; else return false; });
+ assertEquals(headers.map().size(), 1);
+ assertTrue(headers.firstValue("A").isPresent());
+ assertEquals(headers.allValues("A"), map.get("A"));
+ assertEquals(headers.allValues("A").size(), map.get("A").size());
+ assertFalse(headers.firstValue("X").isPresent());
+
+ headers = HttpHeaders.of(map, (name, value) -> {
+ if (name.equals("X")) return true; else return false; });
+ assertEquals(headers.map().size(), 1);
+ assertTrue(headers.firstValue("X").isPresent());
+ assertEquals(headers.allValues("X"), map.get("X"));
+ assertEquals(headers.allValues("X").size(), map.get("X").size());
+ assertFalse(headers.firstValue("A").isPresent());
+ }
+
+
+ @DataProvider(name = "mapValues")
+ public Object[][] mapValues() {
+ List<Map<String, List<String>>> maps = List.of(
+ Map.of("A", List.of("B")),
+ Map.of("A", List.of("B", "C")),
+ Map.of("A", List.of("B", "C", "D")),
+
+ Map.of("A", List.of("B"), "X", List.of("Y", "Z")),
+ Map.of("A", List.of("B", "C"), "X", List.of("Y", "Z")),
+ Map.of("A", List.of("B", "C", "D"), "X", List.of("Y", "Z")),
+
+ Map.of("A", List.of("B"), "X", List.of("Y", "Z")),
+ Map.of("A", List.of("B", "C"), "X", List.of("Y", "Z")),
+ Map.of("A", List.of("B", "C", "D"), "X", List.of("Y", "Z")),
+
+ Map.of("X", List.of("Y", "Z"), "A", List.of("B")),
+ Map.of("X", List.of("Y", "Z"), "A", List.of("B", "C")),
+ Map.of("X", List.of("Y", "Z"), "A", List.of("B", "C", "D"))
+ );
+ return maps.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+ }
+
+ @Test(dataProvider = "mapValues")
+ public void testMapValues(Map<String,List<String>> map) {
+ HttpHeaders headers = HttpHeaders.of(map, ACCEPT_ALL);
+
+ assertEquals(headers.map().size(), map.size());
+ assertTrue(headers.firstValue("A").isPresent());
+ assertTrue(headers.firstValue("a").isPresent());
+ assertEquals(headers.firstValue("A").get(), "B");
+ assertEquals(headers.firstValue("a").get(), "B");
+ assertEquals(headers.allValues("A"), map.get("A"));
+ assertEquals(headers.allValues("a"), map.get("A"));
+ assertEquals(headers.allValues("F").size(), 0);
+ assertTrue(headers.map().get("A").contains("B"));
+ assertFalse(headers.map().get("A").contains("F"));
+ assertThrows(NFE, () -> headers.firstValueAsLong("A"));
+
+ // a non-exhaustive list of mutators
+ assertThrows(UOE, () -> headers.map().put("Z", List.of("Z")));
+ assertThrows(UOE, () -> headers.map().remove("A"));
+ assertThrows(UOE, () -> headers.map().remove("A", "B"));
+ assertThrows(UOE, () -> headers.map().clear());
+ assertThrows(UOE, () -> headers.allValues("A").remove("B"));
+ assertThrows(UOE, () -> headers.allValues("A").remove(1));
+ assertThrows(UOE, () -> headers.allValues("A").clear());
+ assertThrows(UOE, () -> headers.allValues("A").add("Z"));
+ assertThrows(UOE, () -> headers.allValues("A").addAll(List.of("Z")));
+ assertThrows(UOE, () -> headers.allValues("A").add(1, "Z"));
+ }
+
+
+ @DataProvider(name = "caseInsensitivity")
+ public Object[][] caseInsensitivity() {
+ List<Map<String, List<String>>> maps = List.of(
+ Map.of("Accept-Encoding", List.of("gzip, deflate")),
+ Map.of("accept-encoding", List.of("gzip, deflate")),
+ Map.of("AccePT-ENCoding", List.of("gzip, deflate")),
+ Map.of("ACCept-EncodING", List.of("gzip, deflate")),
+ Map.of("ACCEPT-ENCODING", List.of("gzip, deflate"))
+ );
+ return maps.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+ }
+
+ @Test(dataProvider = "caseInsensitivity")
+ public void testCaseInsensitivity(Map<String,List<String>> map) {
+ HttpHeaders headers = HttpHeaders.of(map, ACCEPT_ALL);
+
+ for (String name : List.of("Accept-Encoding", "accept-encoding",
+ "aCCept-EnCODing", "accepT-encodinG")) {
+ assertTrue(headers.firstValue(name).isPresent());
+ assertTrue(headers.allValues(name).contains("gzip, deflate"));
+ assertEquals(headers.firstValue(name).get(), "gzip, deflate");
+ assertEquals(headers.allValues(name).size(), 1);
+ assertEquals(headers.map().size(), 1);
+ assertEquals(headers.map().get(name).size(), 1);
+ assertEquals(headers.map().get(name).get(0), "gzip, deflate");
+ }
+ }
+
+ @DataProvider(name = "valueAsLong")
+ public Object[][] valueAsLong() {
+ return new Object[][] {
+ new Object[] { Map.of("Content-Length", List.of("10")), 10l },
+ new Object[] { Map.of("Content-Length", List.of("101")), 101l },
+ new Object[] { Map.of("Content-Length", List.of("56789")), 56789l },
+ new Object[] { Map.of("Content-Length", List.of(Long.toString(Long.MAX_VALUE))), Long.MAX_VALUE },
+ new Object[] { Map.of("Content-Length", List.of(Long.toString(Long.MIN_VALUE))), Long.MIN_VALUE }
+ };
+ }
+
+ @Test(dataProvider = "valueAsLong")
+ public void testValueAsLong(Map<String,List<String>> map, long expected) {
+ HttpHeaders headers = HttpHeaders.of(map, ACCEPT_ALL);
+ assertEquals(headers.firstValueAsLong("Content-Length").getAsLong(), expected);
+ }
+
+
+ @DataProvider(name = "duplicateNames")
+ public Object[][] duplicateNames() {
+ List<Map<String, List<String>>> maps = List.of(
+ Map.of("X-name", List.of(),
+ "x-name", List.of()),
+ Map.of("X-name", List.of(""),
+ "x-name", List.of("")),
+ Map.of("X-name", List.of("C"),
+ "x-name", List.of("D")),
+ Map.of("X-name", List.of("E"),
+ "Y-name", List.of("F"),
+ "X-Name", List.of("G")),
+ Map.of("X-chegar", List.of("H"),
+ "y-dfuchs", List.of("I"),
+ "Y-dfuchs", List.of("J")),
+ Map.of("X-name ", List.of("K"),
+ "X-Name", List.of("L")),
+ Map.of("X-name", List.of("M"),
+ "\rX-Name", List.of("N"))
+ );
+ return maps.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+ }
+
+ @Test(dataProvider = "duplicateNames")
+ public void testDuplicates(Map<String,List<String>> map) {
+ HttpHeaders headers;
+ try {
+ headers = HttpHeaders.of(map, ACCEPT_ALL);
+ fail("UNEXPECTED: " + headers);
+ } catch (IllegalArgumentException iae) {
+ System.out.println("caught EXPECTED IAE:" + iae);
+ assertTrue(iae.getMessage().contains("duplicate"));
+ }
+ }
+
+
+ @DataProvider(name = "noSplittingJoining")
+ public Object[][] noSplittingJoining() {
+ List<Map<String, List<String>>> maps = List.of(
+ Map.of("A", List.of("B")),
+ Map.of("A", List.of("B", "C")),
+ Map.of("A", List.of("B", "C", "D")),
+ Map.of("A", List.of("B", "C", "D", "E")),
+ Map.of("A", List.of("B", "C", "D", "E", "F")),
+ Map.of("A", List.of("B, C")),
+ Map.of("A", List.of("B, C, D")),
+ Map.of("A", List.of("B, C, D, E")),
+ Map.of("A", List.of("B, C, D, E, F")),
+ Map.of("A", List.of("B, C", "D", "E", "F")),
+ Map.of("A", List.of("B", "C, D", "E", "F")),
+ Map.of("A", List.of("B", "C, D", "E, F")),
+ Map.of("A", List.of("B", "C, D, E", "F")),
+ Map.of("A", List.of("B", "C, D, E, F"))
+ );
+ return maps.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+ }
+
+ @Test(dataProvider = "noSplittingJoining")
+ public void testNoSplittingJoining(Map<String,List<String>> map) {
+ HttpHeaders headers = HttpHeaders.of(map, ACCEPT_ALL);
+ Map<String,List<String>> headersMap = headers.map();
+
+ assertEquals(headers.map().size(), map.size());
+ for (Map.Entry<String,List<String>> entry : map.entrySet()) {
+ String headerName = entry.getKey();
+ List<String> headerValues = entry.getValue();
+ assertEquals(headerValues, headersMap.get(headerName));
+ assertEquals(headerValues, headers.allValues(headerName));
+ assertEquals(headerValues.get(0), headers.firstValue(headerName).get());
+ }
+ }
+
+
+ @DataProvider(name = "trimming")
+ public Object[][] trimming() {
+ List<Map<String, List<String>>> maps = List.of(
+ Map.of("A", List.of("B")),
+ Map.of(" A", List.of("B")),
+ Map.of("A ", List.of("B")),
+ Map.of("A", List.of(" B")),
+ Map.of("A", List.of("B ")),
+ Map.of("\tA", List.of("B")),
+ Map.of("A\t", List.of("B")),
+ Map.of("A", List.of("\tB")),
+ Map.of("A", List.of("B\t")),
+ Map.of("A\r", List.of("B")),
+ Map.of("A\n", List.of("B")),
+ Map.of("A\r\n", List.of("B"))
+ );
+ return maps.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+ }
+
+ @Test(dataProvider = "trimming")
+ public void testTrimming(Map<String,List<String>> map) {
+ HttpHeaders headers = HttpHeaders.of(map, (name, value) -> {
+ assertEquals(name, "A");
+ assertEquals(value, "B");
+ return true;
+ });
+
+ assertEquals(headers.map().size(), 1);
+ assertEquals(headers.firstValue("A").get(), "B");
+ assertEquals(headers.allValues("A"), List.of("B"));
+ assertTrue(headers.map().get("A").equals(List.of("B")));
+ }
+
+
+ @DataProvider(name = "emptyKey")
+ public Object[][] emptyKey() {
+ List<Map<String, List<String>>> maps = List.of(
+ Map.of("", List.of("B")),
+ Map.of(" ", List.of("B")),
+ Map.of(" ", List.of("B")),
+ Map.of("\t", List.of("B")),
+ Map.of("\t\t", List.of("B"))
+ );
+ return maps.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+ }
+
+ @Test(dataProvider = "emptyKey")
+ public void testEmptyKey(Map<String,List<String>> map) {
+ HttpHeaders headers;
+ try {
+ headers = HttpHeaders.of(map, ACCEPT_ALL);
+ fail("UNEXPECTED: " + headers);
+ } catch (IllegalArgumentException iae) {
+ System.out.println("caught EXPECTED IAE:" + iae);
+ assertTrue(iae.getMessage().contains("empty"));
+ }
+ }
+
+
+ @DataProvider(name = "emptyValue")
+ public Object[][] emptyValue() {
+ List<Map<String, List<String>>> maps = List.of(
+ Map.of("A", List.of("")),
+ Map.of("A", List.of("", "")),
+ Map.of("A", List.of("", "", " ")),
+ Map.of("A", List.of("\t")),
+ Map.of("A", List.of("\t\t"))
+ );
+ return maps.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+ }
+
+ @Test(dataProvider = "emptyValue")
+ public void testEmptyValue(Map<String,List<String>> map) {
+ HttpHeaders headers = HttpHeaders.of(map, (name, value) -> {
+ assertEquals(value, "");
+ return true;
+ });
+ assertEquals(headers.map().size(), map.size());
+ assertEquals(headers.map().get("A").get(0), "");
+ headers.allValues("A").forEach(v -> assertEquals(v, ""));
+ assertEquals(headers.firstValue("A").get(), "");
+ }
+
+
+ @DataProvider(name = "noValues")
+ public Object[][] noValues() {
+ List<Map<String, List<String>>> maps = List.of(
+ Map.of("A", List.of()),
+ Map.of("A", List.of(), "B", List.of()),
+ Map.of("A", List.of(), "B", List.of(), "C", List.of()),
+ Map.of("A", new ArrayList()),
+ Map.of("A", new LinkedList())
+ );
+ return maps.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+ }
+
+ @Test(dataProvider = "noValues")
+ public void testNoValues(Map<String,List<String>> map) {
+ HttpHeaders headers = HttpHeaders.of(map, (name, value) -> {
+ fail("UNEXPECTED call to filter");
+ return true;
+ });
+ assertEquals(headers.map().size(), 0);
+ assertEquals(headers.map().get("A"), null);
+ assertEquals(headers.allValues("A").size(), 0);
+ assertFalse(headers.firstValue("A").isPresent());
+ }
+}
--- a/test/jdk/java/net/httpclient/HttpInputStreamTest.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/HttpInputStreamTest.java Fri May 25 16:13:11 2018 +0100
@@ -47,7 +47,7 @@
/*
* @test
* @summary An example on how to read a response body with InputStream.
- * @run main/manual -Dtest.debug=true HttpInputStreamTest
+ * @run main/othervm/manual -Dtest.debug=true HttpInputStreamTest
* @author daniel fuchs
*/
public class HttpInputStreamTest {
--- a/test/jdk/java/net/httpclient/HttpServerAdapters.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/HttpServerAdapters.java Fri May 25 16:13:11 2018 +0100
@@ -27,11 +27,11 @@
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import java.net.InetAddress;
import java.io.ByteArrayInputStream;
import java.net.http.HttpClient.Version;
-import jdk.internal.net.http.common.HttpHeadersImpl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -41,6 +41,7 @@
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.URI;
+import java.net.http.HttpHeaders;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@@ -95,26 +96,26 @@
}
/**
- * A version agnostic adapter class for HTTP Headers.
+ * A version agnostic adapter class for HTTP request Headers.
*/
- public static abstract class HttpTestHeaders {
+ public static abstract class HttpTestRequestHeaders {
public abstract Optional<String> firstValue(String name);
- public abstract void addHeader(String name, String value);
public abstract Set<String> keySet();
public abstract Set<Map.Entry<String, List<String>>> entrySet();
public abstract List<String> get(String name);
public abstract boolean containsKey(String name);
- public static HttpTestHeaders of(Headers headers) {
- return new Http1TestHeaders(headers);
- }
- public static HttpTestHeaders of(HttpHeadersImpl headers) {
- return new Http2TestHeaders(headers);
+ public static HttpTestRequestHeaders of(Headers headers) {
+ return new Http1TestRequestHeaders(headers);
}
- private final static class Http1TestHeaders extends HttpTestHeaders {
+ public static HttpTestRequestHeaders of(HttpHeaders headers) {
+ return new Http2TestRequestHeaders(headers);
+ }
+
+ private static final class Http1TestRequestHeaders extends HttpTestRequestHeaders {
private final Headers headers;
- Http1TestHeaders(Headers h) { this.headers = h; }
+ Http1TestRequestHeaders(Headers h) { this.headers = h; }
@Override
public Optional<String> firstValue(String name) {
if (headers.containsKey(name)) {
@@ -123,11 +124,6 @@
return Optional.empty();
}
@Override
- public void addHeader(String name, String value) {
- headers.add(name, value);
- }
-
- @Override
public Set<String> keySet() { return headers.keySet(); }
@Override
public Set<Map.Entry<String, List<String>>> entrySet() {
@@ -142,17 +138,14 @@
return headers.containsKey(name);
}
}
- private final static class Http2TestHeaders extends HttpTestHeaders {
- private final HttpHeadersImpl headers;
- Http2TestHeaders(HttpHeadersImpl h) { this.headers = h; }
+ private static final class Http2TestRequestHeaders extends HttpTestRequestHeaders {
+ private final HttpHeaders headers;
+ Http2TestRequestHeaders(HttpHeaders h) { this.headers = h; }
@Override
public Optional<String> firstValue(String name) {
return headers.firstValue(name);
}
@Override
- public void addHeader(String name, String value) {
- headers.addHeader(name, value);
- }
public Set<String> keySet() { return headers.map().keySet(); }
@Override
public Set<Map.Entry<String, List<String>>> entrySet() {
@@ -170,6 +163,37 @@
}
/**
+ * A version agnostic adapter class for HTTP response Headers.
+ */
+ public static abstract class HttpTestResponseHeaders {
+ public abstract void addHeader(String name, String value);
+
+ public static HttpTestResponseHeaders of(Headers headers) {
+ return new Http1TestResponseHeaders(headers);
+ }
+ public static HttpTestResponseHeaders of(HttpHeadersBuilder headersBuilder) {
+ return new Http2TestResponseHeaders(headersBuilder);
+ }
+
+ private final static class Http1TestResponseHeaders extends HttpTestResponseHeaders {
+ private final Headers headers;
+ Http1TestResponseHeaders(Headers h) { this.headers = h; }
+ @Override
+ public void addHeader(String name, String value) {
+ headers.add(name, value);
+ }
+ }
+ private final static class Http2TestResponseHeaders extends HttpTestResponseHeaders {
+ private final HttpHeadersBuilder headersBuilder;
+ Http2TestResponseHeaders(HttpHeadersBuilder hb) { this.headersBuilder = hb; }
+ @Override
+ public void addHeader(String name, String value) {
+ headersBuilder.addHeader(name, value);
+ }
+ }
+ }
+
+ /**
* A version agnostic adapter class for HTTP Server Exchange.
*/
public static abstract class HttpTestExchange {
@@ -177,17 +201,17 @@
public abstract Version getExchangeVersion();
public abstract InputStream getRequestBody();
public abstract OutputStream getResponseBody();
- public abstract HttpTestHeaders getRequestHeaders();
- public abstract HttpTestHeaders getResponseHeaders();
+ public abstract HttpTestRequestHeaders getRequestHeaders();
+ public abstract HttpTestResponseHeaders getResponseHeaders();
public abstract void sendResponseHeaders(int code, int contentLength) throws IOException;
public abstract URI getRequestURI();
public abstract String getRequestMethod();
public abstract void close();
- public void serverPush(URI uri, HttpTestHeaders headers, byte[] body) {
+ public void serverPush(URI uri, HttpHeaders headers, byte[] body) {
ByteArrayInputStream bais = new ByteArrayInputStream(body);
serverPush(uri, headers, bais);
}
- public void serverPush(URI uri, HttpTestHeaders headers, InputStream body) {
+ public void serverPush(URI uri, HttpHeaders headers, InputStream body) {
throw new UnsupportedOperationException("serverPush with " + getExchangeVersion());
}
public boolean serverPushAllowed() {
@@ -221,12 +245,12 @@
return exchange.getResponseBody();
}
@Override
- public HttpTestHeaders getRequestHeaders() {
- return HttpTestHeaders.of(exchange.getRequestHeaders());
+ public HttpTestRequestHeaders getRequestHeaders() {
+ return HttpTestRequestHeaders.of(exchange.getRequestHeaders());
}
@Override
- public HttpTestHeaders getResponseHeaders() {
- return HttpTestHeaders.of(exchange.getResponseHeaders());
+ public HttpTestResponseHeaders getResponseHeaders() {
+ return HttpTestResponseHeaders.of(exchange.getResponseHeaders());
}
@Override
public void sendResponseHeaders(int code, int contentLength) throws IOException {
@@ -268,12 +292,13 @@
return exchange.getResponseBody();
}
@Override
- public HttpTestHeaders getRequestHeaders() {
- return HttpTestHeaders.of(exchange.getRequestHeaders());
+ public HttpTestRequestHeaders getRequestHeaders() {
+ return HttpTestRequestHeaders.of(exchange.getRequestHeaders());
}
+
@Override
- public HttpTestHeaders getResponseHeaders() {
- return HttpTestHeaders.of(exchange.getResponseHeaders());
+ public HttpTestResponseHeaders getResponseHeaders() {
+ return HttpTestResponseHeaders.of(exchange.getResponseHeaders());
}
@Override
public void sendResponseHeaders(int code, int contentLength) throws IOException {
@@ -286,20 +311,8 @@
return exchange.serverPushAllowed();
}
@Override
- public void serverPush(URI uri, HttpTestHeaders headers, InputStream body) {
- HttpHeadersImpl headersImpl;
- if (headers instanceof HttpTestHeaders.Http2TestHeaders) {
- headersImpl = ((HttpTestHeaders.Http2TestHeaders)headers).headers.deepCopy();
- } else {
- headersImpl = new HttpHeadersImpl();
- for (Map.Entry<String, List<String>> e : headers.entrySet()) {
- String name = e.getKey();
- for (String v : e.getValue()) {
- headersImpl.addHeader(name, v);
- }
- }
- }
- exchange.serverPush(uri, headersImpl, body);
+ public void serverPush(URI uri, HttpHeaders headers, InputStream body) {
+ exchange.serverPush(uri, headers, body);
}
void doFilter(Filter.Chain filter) throws IOException {
throw new IOException("cannot use HTTP/1.1 filter with HTTP/2 server");
@@ -363,7 +376,7 @@
}
public static boolean expectException(HttpTestExchange e) {
- HttpTestHeaders h = e.getRequestHeaders();
+ HttpTestRequestHeaders h = e.getRequestHeaders();
Optional<String> expectException = h.firstValue("X-expect-exception");
if (expectException.isPresent()) {
return expectException.get().equalsIgnoreCase("true");
--- a/test/jdk/java/net/httpclient/MethodsTest.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/MethodsTest.java Fri May 25 16:13:11 2018 +0100
@@ -21,8 +21,6 @@
* questions.
*/
-import jdk.internal.net.http.common.HttpHeadersImpl;
-
import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
@@ -30,10 +28,10 @@
import java.net.URI;
import java.net.http.HttpResponse;
import java.time.Duration;
+import java.util.Map;
import java.util.Optional;
import static java.net.http.HttpClient.Builder.NO_PROXY;
-
/**
* @test
* @bug 8199135
@@ -75,7 +73,7 @@
return Optional.empty();
}
@Override public HttpHeaders headers() {
- return new HttpHeadersImpl();
+ return HttpHeaders.of(Map.of(), (x, y) -> true);
}
};
client.send(req, HttpResponse.BodyHandlers.ofString());
--- a/test/jdk/java/net/httpclient/RedirectMethodChange.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/RedirectMethodChange.java Fri May 25 16:13:11 2018 +0100
@@ -283,14 +283,14 @@
}
if (newtest) {
- HttpTestHeaders hdrs = he.getRequestHeaders();
+ HttpTestRequestHeaders hdrs = he.getRequestHeaders();
String value = hdrs.firstValue("X-Redirect-Code").get();
int redirectCode = Integer.parseInt(value);
expectedMethod = hdrs.firstValue("X-Expect-Method").get();
if (!readAndCheckBody(he))
return;
- hdrs = he.getResponseHeaders();
- hdrs.addHeader("Location", targetURL);
+ HttpTestResponseHeaders headersbuilder = he.getResponseHeaders();
+ headersbuilder.addHeader("Location", targetURL);
he.sendResponseHeaders(redirectCode, 0);
inTest = true;
} else {
--- a/test/jdk/java/net/httpclient/ThrowingPushPromises.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/ThrowingPushPromises.java Fri May 25 16:13:11 2018 +0100
@@ -35,7 +35,6 @@
* @run testng/othervm -Djdk.internal.httpclient.debug=true ThrowingPushPromises
*/
-import jdk.internal.net.http.common.HttpHeadersImpl;
import jdk.testlibrary.SimpleSSLContext;
import org.testng.annotations.AfterTest;
import org.testng.annotations.AfterClass;
@@ -53,6 +52,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;
@@ -72,6 +72,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Flow;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -679,8 +680,13 @@
}
}
- private static void pushPromiseFor(HttpTestExchange t, URI requestURI, String pushPath, boolean fixed)
- throws IOException
+ static final BiPredicate<String,String> ACCEPT_ALL = (x, y) -> true;
+
+ private static void pushPromiseFor(HttpTestExchange t,
+ URI requestURI,
+ String pushPath,
+ boolean fixed)
+ throws IOException
{
try {
URI promise = new URI(requestURI.getScheme(),
@@ -689,9 +695,13 @@
byte[] promiseBytes = promise.toASCIIString().getBytes(UTF_8);
out.printf("TestServer: %s Pushing promise: %s%n", now(), promise);
err.printf("TestServer: %s Pushing promise: %s%n", now(), promise);
- HttpTestHeaders headers = HttpTestHeaders.of(new HttpHeadersImpl());
+ HttpHeaders headers;
if (fixed) {
- headers.addHeader("Content-length", String.valueOf(promiseBytes.length));
+ String length = String.valueOf(promiseBytes.length);
+ headers = HttpHeaders.of(Map.of("Content-Length", List.of(length)),
+ ACCEPT_ALL);
+ } else {
+ headers = HttpHeaders.of(Map.of(), ACCEPT_ALL); // empty
}
t.serverPush(promise, headers, promiseBytes);
} catch (URISyntaxException x) {
--- a/test/jdk/java/net/httpclient/http2/BadHeadersTest.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/BadHeadersTest.java Fri May 25 16:13:11 2018 +0100
@@ -33,8 +33,7 @@
* @run testng/othervm -Djdk.internal.httpclient.debug=true BadHeadersTest
*/
-import jdk.internal.net.http.common.HttpHeadersImpl;
-import jdk.internal.net.http.common.Pair;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import jdk.internal.net.http.frame.ContinuationFrame;
import jdk.internal.net.http.frame.HeaderFrame;
import jdk.internal.net.http.frame.HeadersFrame;
@@ -44,41 +43,38 @@
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
-
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.Socket;
import java.net.URI;
import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.ByteBuffer;
import java.util.ArrayList;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;
-
import static java.util.List.of;
-import static jdk.internal.net.http.common.Pair.pair;
-import static org.testng.Assert.assertThrows;
+import static java.util.Map.entry;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
// Code copied from ContinuationFrameTest
public class BadHeadersTest {
- private static final List<List<Pair<String, String>>> BAD_HEADERS = of(
- of(pair(":status", "200"), pair(":hello", "GET")), // Unknown pseudo-header
- of(pair(":status", "200"), pair("hell o", "value")), // Space in the name
- of(pair(":status", "200"), pair("hello", "line1\r\n line2\r\n")), // Multiline value
- of(pair(":status", "200"), pair("hello", "DE" + ((char) 0x7F) + "L")), // Bad byte in value
- of(pair("hello", "world!"), pair(":status", "200")) // Pseudo header is not the first one
+ private static final List<List<Entry<String, String>>> BAD_HEADERS = of(
+ of(entry(":status", "200"), entry(":hello", "GET")), // Unknown pseudo-header
+ of(entry(":status", "200"), entry("hell o", "value")), // Space in the name
+ of(entry(":status", "200"), entry("hello", "line1\r\n line2\r\n")), // Multiline value
+ of(entry(":status", "200"), entry("hello", "DE" + ((char) 0x7F) + "L")), // Bad byte in value
+ of(entry("hello", "world!"), entry(":status", "200")) // Pseudo header is not the first one
);
SSLContext sslContext;
@@ -143,7 +139,35 @@
void test(String uri,
boolean sameClient,
BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)
- throws Exception
+ throws Exception
+ {
+ CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier);
+
+ HttpClient client = null;
+ for (int i=0; i< BAD_HEADERS.size(); i++) {
+ if (!sameClient || client == null)
+ client = HttpClient.newBuilder().sslContext(sslContext).build();
+
+ URI uriWithQuery = URI.create(uri + "?BAD_HEADERS=" + i);
+ HttpRequest request = HttpRequest.newBuilder(uriWithQuery)
+ .POST(BodyPublishers.ofString("Hello there!"))
+ .build();
+ System.out.println("\nSending request:" + uriWithQuery);
+ final HttpClient cc = client;
+ try {
+ HttpResponse<String> response = cc.send(request, BodyHandlers.ofString());
+ fail("Expected exception, got :" + response + ", " + response.body());
+ } catch (IOException ioe) {
+ System.out.println("Got EXPECTED: " + ioe);
+ assertDetailMessage(ioe, i);
+ }
+ }
+ }
+
+ @Test(dataProvider = "variants")
+ void testAsync(String uri,
+ boolean sameClient,
+ BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)
{
CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier);
@@ -152,31 +176,45 @@
if (!sameClient || client == null)
client = HttpClient.newBuilder().sslContext(sslContext).build();
- HttpRequest request = HttpRequest.newBuilder(URI.create(uri))
+ URI uriWithQuery = URI.create(uri + "?BAD_HEADERS=" + i);
+ HttpRequest request = HttpRequest.newBuilder(uriWithQuery)
.POST(BodyPublishers.ofString("Hello there!"))
.build();
+ System.out.println("\nSending request:" + uriWithQuery);
final HttpClient cc = client;
- if (i % 2 == 0) {
- assertThrows(IOException.class, () -> cc.send(request, BodyHandlers.ofString()));
- } else {
- Throwable t = null;
- try {
- cc.sendAsync(request, BodyHandlers.ofString()).join();
- } catch (Throwable t0) {
- t = t0;
+
+ Throwable t = null;
+ try {
+ HttpResponse<String> response = cc.sendAsync(request, BodyHandlers.ofString()).get();
+ fail("Expected exception, got :" + response + ", " + response.body());
+ } catch (Throwable t0) {
+ System.out.println("Got EXPECTED: " + t0);
+ if (t0 instanceof ExecutionException) {
+ t0 = t0.getCause();
}
- if (t == null) {
- throw new AssertionError("An exception was expected");
- }
- if (t instanceof CompletionException) {
- Throwable c = t.getCause();
- if (!(c instanceof IOException)) {
- throw new AssertionError("Unexpected exception", c);
- }
- } else if (!(t instanceof IOException)) {
- throw new AssertionError("Unexpected exception", t);
- }
+ t = t0;
}
+ assertDetailMessage(t, i);
+ }
+ }
+
+ // Assertions based on implementation specific detail messages. Keep in
+ // sync with implementation.
+ static void assertDetailMessage(Throwable throwable, int iterationIndex) {
+ assertTrue(throwable instanceof IOException,
+ "Expected IOException, got, " + throwable);
+ assertTrue(throwable.getMessage().contains("protocol error"),
+ "Expected \"protocol error\" in: " + throwable.getMessage());
+
+ if (iterationIndex == 0) { // unknown
+ assertTrue(throwable.getMessage().contains("Unknown pseudo-header"),
+ "Expected \"Unknown pseudo-header\" in: " + throwable.getMessage());
+ } else if (iterationIndex == 4) { // unexpected
+ assertTrue(throwable.getMessage().contains(" Unexpected pseudo-header"),
+ "Expected \" Unexpected pseudo-header\" in: " + throwable.getMessage());
+ } else {
+ assertTrue(throwable.getMessage().contains("Bad header"),
+ "Expected \"Bad header\" in: " + throwable.getMessage());
}
}
@@ -186,38 +224,12 @@
if (sslContext == null)
throw new AssertionError("Unexpected null sslContext");
- http2TestServer = new Http2TestServer("localhost", false, 0) {
- @Override
- protected Http2TestServerConnection createConnection(Http2TestServer http2TestServer,
- Socket socket,
- Http2TestExchangeSupplier exchangeSupplier)
- throws IOException {
- return new Http2TestServerConnection(http2TestServer, socket, exchangeSupplier, null) {
- @Override
- protected HttpHeadersImpl createNewResponseHeaders() {
- return new OrderedHttpHeaders();
- }
- };
- }
- };
+ http2TestServer = new Http2TestServer("localhost", false, 0);
http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo");
int port = http2TestServer.getAddress().getPort();
http2URI = "http://localhost:" + port + "/http2/echo";
- https2TestServer = new Http2TestServer("localhost", true, 0){
- @Override
- protected Http2TestServerConnection createConnection(Http2TestServer http2TestServer,
- Socket socket,
- Http2TestExchangeSupplier exchangeSupplier)
- throws IOException {
- return new Http2TestServerConnection(http2TestServer, socket, exchangeSupplier, null) {
- @Override
- protected HttpHeadersImpl createNewResponseHeaders() {
- return new OrderedHttpHeaders();
- }
- };
- }
- };
+ https2TestServer = new Http2TestServer("localhost", true, 0);
https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo");
port = https2TestServer.getAddress().getPort();
https2URI = "https://localhost:" + port + "/https2/echo";
@@ -239,16 +251,14 @@
static class Http2EchoHandler implements Http2Handler {
- private final AtomicInteger requestNo = new AtomicInteger();
-
@Override
public void handle(Http2TestExchange t) throws IOException {
try (InputStream is = t.getRequestBody();
OutputStream os = t.getResponseBody()) {
byte[] bytes = is.readAllBytes();
- int i = requestNo.incrementAndGet();
- List<Pair<String, String>> p = BAD_HEADERS.get(i % BAD_HEADERS.size());
- p.forEach(h -> t.getResponseHeaders().addHeader(h.first, h.second));
+ // Note: strictly ordered response headers will be added within
+ // the custom sendResponseHeaders implementation, based upon the
+ // query parameter
t.sendResponseHeaders(200, bytes.length);
os.write(bytes);
}
@@ -259,23 +269,34 @@
// allow headers to be sent with a number of CONTINUATION frames.
static class CFTHttp2TestExchange extends Http2TestExchangeImpl {
static volatile BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFrameSupplier;
+ volatile int badHeadersIndex = -1;
static void setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs) {
headerFrameSupplier = hfs;
}
- CFTHttp2TestExchange(int streamid, String method, HttpHeadersImpl reqheaders,
- HttpHeadersImpl rspheaders, URI uri, InputStream is,
+ CFTHttp2TestExchange(int streamid, String method, HttpHeaders reqheaders,
+ HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is,
SSLSession sslSession, BodyOutputStream os,
Http2TestServerConnection conn, boolean pushAllowed) {
- super(streamid, method, reqheaders, rspheaders, uri, is, sslSession,
+ super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession,
os, conn, pushAllowed);
-
+ String query = uri.getQuery();
+ badHeadersIndex = Integer.parseInt(query.substring(query.indexOf("=") + 1));
+ assert badHeadersIndex >= 0 && badHeadersIndex < BAD_HEADERS.size() :
+ "Unexpected badHeadersIndex value: " + badHeadersIndex;
}
@Override
public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
- List<ByteBuffer> encodeHeaders = conn.encodeHeaders(rspheaders);
+ assert rspheadersBuilder.build().map().size() == 0;
+ assert badHeadersIndex >= 0 && badHeadersIndex < BAD_HEADERS.size() :
+ "Unexpected badHeadersIndex value: " + badHeadersIndex;
+
+ List<Entry<String,String>> headers = BAD_HEADERS.get(badHeadersIndex);
+ System.out.println("Server replying with bad headers: " + headers);
+ List<ByteBuffer> encodeHeaders = conn.encodeHeadersOrdered(headers);
+
List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders);
assert headerFrames.size() > 0; // there must always be at least 1
@@ -291,36 +312,4 @@
System.err.println("Sent response headers " + rCode);
}
}
-
- /*
- * Use carefully. This class might not be suitable outside this test's
- * context. Pay attention working with multi Map view returned from map().
- * The reason is that header names must be lower-cased prior to any
- * operation that depends on whether or not the map contains a specific
- * element.
- */
- private static class OrderedHttpHeaders extends HttpHeadersImpl {
-
- private final Map<String, List<String>> map = new LinkedHashMap<>();
-
- @Override
- public void addHeader(String name, String value) {
- super.addHeader(name.toLowerCase(Locale.ROOT), value);
- }
-
- @Override
- public void setHeader(String name, String value) {
- super.setHeader(name.toLowerCase(Locale.ROOT), value);
- }
-
- @Override
- protected Map<String, List<String>> headersMap() {
- return map;
- }
-
- @Override
- protected HttpHeadersImpl newDeepCopy() {
- return new OrderedHttpHeaders();
- }
- }
}
--- a/test/jdk/java/net/httpclient/http2/ContinuationFrameTest.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/ContinuationFrameTest.java Fri May 25 16:13:11 2018 +0100
@@ -38,6 +38,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
+import java.net.http.HttpHeaders;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -49,7 +50,7 @@
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import jdk.internal.net.http.frame.ContinuationFrame;
import jdk.internal.net.http.frame.HeaderFrame;
import jdk.internal.net.http.frame.HeadersFrame;
@@ -212,11 +213,11 @@
headerFrameSupplier = hfs;
}
- CFTHttp2TestExchange(int streamid, String method, HttpHeadersImpl reqheaders,
- HttpHeadersImpl rspheaders, URI uri, InputStream is,
+ CFTHttp2TestExchange(int streamid, String method, HttpHeaders reqheaders,
+ HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is,
SSLSession sslSession, BodyOutputStream os,
Http2TestServerConnection conn, boolean pushAllowed) {
- super(streamid, method, reqheaders, rspheaders, uri, is, sslSession,
+ super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession,
os, conn, pushAllowed);
}
@@ -226,11 +227,12 @@
this.responseLength = responseLength;
if (responseLength > 0 || responseLength < 0) {
long clen = responseLength > 0 ? responseLength : 0;
- rspheaders.setHeader("Content-length", Long.toString(clen));
+ rspheadersBuilder.setHeader("Content-length", Long.toString(clen));
}
- rspheaders.setHeader(":status", Integer.toString(rCode));
+ rspheadersBuilder.setHeader(":status", Integer.toString(rCode));
+ HttpHeaders headers = rspheadersBuilder.build();
- List<ByteBuffer> encodeHeaders = conn.encodeHeaders(rspheaders);
+ List<ByteBuffer> encodeHeaders = conn.encodeHeaders(headers);
List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders);
assert headerFrames.size() > 0; // there must always be at least 1
--- a/test/jdk/java/net/httpclient/http2/ImplicitPushCancel.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/ImplicitPushCancel.java Fri May 25 16:13:11 2018 +0100
@@ -40,18 +40,18 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpResponse.BodyHandlers;
-import java.net.http.HttpResponse.PushPromiseHandler;
-import jdk.internal.net.http.common.HttpHeadersImpl;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
@@ -177,7 +177,7 @@
for (Map.Entry<String,String> promise : promises.entrySet()) {
URI uri = requestURI.resolve(promise.getKey());
InputStream is = new ByteArrayInputStream(promise.getValue().getBytes(UTF_8));
- HttpHeadersImpl headers = new HttpHeadersImpl();
+ HttpHeaders headers = HttpHeaders.of(Collections.emptyMap(), (x, y) -> true);
exchange.serverPush(uri, headers, is);
}
System.err.println("Server: All pushes sent");
--- a/test/jdk/java/net/httpclient/http2/ServerPush.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/ServerPush.java Fri May 25 16:13:11 2018 +0100
@@ -53,7 +53,6 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.testng.Assert.*;
-
public class ServerPush {
static final int LOOPS = 13;
--- a/test/jdk/java/net/httpclient/http2/ServerPushWithDiffTypes.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/ServerPushWithDiffTypes.java Fri May 25 16:13:11 2018 +0100
@@ -46,7 +46,8 @@
import java.net.http.HttpResponse.BodySubscribers;
import java.util.*;
import java.util.concurrent.*;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import java.util.function.BiPredicate;
+
import org.testng.annotations.Test;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.testng.Assert.assertEquals;
@@ -242,14 +243,16 @@
}
}
+ static final BiPredicate<String,String> ACCEPT_ALL = (x, y) -> true;
+
private void pushPromises(Http2TestExchange exchange) throws IOException {
URI requestURI = exchange.getRequestURI();
for (Map.Entry<String,String> promise : promises.entrySet()) {
URI uri = requestURI.resolve(promise.getKey());
InputStream is = new ByteArrayInputStream(promise.getValue().getBytes(UTF_8));
- HttpHeadersImpl headers = new HttpHeadersImpl();
+ Map<String,List<String>> map = Map.of("X-Promise", List.of(promise.getKey()));
+ HttpHeaders headers = HttpHeaders.of(map, ACCEPT_ALL);
// TODO: add some check on headers, maybe
- headers.addHeader("X-Promise", promise.getKey());
exchange.serverPush(uri, headers, is);
}
System.err.println("Server: All pushes sent");
--- a/test/jdk/java/net/httpclient/http2/server/Http2EchoHandler.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/server/Http2EchoHandler.java Fri May 25 16:13:11 2018 +0100
@@ -22,11 +22,11 @@
*/
import java.io.*;
+import java.net.http.HttpHeaders;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
public class Http2EchoHandler implements Http2Handler {
static final Path CWD = Paths.get(".");
@@ -40,10 +40,10 @@
System.err.printf("EchoHandler received request to %s from %s\n",
t.getRequestURI(), t.getRemoteAddress());
InputStream is = t.getRequestBody();
- HttpHeadersImpl map = t.getRequestHeaders();
- HttpHeadersImpl map1 = t.getResponseHeaders();
- map1.addHeader("X-Hello", "world");
- map1.addHeader("X-Bye", "universe");
+ HttpHeaders map = t.getRequestHeaders();
+ HttpHeadersBuilder headersBuilder = t.getResponseHeaders();
+ headersBuilder.addHeader("X-Hello", "world");
+ headersBuilder.addHeader("X-Bye", "universe");
String fixedrequest = map.firstValue("XFixed").orElse(null);
File outfile = Files.createTempFile(CWD, "foo", "bar").toFile();
//System.err.println ("QQQ = " + outfile.toString());
--- a/test/jdk/java/net/httpclient/http2/server/Http2RedirectHandler.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/server/Http2RedirectHandler.java Fri May 25 16:13:11 2018 +0100
@@ -25,7 +25,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.function.Supplier;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
public class Http2RedirectHandler implements Http2Handler {
@@ -44,8 +44,8 @@
System.err.printf("RedirectHandler request to %s from %s\n",
t.getRequestURI().toString(), t.getRemoteAddress().toString());
System.err.println("Redirecting to: " + location);
- HttpHeadersImpl map1 = t.getResponseHeaders();
- map1.addHeader("Location", location);
+ HttpHeadersBuilder headersBuilder = t.getResponseHeaders();
+ headersBuilder.addHeader("Location", location);
t.sendResponseHeaders(301, 1024);
byte[] bb = new byte[1024];
OutputStream os = t.getResponseBody();
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java Fri May 25 16:13:11 2018 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, 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
@@ -26,16 +26,16 @@
import java.io.OutputStream;
import java.net.URI;
import java.net.InetSocketAddress;
+import java.net.http.HttpHeaders;
import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
import javax.net.ssl.SSLSession;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
public interface Http2TestExchange {
- HttpHeadersImpl getRequestHeaders();
+ HttpHeaders getRequestHeaders();
- HttpHeadersImpl getResponseHeaders();
+ HttpHeadersBuilder getResponseHeaders();
URI getRequestURI();
@@ -61,7 +61,7 @@
boolean serverPushAllowed();
- void serverPush(URI uri, HttpHeadersImpl headers, InputStream content);
+ void serverPush(URI uri, HttpHeaders headers, InputStream content);
/**
* Send a PING on this exchanges connection, and completes the returned CF
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java Fri May 25 16:13:11 2018 +0100
@@ -26,17 +26,19 @@
import java.io.IOException;
import java.net.URI;
import java.net.InetSocketAddress;
+import java.net.http.HttpHeaders;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
import javax.net.ssl.SSLSession;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import jdk.internal.net.http.frame.HeaderFrame;
import jdk.internal.net.http.frame.HeadersFrame;
public class Http2TestExchangeImpl implements Http2TestExchange {
- final HttpHeadersImpl reqheaders;
- final HttpHeadersImpl rspheaders;
+ final HttpHeaders reqheaders;
+ final HttpHeadersBuilder rspheadersBuilder;
final URI uri;
final String method;
final InputStream is;
@@ -52,8 +54,8 @@
Http2TestExchangeImpl(int streamid,
String method,
- HttpHeadersImpl reqheaders,
- HttpHeadersImpl rspheaders,
+ HttpHeaders reqheaders,
+ HttpHeadersBuilder rspheadersBuilder,
URI uri,
InputStream is,
SSLSession sslSession,
@@ -61,7 +63,7 @@
Http2TestServerConnection conn,
boolean pushAllowed) {
this.reqheaders = reqheaders;
- this.rspheaders = rspheaders;
+ this.rspheadersBuilder = rspheadersBuilder;
this.uri = uri;
this.method = method;
this.is = is;
@@ -74,7 +76,7 @@
}
@Override
- public HttpHeadersImpl getRequestHeaders() {
+ public HttpHeaders getRequestHeaders() {
return reqheaders;
}
@@ -84,8 +86,8 @@
}
@Override
- public HttpHeadersImpl getResponseHeaders() {
- return rspheaders;
+ public HttpHeadersBuilder getResponseHeaders() {
+ return rspheadersBuilder;
}
@Override
@@ -129,13 +131,14 @@
this.responseLength = responseLength;
if (responseLength > 0 || responseLength < 0) {
long clen = responseLength > 0 ? responseLength : 0;
- rspheaders.setHeader("Content-length", Long.toString(clen));
+ rspheadersBuilder.setHeader("Content-length", Long.toString(clen));
}
- rspheaders.setHeader(":status", Integer.toString(rCode));
+ rspheadersBuilder.setHeader(":status", Integer.toString(rCode));
+ HttpHeaders headers = rspheadersBuilder.build();
Http2TestServerConnection.ResponseHeaders response
- = new Http2TestServerConnection.ResponseHeaders(rspheaders);
+ = new Http2TestServerConnection.ResponseHeaders(headers);
response.streamid(streamid);
response.setFlag(HeaderFrame.END_HEADERS);
@@ -175,13 +178,19 @@
}
@Override
- public void serverPush(URI uri, HttpHeadersImpl headers, InputStream content) {
- OutgoingPushPromise pp = new OutgoingPushPromise(
- streamid, uri, headers, content);
- headers.setHeader(":method", "GET");
- headers.setHeader(":scheme", uri.getScheme());
- headers.setHeader(":authority", uri.getAuthority());
- headers.setHeader(":path", uri.getPath());
+ public void serverPush(URI uri, HttpHeaders headers, InputStream content) {
+ HttpHeadersBuilder headersBuilder = new HttpHeadersBuilder();
+ headersBuilder.setHeader(":method", "GET");
+ headersBuilder.setHeader(":scheme", uri.getScheme());
+ headersBuilder.setHeader(":authority", uri.getAuthority());
+ headersBuilder.setHeader(":path", uri.getPath());
+ for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {
+ for (String value : entry.getValue())
+ headersBuilder.addHeader(entry.getKey(), value);
+ }
+ HttpHeaders combinedHeaders = headersBuilder.build();
+ OutgoingPushPromise pp = new OutgoingPushPromise(streamid, uri, combinedHeaders, content);
+
try {
conn.outputQ.put(pp);
// writeLoop will spin up thread to read the InputStream
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeSupplier.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeSupplier.java Fri May 25 16:13:11 2018 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, 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
@@ -24,7 +24,8 @@
import javax.net.ssl.SSLSession;
import java.io.InputStream;
import java.net.URI;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import java.net.http.HttpHeaders;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
/**
* A supplier of Http2TestExchanges. If the default Http2TestExchange impl is
@@ -39,8 +40,8 @@
Http2TestExchange get(int streamid,
String method,
- HttpHeadersImpl reqheaders,
- HttpHeadersImpl rspheaders,
+ HttpHeaders reqheaders,
+ HttpHeadersBuilder rspheadersBuilder,
URI uri,
InputStream is,
SSLSession sslSession,
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java Fri May 25 16:13:11 2018 +0100
@@ -33,14 +33,14 @@
import java.net.InetAddress;
import javax.net.ssl.*;
import java.net.URISyntaxException;
+import java.net.http.HttpHeaders;
import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import jdk.internal.net.http.frame.*;
import jdk.internal.net.http.hpack.Decoder;
import jdk.internal.net.http.hpack.DecodingCallback;
@@ -455,11 +455,11 @@
outputQ.put(wup);
}
- HttpHeadersImpl decodeHeaders(List<HeaderFrame> frames) throws IOException {
- HttpHeadersImpl headers = createNewResponseHeaders();
+ HttpHeaders decodeHeaders(List<HeaderFrame> frames) throws IOException {
+ HttpHeadersBuilder headersBuilder = createNewHeadersBuilder();
DecodingCallback cb = (name, value) -> {
- headers.addHeader(name.toString(), value.toString());
+ headersBuilder.addHeader(name.toString(), value.toString());
};
for (HeaderFrame frame : frames) {
@@ -469,7 +469,7 @@
}
}
hpackIn.decode(EMPTY_BUFFER, true, cb);
- return headers;
+ return headersBuilder.build();
}
String getRequestLine(String request) {
@@ -486,8 +486,8 @@
return request.substring(start,end);
}
- void addHeaders(String headers, HttpHeadersImpl hdrs) {
- String[] hh = headers.split(CRLF);
+ static void addHeaders(String headersString, HttpHeadersBuilder headersBuilder) {
+ String[] hh = headersString.split(CRLF);
for (String header : hh) {
int colon = header.indexOf(':');
if (colon == -1)
@@ -496,14 +496,14 @@
String value = header.substring(colon+1);
while (value.startsWith(" "))
value = value.substring(1);
- hdrs.addHeader(name, value);
+ headersBuilder.addHeader(name, value);
}
}
// First stream (1) comes from a plaintext HTTP/1.1 request
@SuppressWarnings({"rawtypes","unchecked"})
void createPrimordialStream(Http1InitialRequest request) throws IOException {
- HttpHeadersImpl headers = createNewResponseHeaders();
+ HttpHeadersBuilder headersBuilder = createNewHeadersBuilder();
String requestLine = getRequestLine(request.headers);
String[] tokens = requestLine.split(" ");
if (!tokens[2].equals("HTTP/1.1")) {
@@ -520,18 +520,19 @@
throw new IOException("missing Host");
}
- headers.setHeader(":method", tokens[0]);
- headers.setHeader(":scheme", "http"); // always in this case
- headers.setHeader(":authority", host);
+ headersBuilder.setHeader(":method", tokens[0]);
+ headersBuilder.setHeader(":scheme", "http"); // always in this case
+ headersBuilder.setHeader(":authority", host);
String path = uri.getRawPath();
if (uri.getRawQuery() != null)
path = path + "?" + uri.getRawQuery();
- headers.setHeader(":path", path);
+ headersBuilder.setHeader(":path", path);
Queue q = new Queue(sentinel);
byte[] body = getRequestBody(request);
- addHeaders(getHeaders(request.headers), headers);
- headers.setHeader("Content-length", Integer.toString(body.length));
+ addHeaders(getHeaders(request.headers), headersBuilder);
+ headersBuilder.setHeader("Content-length", Integer.toString(body.length));
+ HttpHeaders headers = headersBuilder.build();
addRequestBodyToQueue(body, q);
streams.put(1, q);
@@ -569,7 +570,7 @@
}
}
boolean endStreamReceived = endStream;
- HttpHeadersImpl headers = decodeHeaders(frames);
+ HttpHeaders headers = decodeHeaders(frames);
// Strict to assert Client correctness. Not all servers are as strict,
// but some are known to be.
@@ -593,7 +594,7 @@
// for this stream/request delivered on Q
@SuppressWarnings({"rawtypes","unchecked"})
- void handleRequest(HttpHeadersImpl headers,
+ void handleRequest(HttpHeaders headers,
Queue queue,
int streamid,
boolean endStreamReceived)
@@ -607,7 +608,6 @@
String authority = headers.firstValue(":authority").orElse("");
//System.out.println("authority = " + authority);
System.err.printf("TestServer: %s %s\n", method, path);
- HttpHeadersImpl rspheaders = createNewResponseHeaders();
int winsize = clientSettings.getParameter(
SettingsFrame.INITIAL_WINDOW_SIZE);
//System.err.println ("Stream window size = " + winsize);
@@ -627,8 +627,9 @@
String us = scheme + "://" + authority + path;
URI uri = new URI(us);
boolean pushAllowed = clientSettings.getParameter(SettingsFrame.ENABLE_PUSH) == 1;
+ HttpHeadersBuilder rspheadersBuilder = createNewHeadersBuilder();
Http2TestExchange exchange = exchangeSupplier.get(streamid, method,
- headers, rspheaders, uri, bis, getSSLSession(),
+ headers, rspheadersBuilder, uri, bis, getSSLSession(),
bos, this, pushAllowed);
// give to user
@@ -655,8 +656,8 @@
}
}
- protected HttpHeadersImpl createNewResponseHeaders() {
- return new HttpHeadersImpl();
+ protected HttpHeadersBuilder createNewHeadersBuilder() {
+ return new HttpHeadersBuilder();
}
private SSLSession getSSLSession() {
@@ -745,7 +746,8 @@
}
}
- List<ByteBuffer> encodeHeaders(HttpHeadersImpl headers) {
+ /** Encodes an group of headers, without any ordering guarantees. */
+ List<ByteBuffer> encodeHeaders(HttpHeaders headers) {
List<ByteBuffer> buffers = new LinkedList<>();
ByteBuffer buf = getBuffer();
@@ -770,6 +772,30 @@
return buffers;
}
+ /** Encodes an ordered list of headers. */
+ List<ByteBuffer> encodeHeadersOrdered(List<Map.Entry<String,String>> headers) {
+ List<ByteBuffer> buffers = new LinkedList<>();
+
+ ByteBuffer buf = getBuffer();
+ boolean encoded;
+ for (Map.Entry<String, String> entry : headers) {
+ String value = entry.getValue();
+ String key = entry.getKey().toLowerCase();
+ do {
+ hpackOut.header(key, value);
+ encoded = hpackOut.encode(buf);
+ if (!encoded) {
+ buf.flip();
+ buffers.add(buf);
+ buf = getBuffer();
+ }
+ } while (!encoded);
+ }
+ buf.flip();
+ buffers.add(buf);
+ return buffers;
+ }
+
static void closeIgnore(Closeable c) {
try {
c.close();
@@ -847,9 +873,9 @@
// returns a minimal response with status 200
// that is the response to the push promise just sent
private ResponseHeaders getPushResponse(int streamid) {
- HttpHeadersImpl h = createNewResponseHeaders();
- h.addHeader(":status", "200");
- ResponseHeaders oh = new ResponseHeaders(h);
+ HttpHeadersBuilder hb = createNewHeadersBuilder();
+ hb.addHeader(":status", "200");
+ ResponseHeaders oh = new ResponseHeaders(hb.build());
oh.streamid(streamid);
oh.setFlag(HeaderFrame.END_HEADERS);
return oh;
@@ -1082,9 +1108,9 @@
// for the hashmap.
static class ResponseHeaders extends Http2Frame {
- HttpHeadersImpl headers;
+ HttpHeaders headers;
- ResponseHeaders(HttpHeadersImpl headers) {
+ ResponseHeaders(HttpHeaders headers) {
super(0, 0);
this.headers = headers;
}
--- a/test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java Fri May 25 16:13:11 2018 +0100
@@ -21,22 +21,22 @@
* questions.
*/
-import java.io.*;
-import java.net.*;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpHeaders;
import jdk.internal.net.http.frame.Http2Frame;
// will be converted to a PushPromiseFrame in the writeLoop
// a thread is then created to produce the DataFrames from the InputStream
class OutgoingPushPromise extends Http2Frame {
- final HttpHeadersImpl headers;
+ final HttpHeaders headers;
final URI uri;
final InputStream is;
final int parentStream; // not the pushed streamid
public OutgoingPushPromise(int parentStream,
URI uri,
- HttpHeadersImpl headers,
+ HttpHeaders headers,
InputStream is) {
super(0,0);
this.uri = uri;
--- a/test/jdk/java/net/httpclient/http2/server/PushHandler.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/server/PushHandler.java Fri May 25 16:13:11 2018 +0100
@@ -23,11 +23,16 @@
import java.io.*;
import java.net.*;
+import java.net.http.HttpHeaders;
import java.nio.file.*;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiPredicate;
public class PushHandler implements Http2Handler {
+ static final BiPredicate<String,String> ACCEPT_ALL = (x, y) -> true;
+
final Path tempFile;
final int loops;
final long file_size;
@@ -50,8 +55,8 @@
for (int i=0; i<loops; i++) {
InputStream is = new FileInputStream(tempFile.toFile());
URI u = requestURI.resolve("/x/y/z/" + Integer.toString(i));
- HttpHeadersImpl h = new HttpHeadersImpl();
- h.addHeader("X-foo", "bar");
+ HttpHeaders h = HttpHeaders.of(Map.of("X-foo", List.of("bar")),
+ ACCEPT_ALL);
ee.serverPush(u, h, is);
}
System.err.println ("Server: sent all pushes");
--- a/test/jdk/java/net/httpclient/offline/FixedHttpHeaders.java Mon May 28 17:22:37 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2018, 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.
- */
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.TreeMap;
-import java.net.http.HttpHeaders;
-
-/**
- * An HttpHeaders consisting of the given name value pairs.
- */
-public class FixedHttpHeaders extends HttpHeaders {
-
- private final Map<String, List<String>> map =
- new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-
- @Override
- public Map<String, List<String>> map() {
- return map;
- }
-
- /**
- * Creates an HttpHeaders of the given name value pairs.
- */
- public static HttpHeaders of(String... params) {
- Objects.requireNonNull(params);
- if ((params.length & 0x1) != 0)
- throw new IllegalArgumentException("odd number of params");
- FixedHttpHeaders headers = new FixedHttpHeaders();
- for (int i = 0; i < params.length; i += 2) {
- String name = params[i];
- String value = params[i + 1];
- headers.map.computeIfAbsent(name, k -> new ArrayList<>(1))
- .add(value);
- }
- return headers;
- }
-}
--- a/test/jdk/java/net/httpclient/offline/OfflineTesting.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/offline/OfflineTesting.java Fri May 25 16:13:11 2018 +0100
@@ -24,19 +24,26 @@
/*
* @test
* @summary Demonstrates how to achieve testing without network connections
- * @build FixedHttpHeaders DelegatingHttpClient FixedHttpResponse FixedResponseHttpClient
+ * @build DelegatingHttpClient FixedHttpResponse FixedResponseHttpClient
* @run testng/othervm OfflineTesting
*/
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiPredicate;
import org.testng.annotations.Test;
+import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@@ -49,8 +56,8 @@
return FixedResponseHttpClient.createClientFrom(
HttpClient.newBuilder(),
200,
- FixedHttpHeaders.of("Server", "nginx",
- "Content-Type", "text/html"),
+ headersOf("Server", "nginx",
+ "Content-Type", "text/html"),
"A response message");
}
@@ -94,11 +101,11 @@
HttpClient client = FixedResponseHttpClient.createClientFrom(
HttpClient.newBuilder(),
404,
- FixedHttpHeaders.of("Connection", "keep-alive",
- "Content-Length", "162",
- "Content-Type", "text/html",
- "Date", "Mon, 15 Jan 2018 15:01:16 GMT",
- "Server", "nginx"),
+ headersOf("Connection", "keep-alive",
+ "Content-Length", "162",
+ "Content-Type", "text/html",
+ "Date", "Mon, 15 Jan 2018 15:01:16 GMT",
+ "Server", "nginx"),
"<html>\n" +
"<head><title>404 Not Found</title></head>\n" +
"<body bgcolor=\"white\">\n" +
@@ -126,7 +133,7 @@
HttpClient client = FixedResponseHttpClient.createEchoClient(
HttpClient.newBuilder(),
200,
- FixedHttpHeaders.of("Connection", "keep-alive"));
+ headersOf("Connection", "keep-alive"));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/echo"))
@@ -146,7 +153,7 @@
HttpClient client = FixedResponseHttpClient.createEchoClient(
HttpClient.newBuilder(),
200,
- FixedHttpHeaders.of("Connection", "keep-alive"));
+ headersOf("Connection", "keep-alive"));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/echo"))
@@ -158,4 +165,26 @@
assertEquals(response.statusCode(), 200);
assertEquals(response.body(), "Hello chegar!!");
}
+
+ // ---
+
+ public static IllegalArgumentException newIAE(String message, Object... args) {
+ return new IllegalArgumentException(format(message, args));
+ }
+
+ static final BiPredicate<String,String> ACCEPT_ALL = (x, y) -> true;
+
+ static HttpHeaders headersOf(String... params) {
+ Map<String,List<String>> map = new HashMap<>();
+ requireNonNull(params);
+ if (params.length == 0 || params.length % 2 != 0) {
+ throw newIAE("wrong number, %d, of parameters", params.length);
+ }
+ for (int i = 0; i < params.length; i += 2) {
+ String name = params[i];
+ String value = params[i + 1];
+ map.put(name, List.of(value));
+ }
+ return HttpHeaders.of(map, ACCEPT_ALL);
+ }
}
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/AuthenticationFilterTest.java Mon May 28 17:22:37 2018 +0100
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/AuthenticationFilterTest.java Fri May 25 16:13:11 2018 +0100
@@ -23,7 +23,7 @@
package jdk.internal.net.http;
-import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersBuilder;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.annotations.AfterClass;
@@ -35,17 +35,19 @@
import java.net.ProxySelector;
import java.net.URI;
import java.net.URL;
-import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Base64;
+import java.util.Collections;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.net.http.HttpClient.Version;
+import java.util.function.BiPredicate;
import static java.lang.String.format;
import static java.lang.System.out;
@@ -163,6 +165,8 @@
});
}
+ static final BiPredicate<String,String> ACCEPT_ALL = (x, y) -> true;
+
private void doTestAuthentication(String uri, Version v, String proxy) throws Exception {
int colon = proxy == null ? -1 : proxy.lastIndexOf(":");
ProxySelector ps = proxy == null ? NO_PROXY
@@ -197,8 +201,9 @@
Exchange<?> exchange = new Exchange<>(req, multi);
out.println("\nSimulating unauthenticated request to " + uri);
filter.request(req, multi);
- assertFalse(req.getSystemHeaders().firstValue(authorization(true)).isPresent());
- assertFalse(req.getSystemHeaders().firstValue(authorization(false)).isPresent());
+ HttpHeaders hdrs = req.getSystemHeadersBuilder().build();
+ assertFalse(hdrs.firstValue(authorization(true)).isPresent());
+ assertFalse(hdrs.firstValue(authorization(false)).isPresent());
assertEquals(authenticator.COUNTER.get(), 0);
// Creates the Response to the first request, and call filter.response
@@ -207,9 +212,10 @@
// credentials, and will also cache the credentials in the multi exchange.
// The credentials shouldn't be put in the cache until the 200 response
// for that request arrives.
- HttpHeadersImpl headers = new HttpHeadersImpl();
- headers.addHeader(authenticate(proxy!=null),
- "Basic realm=\"earth\"");
+ HttpHeadersBuilder headersBuilder = new HttpHeadersBuilder();
+ headersBuilder.addHeader(authenticate(proxy!=null),
+ "Basic realm=\"earth\"");
+ HttpHeaders headers = headersBuilder.build();
Response response = new Response(req, exchange, headers, null, unauthorized, v);
out.println("Simulating " + unauthorized
+ " response from " + uri);
@@ -218,7 +224,7 @@
out.println("Checking filter's response to "
+ unauthorized + " from " + uri);
assertTrue(next != null, "next should not be null");
- String[] up = check(reqURI, next.getSystemHeaders(), proxy);
+ String[] up = check(reqURI, next.getSystemHeadersBuilder().build(), proxy);
assertEquals(authenticator.COUNTER.get(), 1);
// Now simulate a new successful exchange to get the credentials in the cache
@@ -230,10 +236,12 @@
exchange = new Exchange<>(next, multi);
filter.request(next, multi);
out.println("Checking credentials in request header after filter for " + uri);
- check(reqURI, next.getSystemHeaders(), proxy);
- check(next.uri(), next.getSystemHeaders(), proxy);
+ hdrs = next.getSystemHeadersBuilder().build();
+ check(reqURI, hdrs, proxy);
+ check(next.uri(), hdrs, proxy);
out.println("Simulating successful response 200 from " + uri);
- response = new Response(next, exchange, new HttpHeadersImpl(), null, 200, v);
+ HttpHeaders h = HttpHeaders.of(Collections.emptyMap(), ACCEPT_ALL);
+ response = new Response(next, exchange,h, null, 200, v);
next = filter.response(response);
assertTrue(next == null, "next should be null");
assertEquals(authenticator.COUNTER.get(), 1);
@@ -263,7 +271,7 @@
filter.request(req2, multi2);
out.println("Check that filter has added credentials from cache for " + reqURI2
+ " with proxy " + req2.proxy());
- String[] up2 = check(reqURI, req2.getSystemHeaders(), proxy);
+ String[] up2 = check(reqURI, req2.getSystemHeadersBuilder().build(), proxy);
assertTrue(Arrays.deepEquals(up, up2), format("%s:%s != %s:%s", up2[0], up2[1], up[0], up[1]));
assertEquals(authenticator.COUNTER.get(), 1);
@@ -293,24 +301,25 @@
HttpResponse.BodyHandlers.replacing(null),
null, AccessController.getContext());
filter.request(req3, multi3);
+ HttpHeaders h3 = req3.getSystemHeadersBuilder().build();
if (proxy == null) {
out.println("Check that filter has not added proxy credentials from cache for " + reqURI3);
- assert !req3.getSystemHeaders().firstValue(authorization(true)).isPresent()
+ assert !h3.firstValue(authorization(true)).isPresent()
: format("Unexpected proxy credentials found: %s",
- java.util.stream.Stream.of(getAuthorization(req3.getSystemHeaders(), true))
+ java.util.stream.Stream.of(getAuthorization(req3.getSystemHeadersBuilder().build(), true))
.collect(joining(":")));
- assertFalse(req3.getSystemHeaders().firstValue(authorization(true)).isPresent());
+ assertFalse(h3.firstValue(authorization(true)).isPresent());
} else {
out.println("Check that filter has added proxy credentials from cache for " + reqURI3);
- String[] up3 = check(reqURI, req3.getSystemHeaders(), proxy);
+ String[] up3 = check(reqURI, h3, proxy);
assertTrue(Arrays.deepEquals(up, up3), format("%s:%s != %s:%s", up3[0], up3[1], up[0], up[1]));
}
out.println("Check that filter has not added server credentials from cache for " + reqURI3);
- assert !req3.getSystemHeaders().firstValue(authorization(false)).isPresent()
+ assert !h3.firstValue(authorization(false)).isPresent()
: format("Unexpected server credentials found: %s",
- java.util.stream.Stream.of(getAuthorization(req3.getSystemHeaders(), false))
+ java.util.stream.Stream.of(getAuthorization(h3, false))
.collect(joining(":")));
- assertFalse(req3.getSystemHeaders().firstValue(authorization(false)).isPresent());
+ assertFalse(h3.firstValue(authorization(false)).isPresent());
assertEquals(authenticator.COUNTER.get(), 1);
// Now we will verify that credentials for proxies are not used for servers and
@@ -341,23 +350,24 @@
filter.request(req4, multi4);
out.println("Check that filter has not added proxy credentials from cache for "
+ reqURI4 + " (proxy: " + req4.proxy() + ")");
- assert !req4.getSystemHeaders().firstValue(authorization(true)).isPresent()
+ HttpHeaders h4 = req4.getSystemHeadersBuilder().build();
+ assert !h4.firstValue(authorization(true)).isPresent()
: format("Unexpected proxy credentials found: %s",
- java.util.stream.Stream.of(getAuthorization(req4.getSystemHeaders(), true))
+ java.util.stream.Stream.of(getAuthorization(h4, true))
.collect(joining(":")));
- assertFalse(req4.getSystemHeaders().firstValue(authorization(true)).isPresent());
+ assertFalse(h4.firstValue(authorization(true)).isPresent());
if (proxy != null) {
out.println("Check that filter has not added server credentials from cache for "
+ reqURI4 + " (proxy: " + req4.proxy() + ")");
- assert !req4.getSystemHeaders().firstValue(authorization(false)).isPresent()
+ assert !h4.firstValue(authorization(false)).isPresent()
: format("Unexpected server credentials found: %s",
- java.util.stream.Stream.of(getAuthorization(req4.getSystemHeaders(), false))
+ java.util.stream.Stream.of(getAuthorization(h4, false))
.collect(joining(":")));
- assertFalse(req4.getSystemHeaders().firstValue(authorization(false)).isPresent());
+ assertFalse(h4.firstValue(authorization(false)).isPresent());
} else {
out.println("Check that filter has added server credentials from cache for "
+ reqURI4 + " (proxy: " + req4.proxy() + ")");
- String[] up4 = check(reqURI, req4.getSystemHeaders(), proxy);
+ String[] up4 = check(reqURI, h4, proxy);
assertTrue(Arrays.deepEquals(up, up4), format("%s:%s != %s:%s", up4[0], up4[1], up[0], up[1]));
}
assertEquals(authenticator.COUNTER.get(), 1);
@@ -381,24 +391,26 @@
filter.request(req5, multi5);
out.println("Check that filter has not added server credentials from cache for "
+ reqURI + " (proxy: " + req5.proxy() + ")");
- assert !req5.getSystemHeaders().firstValue(authorization(false)).isPresent()
+ HttpHeaders h5 = req5.getSystemHeadersBuilder().build();
+ assert !h5.firstValue(authorization(false)).isPresent()
: format("Unexpected server credentials found: %s",
- java.util.stream.Stream.of(getAuthorization(req5.getSystemHeaders(), false))
+ java.util.stream.Stream.of(getAuthorization(h5, false))
.collect(joining(":")));
- assertFalse(req5.getSystemHeaders().firstValue(authorization(false)).isPresent());
+ assertFalse(h5.firstValue(authorization(false)).isPresent());
out.println("Check that filter has not added proxy credentials from cache for "
+ reqURI + " (proxy: " + req5.proxy() + ")");
- assert !req5.getSystemHeaders().firstValue(authorization(true)).isPresent()
+ assert !h5.firstValue(authorization(true)).isPresent()
: format("Unexpected proxy credentials found: %s",
- java.util.stream.Stream.of(getAuthorization(req5.getSystemHeaders(), true))
+ java.util.stream.Stream.of(getAuthorization(h5, true))
.collect(joining(":")));
- assertFalse(req5.getSystemHeaders().firstValue(authorization(true)).isPresent());
+ assertFalse(h5.firstValue(authorization(true)).isPresent());
assertEquals(authenticator.COUNTER.get(), 1);
// Now simulate a 401 response from the server
- HttpHeadersImpl headers5 = new HttpHeadersImpl();
- headers5.addHeader(authenticate(false),
- "Basic realm=\"earth\"");
+ HttpHeadersBuilder headers5Builder = new HttpHeadersBuilder();
+ headers5Builder.addHeader(authenticate(false),
+ "Basic realm=\"earth\"");
+ HttpHeaders headers5 = headers5Builder.build();
unauthorized = 401;
Response response5 = new Response(req5, exchange5, headers5, null, unauthorized, v);
out.println("Simulating " + unauthorized
@@ -409,12 +421,13 @@
out.println("Checking filter's response to "
+ unauthorized + " from " + uri);
assertTrue(next5 != null, "next5 should not be null");
- String[] up5 = check(reqURI, next5.getSystemHeaders(), null);
+ String[] up5 = check(reqURI, next5.getSystemHeadersBuilder().build(), null);
// now simulate a 200 response from the server
exchange5 = new Exchange<>(next5, multi5);
filter.request(next5, multi5);
- response5 = new Response(next5, exchange5, new HttpHeadersImpl(), null, 200, v);
+ h = HttpHeaders.of(Map.of(), ACCEPT_ALL);
+ response5 = new Response(next5, exchange5, h, null, 200, v);
filter.response(response5);
assertEquals(authenticator.COUNTER.get(), 2);
@@ -432,10 +445,11 @@
filter.request(req6, multi6);
out.println("Check that filter has added server credentials from cache for "
+ reqURI + " (proxy: " + req6.proxy() + ")");
- check(reqURI, req6.getSystemHeaders(), null);
+ HttpHeaders h6 = req6.getSystemHeadersBuilder().build();
+ check(reqURI, h6, null);
out.println("Check that filter has added proxy credentials from cache for "
+ reqURI + " (proxy: " + req6.proxy() + ")");
- String[] up6 = check(reqURI, req6.getSystemHeaders(), proxy);
+ String[] up6 = check(reqURI, h6, proxy);
assertTrue(Arrays.deepEquals(up, up6), format("%s:%s != %s:%s", up6[0], up6[1], up[0], up[1]));
assertEquals(authenticator.COUNTER.get(), 2);
}
@@ -456,18 +470,19 @@
out.println("Check that filter has not added server credentials from cache for "
+ reqURI7 + " (proxy: " + req7.proxy() + ") [resolved uri: "
+ reqURI7.resolve(".") + " should not match " + reqURI.resolve(".") + "]");
- assert !req7.getSystemHeaders().firstValue(authorization(false)).isPresent()
+ HttpHeaders h7 = req7.getSystemHeadersBuilder().build();
+ assert !h7.firstValue(authorization(false)).isPresent()
: format("Unexpected server credentials found: %s",
- java.util.stream.Stream.of(getAuthorization(req7.getSystemHeaders(), false))
+ java.util.stream.Stream.of(getAuthorization(h7, false))
.collect(joining(":")));
- assertFalse(req7.getSystemHeaders().firstValue(authorization(false)).isPresent());
+ assertFalse(h7.firstValue(authorization(false)).isPresent());
out.println("Check that filter has not added proxy credentials from cache for "
+ reqURI7 + " (proxy: " + req7.proxy() + ")");
- assert !req7.getSystemHeaders().firstValue(authorization(true)).isPresent()
+ assert !h7.firstValue(authorization(true)).isPresent()
: format("Unexpected proxy credentials found: %s",
- java.util.stream.Stream.of(getAuthorization(req7.getSystemHeaders(), true))
+ java.util.stream.Stream.of(getAuthorization(h7, true))
.collect(joining(":")));
- assertFalse(req7.getSystemHeaders().firstValue(authorization(true)).isPresent());
+ assertFalse(h7.firstValue(authorization(true)).isPresent());
assertEquals(authenticator.COUNTER.get(), 1);
}