src/java.net.http/share/classes/java/net/http/HttpHeaders.java
changeset 50681 4254bed3c09d
parent 49765 ee6f7a61f3a5
child 56795 03ece2518428
equal deleted inserted replaced
50678:818a23db260c 50681:4254bed3c09d
    23  * questions.
    23  * questions.
    24  */
    24  */
    25 
    25 
    26 package java.net.http;
    26 package java.net.http;
    27 
    27 
       
    28 import java.util.ArrayList;
    28 import java.util.List;
    29 import java.util.List;
       
    30 import java.util.Locale;
    29 import java.util.Map;
    31 import java.util.Map;
    30 import java.util.Optional;
    32 import java.util.Optional;
    31 import java.util.OptionalLong;
    33 import java.util.OptionalLong;
    32 import static java.util.Collections.emptyList;
    34 import java.util.TreeMap;
    33 import static java.util.Collections.unmodifiableList;
    35 import java.util.TreeSet;
       
    36 import java.util.function.BiPredicate;
       
    37 import static java.lang.String.CASE_INSENSITIVE_ORDER;
       
    38 import static java.util.Collections.unmodifiableMap;
    34 import static java.util.Objects.requireNonNull;
    39 import static java.util.Objects.requireNonNull;
    35 
    40 
    36 /**
    41 /**
    37  * A read-only view of a set of HTTP headers.
    42  * A read-only view of a set of HTTP headers.
    38  *
    43  *
    39  * <p> An {@code HttpHeaders} is not created directly, but rather returned from
    44  * <p> An {@code HttpHeaders} is not typically created directly, but rather
    40  * an {@link HttpResponse HttpResponse}. Specific HTTP headers can be set for
    45  * returned from an {@link HttpRequest#headers() HttpRequest} or an
    41  * {@linkplain HttpRequest requests} through the one of the request builder's
    46  * {@link HttpResponse#headers() HttpResponse}. Specific HTTP headers can be
    42  * {@link HttpRequest.Builder#header(String, String) headers} methods.
    47  * set for a {@linkplain HttpRequest request} through one of the request
       
    48  * builder's {@link HttpRequest.Builder#header(String, String) headers} methods.
    43  *
    49  *
    44  * <p> The methods of this class ( that accept a String header name ), and the
    50  * <p> The methods of this class ( that accept a String header name ), and the
    45  * Map returned by the {@link #map() map} method, operate without regard to
    51  * {@code Map} returned by the {@link #map() map} method, operate without regard
    46  * case when retrieving the header value.
    52  * to case when retrieving the header value(s).
       
    53  *
       
    54  * <p> An HTTP header name may appear more than once in the HTTP protocol. As
       
    55  * such, headers are represented as a name and a list of values. Each occurrence
       
    56  * of a header value is added verbatim, to the appropriate header name list,
       
    57  * without interpreting its value. In particular, {@code HttpHeaders} does not
       
    58  * perform any splitting or joining of comma separated header value strings. The
       
    59  * order of elements in a header value list is preserved when {@link
       
    60  * HttpRequest.Builder#header(String, String) building} a request. For
       
    61  * responses, the order of elements in a header value list is the order in which
       
    62  * they were received. The {@code Map} returned by the {@code map} method,
       
    63  * however, does not provide any guarantee with regard to the ordering of its
       
    64  * entries.
    47  *
    65  *
    48  * <p> {@code HttpHeaders} instances are immutable.
    66  * <p> {@code HttpHeaders} instances are immutable.
    49  *
    67  *
    50  * @since 11
    68  * @since 11
    51  */
    69  */
    52 public abstract class HttpHeaders {
    70 public final class HttpHeaders {
    53 
    71 
    54     /**
    72     /**
    55      * Creates an HttpHeaders.
    73      * Returns an {@link Optional} containing the first header string value of
    56      */
    74      * the given named (and possibly multi-valued) header. If the header is not
    57     protected HttpHeaders() {}
    75      * present, then the returned {@code Optional} is empty.
    58 
       
    59     /**
       
    60      * Returns an {@link Optional} containing the first value of the given named
       
    61      * (and possibly multi-valued) header. If the header is not present, then
       
    62      * the returned {@code Optional} is empty.
       
    63      *
       
    64      * @implSpec
       
    65      * The default implementation invokes
       
    66      * {@code allValues(name).stream().findFirst()}
       
    67      *
    76      *
    68      * @param name the header name
    77      * @param name the header name
    69      * @return an {@code Optional<String>} for the first named value
    78      * @return an {@code Optional<String>} containing the first named header
       
    79      *         string value, if present
    70      */
    80      */
    71     public Optional<String> firstValue(String name) {
    81     public Optional<String> firstValue(String name) {
    72         return allValues(name).stream().findFirst();
    82         return allValues(name).stream().findFirst();
    73     }
    83     }
    74 
    84 
    75     /**
    85     /**
    76      * Returns an {@link OptionalLong} containing the first value of the
    86      * Returns an {@link OptionalLong} containing the first header string value
    77      * named header field. If the header is not present, then the Optional is
    87      * of the named header field. If the header is not present, then the
    78      * empty. If the header is present but contains a value that does not parse
    88      * Optional is empty. If the header is present but contains a value that
    79      * as a {@code Long} value, then an exception is thrown.
    89      * does not parse as a {@code Long} value, then an exception is thrown.
    80      *
       
    81      * @implSpec
       
    82      * The default implementation invokes
       
    83      * {@code allValues(name).stream().mapToLong(Long::valueOf).findFirst()}
       
    84      *
    90      *
    85      * @param name the header name
    91      * @param name the header name
    86      * @return  an {@code OptionalLong}
    92      * @return  an {@code OptionalLong}
    87      * @throws NumberFormatException if a value is found, but does not parse as
    93      * @throws NumberFormatException if a value is found, but does not parse as
    88      *                               a Long
    94      *                               a Long
    90     public OptionalLong firstValueAsLong(String name) {
    96     public OptionalLong firstValueAsLong(String name) {
    91         return allValues(name).stream().mapToLong(Long::valueOf).findFirst();
    97         return allValues(name).stream().mapToLong(Long::valueOf).findFirst();
    92     }
    98     }
    93 
    99 
    94     /**
   100     /**
    95      * Returns an unmodifiable List of all of the values of the given named
   101      * Returns an unmodifiable List of all of the header string values of the
    96      * header. Always returns a List, which may be empty if the header is not
   102      * given named header. Always returns a List, which may be empty if the
    97      * present.
   103      * header is not present.
    98      *
       
    99      * @implSpec
       
   100      * The default implementation invokes, among other things, the
       
   101      * {@code map().get(name)} to retrieve the list of header values.
       
   102      *
   104      *
   103      * @param name the header name
   105      * @param name the header name
   104      * @return a List of String values
   106      * @return a List of headers string values
   105      */
   107      */
   106     public List<String> allValues(String name) {
   108     public List<String> allValues(String name) {
   107         requireNonNull(name);
   109         requireNonNull(name);
   108         List<String> values = map().get(name);
   110         List<String> values = map().get(name);
   109         // Making unmodifiable list out of empty in order to make a list which
   111         // Making unmodifiable list out of empty in order to make a list which
   110         // throws UOE unconditionally
   112         // throws UOE unconditionally
   111         return values != null ? values : unmodifiableList(emptyList());
   113         return values != null ? values : List.of();
   112     }
   114     }
   113 
   115 
   114     /**
   116     /**
   115      * Returns an unmodifiable multi Map view of this HttpHeaders.
   117      * Returns an unmodifiable multi Map view of this HttpHeaders.
   116      *
   118      *
   117      * @return the Map
   119      * @return the Map
   118      */
   120      */
   119     public abstract Map<String, List<String>> map();
   121     public Map<String,List<String>> map() {
       
   122         return headers;
       
   123     }
   120 
   124 
   121     /**
   125     /**
   122      * Tests this HTTP headers instance for equality with the given object.
   126      * Tests this HTTP headers instance for equality with the given object.
   123      *
   127      *
   124      * <p> If the given object is not an {@code HttpHeaders} then this
   128      * <p> If the given object is not an {@code HttpHeaders} then this
   147      * {@link Object#hashCode Object.hashCode} method.
   151      * {@link Object#hashCode Object.hashCode} method.
   148      *
   152      *
   149      * @return the hash-code value for this HTTP headers
   153      * @return the hash-code value for this HTTP headers
   150      */
   154      */
   151     public final int hashCode() {
   155     public final int hashCode() {
   152         return map().hashCode();
   156         int h = 0;
       
   157         for (Map.Entry<String, List<String>> e : map().entrySet()) {
       
   158             h += entryHash(e);
       
   159         }
       
   160         return h;
   153     }
   161     }
   154 
   162 
   155     /**
   163     /**
   156      * Returns this HTTP headers as a string.
   164      * Returns this HTTP headers as a string.
   157      *
   165      *
   163         sb.append(super.toString()).append(" { ");
   171         sb.append(super.toString()).append(" { ");
   164         sb.append(map());
   172         sb.append(map());
   165         sb.append(" }");
   173         sb.append(" }");
   166         return sb.toString();
   174         return sb.toString();
   167     }
   175     }
       
   176 
       
   177     /**
       
   178      * Returns an HTTP headers from the given map. The given map's key
       
   179      * represents the header name, and its value the list of string header
       
   180      * values for that header name.
       
   181      *
       
   182      * <p> An HTTP header name may appear more than once in the HTTP protocol.
       
   183      * Such, <i>multi-valued</i>, headers must be represented by a single entry
       
   184      * in the given map, whose entry value is a list that represents the
       
   185      * multiple header string values. Leading and trailing whitespaces are
       
   186      * removed from all string values retrieved from the given map and its lists
       
   187      * before processing. Only headers that, after filtering, contain at least
       
   188      * one, possibly empty string, value will be added to the HTTP headers.
       
   189      *
       
   190      * @apiNote The primary purpose of this method is for testing frameworks.
       
   191      * Per-request headers can be set through one of the {@code HttpRequest}
       
   192      * {@link HttpRequest.Builder#header(String, String) headers} methods.
       
   193      *
       
   194      * @param headerMap the map containing the header names and values
       
   195      * @param filter a filter that can be used to inspect each
       
   196      *               header-name-and-value pair in the given map to determine if
       
   197      *               it should, or should not, be added to the to the HTTP
       
   198      *               headers
       
   199      * @return an HTTP headers instance containing the given headers
       
   200      * @throws NullPointerException if any of: {@code headerMap}, a key or value
       
   201      *        in the given map, or an entry in the map's value list, or
       
   202      *        {@code filter}, is {@code null}
       
   203      * @throws IllegalArgumentException if the given {@code headerMap} contains
       
   204      *         any two keys that are equal ( without regard to case ); or if the
       
   205      *         given map contains any key whose length, after trimming
       
   206      *         whitespaces, is {@code 0}
       
   207      */
       
   208     public static HttpHeaders of(Map<String,List<String>> headerMap,
       
   209                                  BiPredicate<String,String> filter) {
       
   210         requireNonNull(headerMap);
       
   211         requireNonNull(filter);
       
   212         return headersOf(headerMap, filter);
       
   213     }
       
   214 
       
   215     // --
       
   216 
       
   217     private static final HttpHeaders NO_HEADERS = new HttpHeaders(Map.of());
       
   218 
       
   219     private final Map<String,List<String>> headers;
       
   220 
       
   221     private HttpHeaders(Map<String,List<String>> headers) {
       
   222         this.headers = headers;
       
   223     }
       
   224 
       
   225     private static final int entryHash(Map.Entry<String, List<String>> e) {
       
   226         String key = e.getKey();
       
   227         List<String> value = e.getValue();
       
   228         // we know that by construction key and values can't be null
       
   229         int keyHash = key.toLowerCase(Locale.ROOT).hashCode();
       
   230         int valueHash = value.hashCode();
       
   231         return keyHash ^ valueHash;
       
   232     }
       
   233 
       
   234     // Returns a new HTTP headers after performing a structural copy and filtering.
       
   235     private static HttpHeaders headersOf(Map<String,List<String>> map,
       
   236                                          BiPredicate<String,String> filter) {
       
   237         TreeMap<String,List<String>> other = new TreeMap<>(CASE_INSENSITIVE_ORDER);
       
   238         TreeSet<String> notAdded = new TreeSet<>(CASE_INSENSITIVE_ORDER);
       
   239         ArrayList<String> tempList = new ArrayList<>();
       
   240         map.forEach((key, value) -> {
       
   241             String headerName = requireNonNull(key).trim();
       
   242             if (headerName.isEmpty()) {
       
   243                 throw new IllegalArgumentException("empty key");
       
   244             }
       
   245             List<String> headerValues = requireNonNull(value);
       
   246             headerValues.forEach(headerValue -> {
       
   247                 headerValue = requireNonNull(headerValue).trim();
       
   248                 if (filter.test(headerName, headerValue)) {
       
   249                     tempList.add(headerValue);
       
   250                 }
       
   251             });
       
   252 
       
   253             if (tempList.isEmpty()) {
       
   254                 if (other.containsKey(headerName)
       
   255                         || notAdded.contains(headerName.toLowerCase(Locale.ROOT)))
       
   256                     throw new IllegalArgumentException("duplicate key: " + headerName);
       
   257                 notAdded.add(headerName.toLowerCase(Locale.ROOT));
       
   258             } else if (other.put(headerName, List.copyOf(tempList)) != null) {
       
   259                 throw new IllegalArgumentException("duplicate key: " + headerName);
       
   260             }
       
   261             tempList.clear();
       
   262         });
       
   263         return other.isEmpty() ? NO_HEADERS : new HttpHeaders(unmodifiableMap(other));
       
   264     }
   168 }
   265 }