< prev index next >

src/java.net.http/share/classes/java/net/http/HttpHeaders.java

Print this page
rev 50983 : [mq]: finalHttpHeaders
rev 50501 : http-client-branch: Merge with default
rev 50451 : 8197564: HTTP Client implementation
Reviewed-by: chegar, dfuchs, michaelm, prappo
Contributed-by: Chris Hegarty <chris.hegarty@oracle.com>, Daniel Fuchs <daniel.fuchs@oracle.com>, Michael McMahon <michael.x.mcmahon@oracle.com>, Pavel Rappo <pavel.rappo@oracle.com>
rev 49299 : http-client-branch: CSR review commet - outboard pre-defined BP/BH/BS
rev 49082 : http-client-branch: revert overzealous use of @linkplain
rev 49074 : http-client-branch: move to standard package and module name

*** 23,88 **** * questions. */ package java.net.http; import java.util.List; 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 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> 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. * * <p> {@code HttpHeaders} instances are immutable. * * @since 11 */ ! public abstract class HttpHeaders { /** ! * Creates an HttpHeaders. ! */ ! protected 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()} * * @param name the header name ! * @return an {@code Optional<String>} for the first named value */ 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()} * * @param name the header name * @return an {@code OptionalLong} * @throws NumberFormatException if a value is found, but does not parse as * a Long --- 23,94 ---- * questions. */ 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 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 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 ! * {@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 final class HttpHeaders { /** ! * 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>} 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 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} * @throws NumberFormatException if a value is found, but does not parse as * a Long
*** 90,124 **** public OptionalLong firstValueAsLong(String name) { return allValues(name).stream().mapToLong(Long::valueOf).findFirst(); } /** ! * 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. * * @param name the header name ! * @return a List of 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()); } /** * Returns an unmodifiable multi Map view of this HttpHeaders. * * @return the Map */ ! public abstract Map<String, List<String>> map(); /** * Tests this HTTP headers instance for equality with the given object. * * <p> If the given object is not an {@code HttpHeaders} then this --- 96,128 ---- public OptionalLong firstValueAsLong(String name) { return allValues(name).stream().mapToLong(Long::valueOf).findFirst(); } /** ! * 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 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 : List.of(); } /** * Returns an unmodifiable multi Map view of this HttpHeaders. * * @return the Map */ ! public Map<String,List<String>> map() { ! return headers; ! } /** * Tests this HTTP headers instance for equality with the given object. * * <p> If the given object is not an {@code HttpHeaders} then this
*** 163,168 **** --- 167,252 ---- sb.append(super.toString()).append(" { "); sb.append(map()); 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)); + } }
< prev index next >