All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.http.HttpFields Maven / Gradle / Ivy

There is a newer version: 12.1.0.alpha0
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.http;

import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * 

An ordered collection of {@link HttpField}s that represent the HTTP headers * or HTTP trailers of an HTTP request or an HTTP response.

* *

{@link HttpFields} is immutable and typically used in server-side HTTP requests * and client-side HTTP responses, while {@link HttpFields.Mutable} is mutable and * typically used in server-side HTTP responses and client-side HTTP requests.

* *

Access is always more efficient using {@link HttpHeader} keys rather than {@link String} field names.

* *

The primary implementations of {@code HttpFields} have been optimized assuming few * lookup operations, thus typically if many {@link HttpField}s need to looked up, it may be * better to use an {@link Iterator} to find multiple fields in a single iteration.

*/ public interface HttpFields extends Iterable, Supplier { /** *

A constant {@link HttpField} for the HTTP header:

*

{@code Expires: Thu, 01 Jan 1970 00:00:00 GMT}

*/ HttpField EXPIRES_01JAN1970 = new PreEncodedHttpField(HttpHeader.EXPIRES, DateGenerator.__01Jan1970); /** *

A constant {@link HttpField} for the HTTP header:

*

{@code Connection: close}

*/ HttpField CONNECTION_CLOSE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); /** *

A constant {@link HttpField} for the HTTP header:

*

{@code Connection: keep-alive}

*/ HttpField CONNECTION_KEEPALIVE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()); /** *

A constant {@link HttpField} for the HTTP header:

*

{@code Content-Length: 0}

*/ HttpField CONTENT_LENGTH_0 = new PreEncodedHttpField(HttpHeader.CONTENT_LENGTH, 0L); /** *

A constant for an immutable and empty {@link HttpFields}.

*/ HttpFields EMPTY = new EmptyHttpFields(); /** *

Returns an empty {@link Mutable} instance.

* * @return an empty {@link Mutable} instance */ static Mutable build() { return new org.eclipse.jetty.http.MutableHttpFields(); } /** *

Returns an empty {@link Mutable} instance with the given initial {@code capacity}.

*

The given {@code capacity} indicates the initial capacity of the storage for the * {@link HttpField}s.

*

When the capacity is exceeded, the storage is resized to accommodate the additional * {@link HttpField}s.

* * @param capacity the initial capacity of the storage for the {@link HttpField}s * @return an empty instance with the given initial {@code capacity} */ static Mutable build(int capacity) { return new org.eclipse.jetty.http.MutableHttpFields(capacity); } /** *

Returns a new {@link Mutable} instance containing a copy of all the * {@link HttpField}s of the given {@link HttpFields} parameter.

* * @param fields the {@link HttpFields} to copy * @return a new {@link Mutable} instance containing a copy of the given {@link HttpFields} */ static Mutable build(HttpFields fields) { return new org.eclipse.jetty.http.MutableHttpFields(fields); } /** *

Returns a new {@link Mutable} instance containing a copy of all the * {@link HttpField}s of the given {@link HttpFields}, replacing with the * given {@link HttpField} all the fields {@link HttpField}s with the same * name.

*

All existing {@link HttpField}s with the same name as the * replacing {@link HttpField} will be replaced by the given {@link HttpField}. * If there are no fields with that name, the given {@link HttpField} * is added to the returned {@link Mutable} instance.

* * @param fields the {@link HttpFields} to copy * @param replaceField the {@link HttpField} that replaces others with the same name * @return a new {@link Mutable} instance containing the given {@link HttpFields} * and the given replacement {@link HttpField} */ static Mutable build(HttpFields fields, HttpField replaceField) { return new org.eclipse.jetty.http.MutableHttpFields(fields, replaceField); } /** *

Returns a new {@link Mutable} instance containing a copy of all the * {@link HttpField}s of the given {@link HttpFields}, removing the * {@link HttpField}s with the given names.

* * @param fields the {@link HttpFields} to copy * @param removeFields the names of the fields to remove * @return a new {@link Mutable} instance containing the given {@link HttpFields} * without the fields with the given names */ static Mutable build(HttpFields fields, EnumSet removeFields) { return new org.eclipse.jetty.http.MutableHttpFields(fields, removeFields); } /** *

Returns an immutable {@link HttpFields} instance containing the given * {@link HttpField}s.

* * @param fields the {@link HttpField}s to be contained in the returned instance * @return a new immutable {@link HttpFields} instance with the given {@link HttpField}s */ static HttpFields from(HttpField... fields) { return new org.eclipse.jetty.http.ImmutableHttpFields(fields); } /** *

Supplies this instance, typically used to supply HTTP trailers.

* * @return this instance */ @Override default HttpFields get() { return this; } @Override default Iterator iterator() { return listIterator(); } /** * @return an iterator over the {@link HttpField}s in this {@code HttpFields}. * @see #listIterator(int) */ default ListIterator listIterator() { return listIterator(0); } /** * @return an iterator over the {@link HttpField}s in this {@code HttpFields} starting at the given index. * @see #listIterator() */ ListIterator listIterator(int index); /** *

Returns an immutable copy of this {@link HttpFields} instance.

* * @return a new immutable copy of this {@link HttpFields} instance */ default HttpFields asImmutable() { return HttpFields.build(this).asImmutable(); } /** *

Returns the HTTP/1.1 string representation of the {@link HttpField}s of this instance.

*

The format of each field is: {@code : \r\n}, and there will be * an additional {@code \r\n} after the last field, for example:

*
{@code
     * Host: localhost\r\n
     * Connection: close\r\n
     * \r\n
     * }
* * @return an HTTP/1.1 string representation of the {@link HttpField}s of this instance */ default String asString() { StringBuilder buffer = new StringBuilder(); for (HttpField field : this) { if (field != null) { String tmp = field.getName(); if (tmp != null) buffer.append(tmp); buffer.append(": "); tmp = field.getValue(); if (tmp != null) buffer.append(tmp); buffer.append("\r\n"); } } buffer.append("\r\n"); return buffer.toString(); } /** *

Returns whether this instance contains the given {@link HttpField}.

*

The comparison of {@link HttpField} names is case-insensitive via * {@link HttpField#isSameName(HttpField)}. * The comparison of the field value is case-insensitive via * {@link HttpField#contains(String)}.

* * @param field the {@link HttpField} to search for * @return whether this instance contains the given {@link HttpField} */ default boolean contains(HttpField field) { for (HttpField f : this) { if (f.isSameName(field) && (f.equals(field) || f.contains(field.getValue()))) return true; } return false; } /** *

Returns whether this instance contains the given {@link HttpHeader} * with the given value.

*

The comparison of the field value is case-insensitive via * {@link HttpField#contains(String)}.

* * @param header the name of the field to search for * @param value the value to search for * @return whether this instance contains the given * {@link HttpHeader} with the given value */ default boolean contains(HttpHeader header, String value) { for (HttpField f : this) { if (f.getHeader() == header && f.contains(value)) return true; } return false; } /** * Look for a value as the last value in a possible multivalued field. * Parameters and specifically quality parameters are not considered. * @param header The {@link HttpHeader} type to search for. * @param value The value to search for (case-insensitive) * @return True iff the value is contained in the field value entirely or * as the last element of a quoted comma separated list. * @see HttpField#containsLast(String) */ default boolean containsLast(HttpHeader header, String value) { for (ListIterator i = listIterator(size()); i.hasPrevious();) { HttpField f = i.previous(); if (f.getHeader() == header) return f.containsLast(value); } return false; } /** *

Returns whether this instance contains the given field name * with the given value.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}. * The comparison of the field value is case-insensitive via * {@link HttpField#contains(String)}.

* * @param name the case-insensitive field name to search for * @param value the field value to search for * @return whether this instance contains the given * field name with the given value */ default boolean contains(String name, String value) { for (HttpField f : this) { if (f.is(name) && f.contains(value)) return true; } return false; } /** *

Returns whether this instance contains the given field name.

* * @param header the field name to search for * @return whether this instance contains the given field name */ default boolean contains(HttpHeader header) { for (HttpField f : this) { if (f.getHeader() == header) return true; } return false; } /** *

Returns whether this instance contains at least one of the given * field names.

* * @param headers the field names to search among * @return whether this instance contains at least one of the given field names */ default boolean contains(EnumSet headers) { for (HttpField f : this) { if (headers.contains(f.getHeader())) return true; } return false; } /** *

Returns whether this instance contains the given field name.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}. If possible, it is more efficient to use * {@link #contains(HttpHeader)}. * * @param name the case-insensitive field name to search for * @return whether this instance contains the given field name * @see #contains(HttpHeader) */ default boolean contains(String name) { for (HttpField f : this) { if (f.is(name)) return true; } return false; } /** *

Returns the encoded value of the first field with the given field name, * or {@code null} if no such header is present.

*

In case of multi-valued fields, the returned value is the encoded * value, including commas and quotes, as returned by {@link HttpField#getValue()}.

* * @param header the field name to search for * @return the raw value of the first field with the given field name, * or {@code null} if no such header is present * @see HttpField#getValue() */ default String get(HttpHeader header) { for (HttpField f : this) { if (f.getHeader() == header) return f.getValue(); } return null; } /** *

Returns the encoded value of the last field with the given field name, * or {@code null} if no such header is present.

*

In case of multi-valued fields, the returned value is the encoded * value, including commas and quotes, as returned by {@link HttpField#getValue()}.

* * @param header the field name to search for * @return the raw value of the last field with the given field name, * or {@code null} if no such header is present * @see HttpField#getValue() */ default String getLast(HttpHeader header) { for (ListIterator i = listIterator(size()); i.hasPrevious();) { HttpField f = i.previous(); if (f.getHeader() == header) return f.getValue(); } return null; } /** *

Returns the encoded value of the first field with the given field name, * or {@code null} if no such field is present.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}. If possible, it is more efficient to use {@link #get(HttpHeader)}.

*

In case of multi-valued fields, the returned value is the encoded * value, including commas and quotes, as returned by {@link HttpField#getValue()}.

* * @param name the case-insensitive field name to search for * @return the raw value of the first field with the given field name, * or {@code null} if no such field is present * @see HttpField#getValue() * @see #get(HttpHeader) */ default String get(String name) { for (HttpField f : this) { if (f.is(name)) return f.getValue(); } return null; } /** *

Returns all the values of all the fields with the given field name.

*

In case of multi-valued fields, the multi-value of the field is split using * {@link QuotedCSV}, taking into account the given {@code keepQuotes} parameter.

* * @param header the field name to search for * @param keepQuotes whether the fields values should retain the quotes * @return a list of all the values of all the fields with the given name, * or an empty list if no such field name is present */ default List getCSV(HttpHeader header, boolean keepQuotes) { QuotedCSV values = null; for (HttpField f : this) { if (f.getHeader() == header) { if (values == null) values = new QuotedCSV(keepQuotes); values.addValue(f.getValue()); } } return values == null ? Collections.emptyList() : values.getValues(); } /** *

Returns all the values of all the fields with the given field name.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}.

*

In case of multi-valued fields, the multi-value of the field is split using * {@link QuotedCSV}, taking into account the given {@code keepQuotes} parameter.

* * @param name the case-insensitive field name to search for * @param keepQuotes whether the fields values should retain the quotes * @return a list of all the values of all the fields with the given name, * or an empty list if no such field name is present */ default List getCSV(String name, boolean keepQuotes) { QuotedCSV values = null; for (HttpField f : this) { if (f.is(name)) { if (values == null) values = new QuotedCSV(keepQuotes); values.addValue(f.getValue()); } } return values == null ? Collections.emptyList() : values.getValues(); } /** *

Returns the value of a date field as the number of milliseconds * since the Unix Epoch, or -1 if no such field is present.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}.

* * @param name the case-insensitive field name to search for * @return the value of the field as the number of milliseconds since Unix Epoch, * or -1 if no such field is present */ default long getDateField(String name) { return parseDateField(getField(name)); } /** *

Returns the value of a date field as the number of milliseconds * since the Unix Epoch, or -1 if no such field is present.

* * @param header the field name to search for * @return the value of the field as the number of milliseconds since Unix Epoch, * or -1 if no such field is present */ default long getDateField(HttpHeader header) { return parseDateField(getField(header)); } private static long parseDateField(HttpField field) { if (field == null) return -1; String val = HttpField.getValueParameters(field.getValue(), null); if (val == null) return -1; return TimeUnit.SECONDS.toMillis(HttpDateTime.parse(val).toEpochSecond()); } /** *

Returns the {@link HttpField} at the given {@code index}, * or {@code null} if there is no field at the given index.

* * @param index the index of the {@link HttpField} * @return the {@link HttpField} at the given {@code index}, * or {@code null} if there is no field at the given index */ default HttpField getField(int index) { int i = 0; for (HttpField f : this) { if (i++ == index) return f; } return null; } /** *

Returns the first {@link HttpField} with the given field name, * or {@code null} if no such field is present.

* * @param header the field name to search for * @return the first {@link HttpField} with the given field name, * or {@code null} if no such field is present */ default HttpField getField(HttpHeader header) { for (HttpField f : this) { if (f.getHeader() == header) return f; } return null; } /** *

Returns the first {@link HttpField} with the given field name, * or {@code null} if no such field is present.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}.

* * @param name the case-insensitive field name to search for * @return the first {@link HttpField} with the given field name, * or {@code null} if no such field is present */ default HttpField getField(String name) { for (HttpField f : this) { if (f.is(name)) return f; } return null; } /** * @return an enumeration of field names * @deprecated use {@link #getFieldNamesCollection()} */ @Deprecated default Enumeration getFieldNames() { return Collections.enumeration(getFieldNamesCollection()); } /** *

Returns a {@link Set} of the field names.

*

Case-sensitivity of the field names is preserved.

* * @return an immutable {@link Set} of the field names. Changes made to the * {@code HttpFields} after this call are not reflected in the set. */ default Set getFieldNamesCollection() { Set seenByHeader = EnumSet.noneOf(HttpHeader.class); Set buildByName = null; List list = new ArrayList<>(size()); for (HttpField f : this) { HttpHeader header = f.getHeader(); if (header == null) { if (buildByName == null) buildByName = new TreeSet<>(String::compareToIgnoreCase); if (buildByName.add(f.getName())) list.add(f.getName()); } else if (seenByHeader.add(header)) { list.add(f.getName()); } } Set seenByName = buildByName; // use the list to retain a rough ordering return new AbstractSet<>() { @Override public Iterator iterator() { return list.iterator(); } @Override public int size() { return list.size(); } @Override public boolean contains(Object o) { if (o instanceof String s) return seenByName != null && seenByName.contains(s) || seenByHeader.contains(HttpHeader.CACHE.get(s)); return false; } }; } /** *

Returns all the {@link HttpField}s with the given field name.

* * @param header the field name to search for * @return a list of the {@link HttpField}s with the given field name, * or an empty list if no such field name is present */ default List getFields(HttpHeader header) { return getFields(header, (f, h) -> f.getHeader() == h); } /** *

Returns all the {@link HttpField}s with the given field name.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}.

* * @param name the case-insensitive field name to search for * @return a list of the {@link HttpField}s with the given field name, * or an empty list if no such field name is present */ default List getFields(String name) { return getFields(name, (f, n) -> f.is(name)); } private List getFields(T header, BiPredicate predicate) { return stream() .filter(f -> predicate.test(f, header)) .collect(Collectors.toList()); } /** *

Returns the value of a field as a {@code long} value, * or -1 if no such field is present.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}.

* * @param name the case-insensitive field name * @return the value of the field as a {@code long}, * or -1 if no such field is present * @throws NumberFormatException if the value of the field * cannot be converted to a {@code long} */ default long getLongField(String name) throws NumberFormatException { HttpField field = getField(name); return field == null ? -1L : field.getLongValue(); } /** *

Returns the value of a field as a {@code long} value, * or -1 if no such field is present.

* * @param header the field name * @return the value of the field as a {@code long}, * or -1 if no such field is present * @throws NumberFormatException if the value of the field * cannot be converted to a {@code long} */ default long getLongField(HttpHeader header) throws NumberFormatException { HttpField field = getField(header); return field == null ? -1L : field.getLongValue(); } /** *

Returns all the values of all the fields with the given name, * split and sorted in quality order using {@link QuotedQualityCSV}.

* * @param header the field name to search for * @return a list of all the values of all the fields with the given name, * split and sorted in quality order, * or an empty list if no such field name is present */ default List getQualityCSV(HttpHeader header) { return getQualityCSV(header, null); } /** *

Returns all the values of all the fields with the given name, * split and sorted first in quality order and then optionally further * sorted with the given function, using {@link QuotedQualityCSV}.

* * @param header the field name to search for * @param secondaryOrdering the secondary sort function, or {@code null} * for no secondary sort * @return a list of all the values of all the fields with the given name, * split and sorted in quality order and optionally further sorted with * the given function, * or an empty list if no such field name is present */ default List getQualityCSV(HttpHeader header, ToIntFunction secondaryOrdering) { QuotedQualityCSV values = null; for (HttpField f : this) { if (f.getHeader() == header) { if (values == null) values = new QuotedQualityCSV(secondaryOrdering); values.addValue(f.getValue()); } } return values == null ? Collections.emptyList() : values.getValues(); } /** *

Returns all the values of all the fields with the given name, * split and sorted in quality order using {@link QuotedQualityCSV}.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}.

* * @param name the case-insensitive field name to search for * @return a list of all the values of all the fields with the given name, * split and sorted in quality order, * or an empty list if no such field name is present */ default List getQualityCSV(String name) { QuotedQualityCSV values = null; for (HttpField f : this) { if (f.is(name)) { if (values == null) values = new QuotedQualityCSV(); values.addValue(f.getValue()); } } return values == null ? Collections.emptyList() : values.getValues(); } /** *

Returns an {@link Enumeration} of the encoded values of all the fields * with the given name.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}.

*

In case of multi-valued fields, the returned value is the encoded * value, including commas and quotes, as returned by {@link HttpField#getValue()}.

* * @param name the case-insensitive field name to search for * @return an {@link Enumeration} of the encoded values of all * the fields with the given name */ default Enumeration getValues(String name) { Iterator i = iterator(); return new Enumeration<>() { HttpField _field; @Override public boolean hasMoreElements() { if (_field != null) return true; while (i.hasNext()) { HttpField f = i.next(); if (f.is(name) && f.getValue() != null) { _field = f; return true; } } return false; } @Override public String nextElement() { if (hasMoreElements()) { String value = _field.getValue(); _field = null; return value; } throw new NoSuchElementException(); } }; } /** *

Returns a list of the encoded values of all the fields * with the given name.

*

In case of multi-valued fields, the returned value is the encoded * value, including commas and quotes, as returned by {@link HttpField#getValue()}.

* * @param header the field name to search for * @return a list of the encoded values of all the fields * with the given name, * or an empty list if no such field name is present */ default List getValuesList(HttpHeader header) { final List list = new ArrayList<>(); for (HttpField f : this) { if (f.getHeader() == header) list.add(f.getValue()); } return list; } /** *

Returns a list of the encoded values of all the fields * with the given name.

*

The comparison of field name is case-insensitive via * {@link HttpField#is(String)}.

*

In case of multi-valued fields, the returned value is the encoded * value, including commas and quotes, as returned by {@link HttpField#getValue()}.

* * @param name the field name to search for * @return a list of the encoded values of all the fields * with the given name, * or an empty list if no such field name is present */ default List getValuesList(String name) { final List list = new ArrayList<>(); for (HttpField f : this) { if (f.is(name)) list.add(f.getValue()); } return list; } /** *

Returns whether this instance is equal to the given instance.

*

Returns {@code true} if and only if the two instances have the * same number of fields, in the same order, and each field is equal, * but makes no difference between {@link Mutable} and non-{@link Mutable} * instances.

* * @param that the {@link HttpFields} instance to compare to * @return whether the two instances are equal */ default boolean isEqualTo(HttpFields that) { if (size() != that.size()) return false; Iterator i = that.iterator(); for (HttpField f : this) { if (!i.hasNext()) return false; if (!f.equals(i.next())) return false; } return !i.hasNext(); } /** * @return the number of {@link HttpField}s in this instance */ default int size() { int size = 0; for (HttpField ignored : this) { size++; } return size; } /** * @return a sequential stream of the {@link HttpField}s in this instance */ default Stream stream() { return StreamSupport.stream(spliterator(), false); } /** *

A mutable version of {@link HttpFields}.

*

Name and value pairs representing HTTP headers or HTTP * trailers can be added to or removed from this instance.

*/ interface Mutable extends HttpFields { /** *

Adds a new {@link HttpField} with the given name and string value.

*

The new {@link HttpField} is added even if a field with the * same name is already present.

* * @param name the non-{@code null} name of the field * @param value the non-{@code null} value of the field * @return this instance */ default Mutable add(String name, String value) { Objects.requireNonNull(name); Objects.requireNonNull(value); return add(new HttpField(name, value)); } /** *

Adds a new {@link HttpField} with the given name and {@code long} value.

*

The new {@link HttpField} is added even if a field with the * same name is already present.

* * @param name the non-{@code null} name of the field * @param value the value of the field * @return this instance */ default Mutable add(String name, long value) { Objects.requireNonNull(name); return add(new HttpField.LongValueHttpField(name, value)); } /** *

Adds a new {@link HttpField} with the given name and value.

*

The new {@link HttpField} is added even if a field with the * same name is already present.

* * @param header the non-{@code null} name of the field * @param value the non-{@code null} value of the field * @return this instance */ default Mutable add(HttpHeader header, HttpHeaderValue value) { Objects.requireNonNull(header); Objects.requireNonNull(value); return add(header, value.toString()); } /** *

Adds a new {@link HttpField} with the given name and string value.

*

The new {@link HttpField} is added even if a field with the * same name is already present.

* * @param header the non-{@code null} name of the field * @param value the non-{@code null} value of the field * @return this instance */ default Mutable add(HttpHeader header, String value) { Objects.requireNonNull(header); Objects.requireNonNull(value); return add(new HttpField(header, value)); } /** *

Adds a new {@link HttpField} with the given name and {@code long} value.

*

The new {@link HttpField} is added even if a field with the * same name is already present.

* * @param header the non-{@code null} name of the field * @param value the value of the field * @return this instance */ default Mutable add(HttpHeader header, long value) { Objects.requireNonNull(header); return add(new HttpField.LongValueHttpField(header, value)); } /** *

Adds the given {@link HttpField} to this instance.

* * @param field the {@link HttpField} to add * @return this instance */ default Mutable add(HttpField field) { Objects.requireNonNull(field); ListIterator i = listIterator(size()); i.add(field); return this; } /** *

Adds all the {@link HttpField}s of the given {@link HttpFields} * to this instance.

* * @param fields the fields to add * @return this instance */ default Mutable add(HttpFields fields) { for (HttpField field : fields) { add(field); } return this; } /** *

Adds a field associated with a list of values.

* * @param name the name of the field * @param list the List value of the field. * @return this builder */ default Mutable add(String name, List list) { Objects.requireNonNull(name); Objects.requireNonNull(list); if (list.isEmpty()) return this; if (list.size() == 1) { String v = list.get(0); return add(name, v == null ? "" : v); } HttpField field = new HttpField.MultiHttpField(name, list); return add(field); } /** *

Adds the given value(s) to the {@link HttpField} with the given name, * encoding them as comma-separated if necessary, * unless they are already present in existing fields with the same name.

* * @param header the field name of the field to add the value(s) to * @param values the value(s) to add * @return this instance */ default Mutable addCSV(HttpHeader header, String... values) { Objects.requireNonNull(header); QuotedCSV existing = null; for (HttpField f : this) { if (f.getHeader() == header) { if (existing == null) existing = new QuotedCSV(false); existing.addValue(f.getValue()); } } String value = formatCsvExcludingExisting(existing, values); if (value != null) add(header, value); return this; } /** *

Adds the given value(s) to the {@link HttpField} with the given name, * encoding them as comma-separated if necessary, * unless they are already present in existing fields with the same name.

* * @param name the field name of the field to add the value(s) to * @param values the value(s) to add * @return this instance */ default Mutable addCSV(String name, String... values) { Objects.requireNonNull(name); QuotedCSV existing = null; for (HttpField f : this) { if (f.is(name)) { if (existing == null) existing = new QuotedCSV(false); existing.addValue(f.getValue()); } } String value = formatCsvExcludingExisting(existing, values); if (value != null) add(name, value); return this; } /** *

Adds a new date {@link HttpField} with the given name and {@code date} value.

* The {@code date} parameter is the number of milliseconds from the Unix Epoch, * and it is formatted into a string via {@link DateGenerator#formatDate(long)}. * * @param name the non-{@code null} name of the field * @param date the field date value * @return this instance */ default Mutable addDateField(String name, long date) { Objects.requireNonNull(name); add(name, DateGenerator.formatDate(date)); return this; } /** *

Removes all the fields from this instance.

* * @return this instance */ default Mutable clear() { Iterator i = iterator(); while (i.hasNext()) { i.next(); i.remove(); } return this; } /** *

Ensures that the given {@link HttpField} is present when the field * may not exist or may exist and be multi-valued.

*

Multiple existing {@link HttpField}s are merged into a single field.

* * @param field the field to ensure to be present */ default void ensureField(HttpField field) { Objects.requireNonNull(field); HttpHeader header = field.getHeader(); // Is the field value multi valued? if (field.getValue().indexOf(',') < 0) { if (header != null) computeField(header, (h, l) -> computeEnsure(field, l)); else computeField(field.getName(), (h, l) -> computeEnsure(field, l)); } else { if (header != null) computeField(header, (h, l) -> computeEnsure(field, field.getValues(), l)); else computeField(field.getName(), (h, l) -> computeEnsure(field, field.getValues(), l)); } } /** *

Puts the given {@link HttpField} into this instance.

*

If a fields with the same name is present, the given field * replaces it, and other existing fields with the same name * are removed, so that only the given field will be present.

*

If a field with the same name is not present, the given * field will be added.

* * @param field the field to put * @return this instance */ default Mutable put(HttpField field) { Objects.requireNonNull(field); boolean put = false; ListIterator i = listIterator(); while (i.hasNext()) { HttpField f = i.next(); if (f.isSameName(field)) { if (put) { i.remove(); } else { i.set(field); put = true; } } } if (!put) add(field); return this; } /** *

This method behaves like {@link #remove(String)} when * the given {@code value} is {@code null}, otherwise behaves * like {@link #put(HttpField)}.

* * @param name the name of the field * @param value the value of the field; if {@code null} the field is removed * @return this instance */ default Mutable put(String name, String value) { Objects.requireNonNull(name); if (value == null) return remove(name); return put(new HttpField(name, value)); } /** *

This method behaves like {@link #remove(HttpHeader)} when * the given {@code value} is {@code null}, otherwise behaves * like {@link #put(HttpField)}.

* * @param header the name of the field * @param value the value of the field; if {@code null} the field is removed * @return this instance */ default Mutable put(HttpHeader header, HttpHeaderValue value) { Objects.requireNonNull(header); if (value == null) return remove(header); return put(new HttpField(header, value.toString())); } /** *

This method behaves like {@link #remove(HttpHeader)} when * the given {@code value} is {@code null}, otherwise behaves * like {@link #put(HttpField)}.

* * @param header the name of the field * @param value the value of the field; if {@code null} the field is removed * @return this instance */ default Mutable put(HttpHeader header, String value) { Objects.requireNonNull(header); if (value == null) return remove(header); return put(new HttpField(header, value)); } /** *

Puts a field associated with a list of values.

* * @param name the name of the field * @param list the List value of the field. If null the field is cleared. * @return this builder */ default Mutable put(String name, List list) { Objects.requireNonNull(name); if (list == null || list.isEmpty()) return remove(name); if (list.size() == 1) { String value = list.get(0); return put(name, value == null ? "" : value); } HttpField field = new HttpField.MultiHttpField(name, list); return put(field); } /** *

Puts a new date {@link HttpField} with the given name and {@code date} value, * with the semantic of {@link #put(HttpField)}.

* The {@code date} parameter is the number of milliseconds from the Unix Epoch, * and it is formatted into a string via {@link DateGenerator#formatDate(long)}. * * @param name the non-{@code null} name of the field * @param date the field date value * @return this instance */ default Mutable putDate(HttpHeader name, long date) { Objects.requireNonNull(name); return put(name, DateGenerator.formatDate(date)); } /** *

Puts a new date {@link HttpField} with the given name and {@code date} value, * with the semantic of {@link #put(HttpField)}.

* The {@code date} parameter is the number of milliseconds from the Unix Epoch, * and it is formatted into a string via {@link DateGenerator#formatDate(long)}. * * @param name the non-{@code null} name of the field * @param date the field date value * @return this instance */ default Mutable putDate(String name, long date) { Objects.requireNonNull(name); return put(name, DateGenerator.formatDate(date)); } /** *

Puts a new {@link HttpField} with the given name and {@code long} value, * with the semantic of {@link #put(HttpField)}.

* * @param header the non-{@code null} name of the field * @param value the value of the field * @return this instance */ default Mutable put(HttpHeader header, long value) { Objects.requireNonNull(header); if (value == 0 && header == HttpHeader.CONTENT_LENGTH) return put(HttpFields.CONTENT_LENGTH_0); return put(new HttpField.LongValueHttpField(header, value)); } /** *

Puts a new {@link HttpField} with the given name and {@code long} value, * with the semantic of {@link #put(HttpField)}.

* * @param name the non-{@code null} name of the field * @param value the value of the field * @return this instance */ default Mutable put(String name, long value) { Objects.requireNonNull(name); if (value == 0 && HttpHeader.CONTENT_LENGTH.is(name)) return put(HttpFields.CONTENT_LENGTH_0); return put(new HttpField.LongValueHttpField(name, value)); } /** *

Computes a single field for the given {@link HttpHeader} and for existing fields with the same header.

* *

The compute function receives the field name and a list of fields with the same name * so that their values can be used to compute the value of the field that is returned * by the compute function parameter. * If the compute function returns {@code null}, the fields with the given name are removed.

*

This method comes handy when you want to add an HTTP header if it does not exist, * or add a value if the HTTP header already exists, similarly to * {@link Map#compute(Object, BiFunction)}.

* *

This method can be used to {@link #put(HttpField) put} a new field (or blindly replace its value):

*
{@code
         * httpFields.computeField("X-New-Header",
         *     (name, fields) -> new HttpField(name, "NewValue"));
         * }
* *

This method can be used to coalesce many fields into one:

*
{@code
         * // Input:
         * GET / HTTP/1.1
         * Host: localhost
         * Cookie: foo=1
         * Cookie: bar=2,baz=3
         * User-Agent: Jetty
         *
         * // Computation:
         * httpFields.computeField("Cookie", (name, fields) ->
         * {
         *     // No cookies, nothing to do.
         *     if (fields == null)
         *         return null;
         *
         *     // Coalesces all cookies.
         *     String coalesced = fields.stream()
         *         .flatMap(field -> Stream.of(field.getValues()))
         *         .collect(Collectors.joining(", "));
         *
         *     // Returns a single Cookie header with all cookies.
         *     return new HttpField(name, coalesced);
         * }
         *
         * // Output:
         * GET / HTTP/1.1
         * Host: localhost
         * Cookie: foo=1, bar=2, baz=3
         * User-Agent: Jetty
         * }
* *

This method can be used to replace a field:

*
{@code
         * httpFields.computeField("X-Length", (name, fields) ->
         * {
         *     if (fields == null)
         *         return null;
         *
         *     // Get any value among the X-Length headers.
         *     String length = fields.stream()
         *         .map(HttpField::getValue)
         *         .findAny()
         *         .orElse("0");
         *
         *     // Replace X-Length headers with X-Capacity header.
         *     return new HttpField("X-Capacity", length);
         * });
         * }
* *

This method can be used to remove a field:

*
{@code
         * httpFields.computeField("Connection", (name, fields) -> null);
         * }
* * @param header the field name * @param computeFn the compute function * @return this instance */ default Mutable computeField(HttpHeader header, BiFunction, HttpField> computeFn) { Objects.requireNonNull(header); HttpField result = computeFn.apply(header, stream().filter(f -> f.getHeader() == header).toList()); return result != null ? put(result) : remove(header); } /** *

Computes a single field for the given HTTP field name and for existing fields with the same name.

* * @param name the field name * @param computeFn the compute function * @return this instance * @see #computeField(HttpHeader, BiFunction) */ default Mutable computeField(String name, BiFunction, HttpField> computeFn) { Objects.requireNonNull(name); HttpField result = computeFn.apply(name, stream().filter(f -> f.is(name)).toList()); return result != null ? put(result) : remove(name); } /** *

Removes all the fields with the given name.

* * @param header the name of the fields to remove * @return this instance */ default Mutable remove(HttpHeader header) { Objects.requireNonNull(header); Iterator i = iterator(); while (i.hasNext()) { HttpField f = i.next(); if (f.getHeader() == header) i.remove(); } return this; } /** *

Removes all the fields with the given names.

* * @param headers the names of the fields to remove * @return this instance */ default Mutable remove(EnumSet headers) { Iterator i = iterator(); while (i.hasNext()) { HttpField f = i.next(); HttpHeader h = f.getHeader(); if (h != null && headers.contains(h)) i.remove(); } return this; } /** *

Removes all the fields with the given name.

* * @param name the name of the fields to remove * @return this instance */ default Mutable remove(String name) { Objects.requireNonNull(name); for (ListIterator i = listIterator(); i.hasNext(); ) { HttpField f = i.next(); if (f.is(name)) i.remove(); } return this; } private static String formatCsvExcludingExisting(QuotedCSV existing, String... values) { // remove any existing values from the new values boolean add = true; if (existing != null && !existing.isEmpty()) { add = false; for (int i = values.length; i-- > 0; ) { String unquoted = QuotedCSV.unquote(values[i]); if (existing.getValues().contains(unquoted)) values[i] = null; else add = true; } } if (add) { StringBuilder value = new StringBuilder(); for (String v : values) { if (v == null) continue; if (!value.isEmpty()) value.append(", "); value.append(v); } if (!value.isEmpty()) return value.toString(); } return null; } private static HttpField computeEnsure(HttpField ensure, List fields) { // If no existing fields return the ensure field if (fields == null || fields.isEmpty()) return ensure; String ensureValue = ensure.getValue(); // Handle a single existing field if (fields.size() == 1) { // If the existing field contains the ensure value, return it, else append values. HttpField f = fields.get(0); return f.contains(ensureValue) ? f : new HttpField(ensure.getHeader(), ensure.getName(), f.getValue() + ", " + ensureValue); } // Handle multiple existing fields StringBuilder v = new StringBuilder(); for (HttpField f : fields) { // Always append multiple fields into a single field value if (!v.isEmpty()) v.append(", "); v.append(f.getValue()); // check if the ensure value is already contained if (ensureValue != null && f.contains(ensureValue)) ensureValue = null; } // If the ensure value was not contained append it if (ensureValue != null) v.append(", ").append(ensureValue); return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); } private static HttpField computeEnsure(HttpField ensure, String[] values, List fields) { // If no existing fields return the ensure field if (fields == null || fields.isEmpty()) return ensure; // Handle a single existing field if (fields.size() == 1) { HttpField f = fields.get(0); // check which ensured values are already contained int ensured = values.length; for (int i = 0; i < values.length; i++) { if (f.contains(values[i])) { ensured--; values[i] = null; } } // if all ensured values contained return the existing field if (ensured == 0) return f; // else if no ensured values contained append the entire ensured valued if (ensured == values.length) return new HttpField(ensure.getHeader(), ensure.getName(), f.getValue() + ", " + ensure.getValue()); // else append just the ensured values that are not contained StringBuilder v = new StringBuilder(f.getValue()); for (String value : values) { if (value != null) v.append(", ").append(value); } return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); } // Handle a multiple existing field StringBuilder v = new StringBuilder(); int ensured = values.length; for (HttpField f : fields) { // Always append multiple fields into a single field value if (!v.isEmpty()) v.append(", "); v.append(f.getValue()); // null out ensured values that are included for (int i = 0; i < values.length; i++) { if (values[i] != null && f.contains(values[i])) { ensured--; values[i] = null; } } } // if no ensured values exist append them all if (ensured == values.length) v.append(", ").append(ensure.getValue()); // else if some ensured values are missing, append them else if (ensured > 0) { for (String value : values) { if (value != null) v.append(", ").append(value); } } // return a merged header with missing ensured values added return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); } /** * A wrapper of {@link HttpFields} instances. */ class Wrapper implements Mutable { private final Mutable _fields; public Wrapper(Mutable fields) { _fields = fields; } /** * Called when a field is added (including as part of a put). * * @param field The field being added. * @return The field to add, or null if the add is to be ignored. */ public HttpField onAddField(HttpField field) { return field; } /** * Called when a field is removed (including as part of a put). * * @param field The field being removed. * @return True if the field should be removed, false otherwise. */ public boolean onRemoveField(HttpField field) { return true; } public HttpField onReplaceField(HttpField oldField, HttpField newField) { return newField; } @Override public int size() { // This impl needed only as an optimization return _fields.size(); } @Override public Stream stream() { // This impl needed only as an optimization return _fields.stream(); } @Override public Mutable add(HttpField field) { if (field != null) { field = onAddField(field); if (field != null) _fields.add(field); } return this; } @Override public Mutable put(HttpField field) { Objects.requireNonNull(field); // rewrite put to ensure that removes are called before replace int put = -1; ListIterator i = _fields.listIterator(); while (i.hasNext()) { HttpField f = i.next(); if (f.isSameName(field)) { if (put < 0) put = i.previousIndex(); else if (onRemoveField(f)) i.remove(); } } if (put < 0) { field = onAddField(field); if (field != null) _fields.add(field); } else { i = _fields.listIterator(put); HttpField old = i.next(); field = onReplaceField(old, field); if (field != null) i.set(field); } return this; } @Override public Mutable clear() { _fields.clear(); return this; } @Override public ListIterator listIterator(int index) { ListIterator i = _fields.listIterator(index); return new ListIterator<>() { HttpField last; @Override public boolean hasNext() { return i.hasNext(); } @Override public HttpField next() { return last = i.next(); } @Override public boolean hasPrevious() { return i.hasPrevious(); } @Override public HttpField previous() { return last = i.previous(); } @Override public int nextIndex() { return i.nextIndex(); } @Override public int previousIndex() { return i.previousIndex(); } @Override public void remove() { if (last != null && onRemoveField(last)) { last = null; i.remove(); } } @Override public void set(HttpField field) { if (field == null) { if (last != null && onRemoveField(last)) { last = null; i.remove(); } } else { if (last != null) { field = onReplaceField(last, field); if (field != null) { last = null; i.set(field); } } } } @Override public void add(HttpField field) { if (field != null) { field = onAddField(field); if (field != null) { last = null; i.add(field); } } } }; } } } @Deprecated(forRemoval = true) class MutableHttpFields extends org.eclipse.jetty.http.MutableHttpFields { } @Deprecated(forRemoval = true) class ImmutableHttpFields extends org.eclipse.jetty.http.ImmutableHttpFields { protected ImmutableHttpFields(HttpField[] fields, int size) { super(fields, size); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy