Read Only Http2Headers
Motivation: A read only implementation of Http2Headers can allow for a more efficient usage of memory and more performant combined construction and iteration during serialization. Modifications: - Add a new ReadOnlyHttp2Headers class Result: ReadOnlyHttp2Headers exists and can be used for performance reasons when appropriate. ``` Benchmark (headerCount) Mode Cnt Score Error Units ReadOnlyHttp2HeadersBenchmark.defaultClientHeaders 1 avgt 20 96.156 ± 1.902 ns/op ReadOnlyHttp2HeadersBenchmark.defaultClientHeaders 5 avgt 20 157.925 ± 3.847 ns/op ReadOnlyHttp2HeadersBenchmark.defaultClientHeaders 10 avgt 20 236.257 ± 2.663 ns/op ReadOnlyHttp2HeadersBenchmark.defaultClientHeaders 20 avgt 20 392.861 ± 3.932 ns/op ReadOnlyHttp2HeadersBenchmark.defaultServerHeaders 1 avgt 20 48.759 ± 0.466 ns/op ReadOnlyHttp2HeadersBenchmark.defaultServerHeaders 5 avgt 20 113.122 ± 0.948 ns/op ReadOnlyHttp2HeadersBenchmark.defaultServerHeaders 10 avgt 20 192.698 ± 1.936 ns/op ReadOnlyHttp2HeadersBenchmark.defaultServerHeaders 20 avgt 20 348.974 ± 3.111 ns/op ReadOnlyHttp2HeadersBenchmark.defaultTrailers 1 avgt 20 35.694 ± 0.271 ns/op ReadOnlyHttp2HeadersBenchmark.defaultTrailers 5 avgt 20 98.993 ± 2.933 ns/op ReadOnlyHttp2HeadersBenchmark.defaultTrailers 10 avgt 20 171.035 ± 5.068 ns/op ReadOnlyHttp2HeadersBenchmark.defaultTrailers 20 avgt 20 330.621 ± 3.381 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyClientHeaders 1 avgt 20 40.573 ± 0.474 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyClientHeaders 5 avgt 20 56.516 ± 0.660 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyClientHeaders 10 avgt 20 76.890 ± 0.776 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyClientHeaders 20 avgt 20 117.531 ± 1.393 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyServerHeaders 1 avgt 20 29.206 ± 0.264 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyServerHeaders 5 avgt 20 44.587 ± 0.312 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyServerHeaders 10 avgt 20 64.458 ± 1.169 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyServerHeaders 20 avgt 20 107.179 ± 0.881 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyTrailers 1 avgt 20 21.563 ± 0.202 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyTrailers 5 avgt 20 41.019 ± 0.440 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyTrailers 10 avgt 20 64.053 ± 0.785 ns/op ReadOnlyHttp2HeadersBenchmark.readOnlyTrailers 20 avgt 20 113.737 ± 4.433 ns/op ```
This commit is contained in:
parent
fe2b55cea1
commit
06e7627b5f
@ -34,7 +34,7 @@ public class DefaultHttp2Headers
|
||||
return !isUpperCase(value);
|
||||
}
|
||||
};
|
||||
private static final NameValidator<CharSequence> HTTP2_NAME_VALIDATOR = new NameValidator<CharSequence>() {
|
||||
static final NameValidator<CharSequence> HTTP2_NAME_VALIDATOR = new NameValidator<CharSequence>() {
|
||||
@Override
|
||||
public void validateName(CharSequence name) {
|
||||
if (name == null || name.length() == 0) {
|
||||
|
@ -0,0 +1,831 @@
|
||||
/*
|
||||
* Copyright 2016 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import io.netty.handler.codec.Headers;
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import static io.netty.handler.codec.CharSequenceValueConverter.INSTANCE;
|
||||
import static io.netty.handler.codec.http2.DefaultHttp2Headers.HTTP2_NAME_VALIDATOR;
|
||||
import static io.netty.util.internal.EmptyArrays.EMPTY_ASCII_STRINGS;
|
||||
|
||||
/**
|
||||
* A variant of {@link Http2Headers} which only supports read-only methods.
|
||||
* <p>
|
||||
* Any array passed to this class may be used directly in the underlying data structures of this class. If these
|
||||
* arrays may be modified it is the caller's responsibility to supply this class with a copy of the array.
|
||||
* <p>
|
||||
* This may be a good alternative to {@link DefaultHttp2Headers} if your have a fixed set of headers which will not
|
||||
* change.
|
||||
*/
|
||||
public final class ReadOnlyHttp2Headers implements Http2Headers {
|
||||
private static final byte PSEUDO_HEADER_TOKEN = (byte) ':';
|
||||
private final AsciiString[] pseudoHeaders;
|
||||
private final AsciiString[] otherHeaders;
|
||||
|
||||
/**
|
||||
* Used to create read only object designed to represent trailers.
|
||||
* <p>
|
||||
* If this is used for a purpose other than trailers you may violate the header serialization ordering defined by
|
||||
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.1">RFC 7540, 8.1.2.1</a>.
|
||||
* @param validateHeaders {@code true} will run validation on each header name/value pair to ensure protocol
|
||||
* compliance.
|
||||
* @param otherHeaders A an array of key:value pairs. Must not contain any
|
||||
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.1">pseudo headers</a>
|
||||
* or {@code null} names/values.
|
||||
* A copy will <strong>NOT</strong> be made of this array. If the contents of this array
|
||||
* may be modified externally you are responsible for passing in a copy.
|
||||
* @return A read only representation of the headers.
|
||||
*/
|
||||
public static ReadOnlyHttp2Headers trailers(boolean validateHeaders, AsciiString... otherHeaders) {
|
||||
return new ReadOnlyHttp2Headers(validateHeaders, EMPTY_ASCII_STRINGS, otherHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new read only representation of headers used by clients.
|
||||
* @param validateHeaders {@code true} will run validation on each header name/value pair to ensure protocol
|
||||
* compliance.
|
||||
* @param method The value for {@link PseudoHeaderName#METHOD}.
|
||||
* @param path The value for {@link PseudoHeaderName#PATH}.
|
||||
* @param scheme The value for {@link PseudoHeaderName#SCHEME}.
|
||||
* @param authority The value for {@link PseudoHeaderName#AUTHORITY}.
|
||||
* @param otherHeaders A an array of key:value pairs. Must not contain any
|
||||
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.1">pseudo headers</a>
|
||||
* or {@code null} names/values.
|
||||
* A copy will <strong>NOT</strong> be made of this array. If the contents of this array
|
||||
* may be modified externally you are responsible for passing in a copy.
|
||||
* @return a new read only representation of headers used by clients.
|
||||
*/
|
||||
public static ReadOnlyHttp2Headers clientHeaders(boolean validateHeaders,
|
||||
AsciiString method, AsciiString path,
|
||||
AsciiString scheme, AsciiString authority,
|
||||
AsciiString... otherHeaders) {
|
||||
return new ReadOnlyHttp2Headers(validateHeaders,
|
||||
new AsciiString[] {
|
||||
PseudoHeaderName.METHOD.value(), method, PseudoHeaderName.PATH.value(), path,
|
||||
PseudoHeaderName.SCHEME.value(), scheme, PseudoHeaderName.AUTHORITY.value(), authority
|
||||
},
|
||||
otherHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new read only representation of headers used by servers.
|
||||
* @param validateHeaders {@code true} will run validation on each header name/value pair to ensure protocol
|
||||
* compliance.
|
||||
* @param status The value for {@link PseudoHeaderName#STATUS}.
|
||||
* @param otherHeaders A an array of key:value pairs. Must not contain any
|
||||
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.1">pseudo headers</a>
|
||||
* or {@code null} names/values.
|
||||
* A copy will <strong>NOT</strong> be made of this array. If the contents of this array
|
||||
* may be modified externally you are responsible for passing in a copy.
|
||||
* @return a new read only representation of headers used by servers.
|
||||
*/
|
||||
public static ReadOnlyHttp2Headers serverHeaders(boolean validateHeaders,
|
||||
AsciiString status,
|
||||
AsciiString... otherHeaders) {
|
||||
return new ReadOnlyHttp2Headers(validateHeaders,
|
||||
new AsciiString[] { PseudoHeaderName.STATUS.value(), status },
|
||||
otherHeaders);
|
||||
}
|
||||
|
||||
private ReadOnlyHttp2Headers(boolean validateHeaders, AsciiString[] pseudoHeaders, AsciiString... otherHeaders) {
|
||||
assert (pseudoHeaders.length & 1) == 0; // pseudoHeaders are only set internally so assert should be enough.
|
||||
if ((otherHeaders.length & 1) != 0) {
|
||||
throw newInvalidArraySizeException();
|
||||
}
|
||||
if (validateHeaders) {
|
||||
validateHeaders(pseudoHeaders, otherHeaders);
|
||||
}
|
||||
this.pseudoHeaders = pseudoHeaders;
|
||||
this.otherHeaders = otherHeaders;
|
||||
}
|
||||
|
||||
private static IllegalArgumentException newInvalidArraySizeException() {
|
||||
return new IllegalArgumentException("pseudoHeaders and otherHeaders must be arrays of [name, value] pairs");
|
||||
}
|
||||
|
||||
private static void validateHeaders(AsciiString[] pseudoHeaders, AsciiString... otherHeaders) {
|
||||
// We are only validating values... so start at 1 and go until end.
|
||||
for (int i = 1; i < pseudoHeaders.length; i += 2) {
|
||||
// pseudoHeaders names are only set internally so they are assumed to be valid.
|
||||
if (pseudoHeaders[i] == null) {
|
||||
throw new IllegalArgumentException("pseudoHeaders value at index " + i + " is null");
|
||||
}
|
||||
}
|
||||
|
||||
boolean seenNonPseudoHeader = false;
|
||||
final int otherHeadersEnd = otherHeaders.length - 1;
|
||||
for (int i = 0; i < otherHeadersEnd; i += 2) {
|
||||
AsciiString name = otherHeaders[i];
|
||||
HTTP2_NAME_VALIDATOR.validateName(name);
|
||||
if (!seenNonPseudoHeader && !name.isEmpty() && name.byteAt(0) != PSEUDO_HEADER_TOKEN) {
|
||||
seenNonPseudoHeader = true;
|
||||
} else if (seenNonPseudoHeader && !name.isEmpty() && name.byteAt(0) == PSEUDO_HEADER_TOKEN) {
|
||||
throw new IllegalArgumentException(
|
||||
"otherHeaders name at index " + i + " is a pseudo header that appears after non-pseudo headers.");
|
||||
}
|
||||
if (otherHeaders[i + 1] == null) {
|
||||
throw new IllegalArgumentException("otherHeaders value at index " + (i + 1) + " is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AsciiString get0(CharSequence name) {
|
||||
final int nameHash = AsciiString.hashCode(name);
|
||||
|
||||
final int pseudoHeadersEnd = pseudoHeaders.length - 1;
|
||||
for (int i = 0; i < pseudoHeadersEnd; i += 2) {
|
||||
AsciiString roName = pseudoHeaders[i];
|
||||
if (roName.hashCode() == nameHash && roName.contentEqualsIgnoreCase(name)) {
|
||||
return pseudoHeaders[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
final int otherHeadersEnd = otherHeaders.length - 1;
|
||||
for (int i = 0; i < otherHeadersEnd; i += 2) {
|
||||
AsciiString roName = otherHeaders[i];
|
||||
if (roName.hashCode() == nameHash && roName.contentEqualsIgnoreCase(name)) {
|
||||
return otherHeaders[i + 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence get(CharSequence name) {
|
||||
return get0(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence get(CharSequence name, CharSequence defaultValue) {
|
||||
CharSequence value = get(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getAndRemove(CharSequence name, CharSequence defaultValue) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CharSequence> getAll(CharSequence name) {
|
||||
final int nameHash = AsciiString.hashCode(name);
|
||||
List<CharSequence> values = new ArrayList<CharSequence>();
|
||||
|
||||
final int pseudoHeadersEnd = pseudoHeaders.length - 1;
|
||||
for (int i = 0; i < pseudoHeadersEnd; i += 2) {
|
||||
AsciiString roName = pseudoHeaders[i];
|
||||
if (roName.hashCode() == nameHash && roName.contentEqualsIgnoreCase(name)) {
|
||||
values.add(pseudoHeaders[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
final int otherHeadersEnd = otherHeaders.length - 1;
|
||||
for (int i = 0; i < otherHeadersEnd; i += 2) {
|
||||
AsciiString roName = otherHeaders[i];
|
||||
if (roName.hashCode() == nameHash && roName.contentEqualsIgnoreCase(name)) {
|
||||
values.add(otherHeaders[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CharSequence> getAllAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getBoolean(CharSequence name) {
|
||||
AsciiString value = get0(name);
|
||||
return value != null ? INSTANCE.convertToBoolean(value) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(CharSequence name, boolean defaultValue) {
|
||||
Boolean value = getBoolean(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte getByte(CharSequence name) {
|
||||
AsciiString value = get0(name);
|
||||
return value != null ? INSTANCE.convertToByte(value) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(CharSequence name, byte defaultValue) {
|
||||
Byte value = getByte(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Character getChar(CharSequence name) {
|
||||
AsciiString value = get0(name);
|
||||
return value != null ? INSTANCE.convertToChar(value) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getChar(CharSequence name, char defaultValue) {
|
||||
Character value = getChar(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Short getShort(CharSequence name) {
|
||||
AsciiString value = get0(name);
|
||||
return value != null ? INSTANCE.convertToShort(value) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(CharSequence name, short defaultValue) {
|
||||
Short value = getShort(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getInt(CharSequence name) {
|
||||
AsciiString value = get0(name);
|
||||
return value != null ? INSTANCE.convertToInt(value) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(CharSequence name, int defaultValue) {
|
||||
Integer value = getInt(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLong(CharSequence name) {
|
||||
AsciiString value = get0(name);
|
||||
return value != null ? INSTANCE.convertToLong(value) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(CharSequence name, long defaultValue) {
|
||||
Long value = getLong(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float getFloat(CharSequence name) {
|
||||
AsciiString value = get0(name);
|
||||
return value != null ? INSTANCE.convertToFloat(value) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(CharSequence name, float defaultValue) {
|
||||
Float value = getFloat(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDouble(CharSequence name) {
|
||||
AsciiString value = get0(name);
|
||||
return value != null ? INSTANCE.convertToDouble(value) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(CharSequence name, double defaultValue) {
|
||||
Double value = getDouble(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTimeMillis(CharSequence name) {
|
||||
AsciiString value = get0(name);
|
||||
return value != null ? INSTANCE.convertToTimeMillis(value) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeMillis(CharSequence name, long defaultValue) {
|
||||
Long value = getTimeMillis(name);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getBooleanAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBooleanAndRemove(CharSequence name, boolean defaultValue) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte getByteAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByteAndRemove(CharSequence name, byte defaultValue) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Character getCharAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getCharAndRemove(CharSequence name, char defaultValue) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Short getShortAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShortAndRemove(CharSequence name, short defaultValue) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getIntAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntAndRemove(CharSequence name, int defaultValue) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLongAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLongAndRemove(CharSequence name, long defaultValue) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float getFloatAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloatAndRemove(CharSequence name, float defaultValue) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDoubleAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDoubleAndRemove(CharSequence name, double defaultValue) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTimeMillisAndRemove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeMillisAndRemove(CharSequence name, long defaultValue) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(CharSequence name) {
|
||||
return get(name) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(CharSequence name, CharSequence value) {
|
||||
final int nameHash = AsciiString.hashCode(name);
|
||||
final int valueHash = AsciiString.hashCode(value);
|
||||
|
||||
final int pseudoHeadersEnd = pseudoHeaders.length - 1;
|
||||
for (int i = 0; i < pseudoHeadersEnd; i += 2) {
|
||||
AsciiString roName = pseudoHeaders[i];
|
||||
AsciiString roValue = pseudoHeaders[i + 1];
|
||||
if (roName.hashCode() == nameHash && roValue.hashCode() == valueHash &&
|
||||
roName.contentEqualsIgnoreCase(name) && roValue.contentEqualsIgnoreCase(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
final int otherHeadersEnd = otherHeaders.length - 1;
|
||||
for (int i = 0; i < otherHeadersEnd; i += 2) {
|
||||
AsciiString roName = otherHeaders[i];
|
||||
AsciiString roValue = otherHeaders[i + 1];
|
||||
if (roName.hashCode() == nameHash && roValue.hashCode() == valueHash &&
|
||||
roName.contentEqualsIgnoreCase(name) && roValue.contentEqualsIgnoreCase(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsObject(CharSequence name, Object value) {
|
||||
if (value instanceof CharSequence) {
|
||||
return contains(name, (CharSequence) value);
|
||||
}
|
||||
return contains(name, value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsBoolean(CharSequence name, boolean value) {
|
||||
return contains(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsByte(CharSequence name, byte value) {
|
||||
return contains(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsChar(CharSequence name, char value) {
|
||||
return contains(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsShort(CharSequence name, short value) {
|
||||
return contains(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsInt(CharSequence name, int value) {
|
||||
return contains(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsLong(CharSequence name, long value) {
|
||||
return contains(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsFloat(CharSequence name, float value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsDouble(CharSequence name, double value) {
|
||||
return contains(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsTimeMillis(CharSequence name, long value) {
|
||||
return contains(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return (pseudoHeaders.length + otherHeaders.length) >>> 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return pseudoHeaders.length == 0 && otherHeaders.length == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<CharSequence> names() {
|
||||
if (isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<CharSequence> names = new LinkedHashSet<CharSequence>(size());
|
||||
final int pseudoHeadersEnd = pseudoHeaders.length - 1;
|
||||
for (int i = 0; i < pseudoHeadersEnd; i += 2) {
|
||||
names.add(pseudoHeaders[i]);
|
||||
}
|
||||
|
||||
final int otherHeadersEnd = otherHeaders.length - 1;
|
||||
for (int i = 0; i < otherHeadersEnd; i += 2) {
|
||||
names.add(otherHeaders[i]);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers add(CharSequence name, CharSequence value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers add(CharSequence name, Iterable<? extends CharSequence> values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers add(CharSequence name, CharSequence... values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addObject(CharSequence name, Object value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addObject(CharSequence name, Iterable<?> values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addObject(CharSequence name, Object... values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addBoolean(CharSequence name, boolean value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addByte(CharSequence name, byte value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addChar(CharSequence name, char value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addShort(CharSequence name, short value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addInt(CharSequence name, int value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addLong(CharSequence name, long value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addFloat(CharSequence name, float value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addDouble(CharSequence name, double value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers addTimeMillis(CharSequence name, long value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers add(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers set(CharSequence name, CharSequence value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers set(CharSequence name, Iterable<? extends CharSequence> values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers set(CharSequence name, CharSequence... values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setObject(CharSequence name, Object value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setObject(CharSequence name, Iterable<?> values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setObject(CharSequence name, Object... values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setBoolean(CharSequence name, boolean value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setByte(CharSequence name, byte value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setChar(CharSequence name, char value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setShort(CharSequence name, short value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setInt(CharSequence name, int value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setLong(CharSequence name, long value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setFloat(CharSequence name, float value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setDouble(CharSequence name, double value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setTimeMillis(CharSequence name, long value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers set(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setAll(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(CharSequence name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers clear() {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<CharSequence, CharSequence>> iterator() {
|
||||
return new ReadOnlyIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers method(CharSequence value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers scheme(CharSequence value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers authority(CharSequence value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers path(CharSequence value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers status(CharSequence value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence method() {
|
||||
return get(PseudoHeaderName.METHOD.value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence scheme() {
|
||||
return get(PseudoHeaderName.SCHEME.value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence authority() {
|
||||
return get(PseudoHeaderName.AUTHORITY.value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence path() {
|
||||
return get(PseudoHeaderName.PATH.value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence status() {
|
||||
return get(PseudoHeaderName.STATUS.value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
|
||||
String separator = "";
|
||||
for (Map.Entry<CharSequence, CharSequence> entry : this) {
|
||||
builder.append(separator);
|
||||
builder.append(entry.getKey()).append(": ").append(entry.getValue());
|
||||
separator = ", ";
|
||||
}
|
||||
return builder.append(']').toString();
|
||||
}
|
||||
|
||||
private final class ReadOnlyIterator implements Map.Entry<CharSequence, CharSequence>,
|
||||
Iterator<Map.Entry<CharSequence, CharSequence>> {
|
||||
private int i;
|
||||
private AsciiString[] current = pseudoHeaders.length != 0 ? pseudoHeaders : otherHeaders;
|
||||
private AsciiString key;
|
||||
private AsciiString value;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i != current.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<CharSequence, CharSequence> next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
key = current[i];
|
||||
value = current[i + 1];
|
||||
i += 2;
|
||||
if (i == current.length && current == pseudoHeaders) {
|
||||
current = otherHeaders;
|
||||
i = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence setValue(CharSequence value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return key.toString() + '=' + value.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -153,7 +153,7 @@ public class DefaultHttp2HeadersTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyPseudoHeadersFirst(Http2Headers headers) {
|
||||
static void verifyPseudoHeadersFirst(Http2Headers headers) {
|
||||
CharSequence lastNonPseudoName = null;
|
||||
for (Entry<CharSequence, CharSequence> entry: headers) {
|
||||
if (entry.getKey().length() == 0 || entry.getKey().charAt(0) != ':') {
|
||||
|
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2016 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import io.netty.util.AsciiString;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.netty.handler.codec.http2.DefaultHttp2HeadersTest.verifyPseudoHeadersFirst;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ReadOnlyHttp2HeadersTest {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void notKeyValuePairThrows() {
|
||||
ReadOnlyHttp2Headers.trailers(false, new AsciiString[]{ null });
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void nullTrailersNotAllowed() {
|
||||
ReadOnlyHttp2Headers.trailers(false, (AsciiString[]) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullHeaderNameNotChecked() {
|
||||
ReadOnlyHttp2Headers.trailers(false, null, null);
|
||||
}
|
||||
|
||||
@Test(expected = Http2Exception.class)
|
||||
public void nullHeaderNameValidated() {
|
||||
ReadOnlyHttp2Headers.trailers(true, null, new AsciiString("foo"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void pseudoHeaderNotAllowedAfterNonPseudoHeaders() {
|
||||
ReadOnlyHttp2Headers.trailers(true, new AsciiString(":name"), new AsciiString("foo"),
|
||||
new AsciiString("othername"), new AsciiString("goo"),
|
||||
new AsciiString(":pseudo"), new AsciiString("val"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void nullValuesAreNotAllowed() {
|
||||
ReadOnlyHttp2Headers.trailers(true, new AsciiString("foo"), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emtpyHeaderNameAllowed() {
|
||||
ReadOnlyHttp2Headers.trailers(false, AsciiString.EMPTY_STRING, new AsciiString("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPseudoHeadersMustComeFirstWhenIteratingServer() {
|
||||
Http2Headers headers = newServerHeaders();
|
||||
verifyPseudoHeadersFirst(headers);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPseudoHeadersMustComeFirstWhenIteratingClient() {
|
||||
Http2Headers headers = newClientHeaders();
|
||||
verifyPseudoHeadersFirst(headers);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testIteratorReadOnlyClient() {
|
||||
testIteratorReadOnly(newClientHeaders());
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testIteratorReadOnlyServer() {
|
||||
testIteratorReadOnly(newServerHeaders());
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testIteratorReadOnlyTrailers() {
|
||||
testIteratorReadOnly(newTrailers());
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testIteratorEntryReadOnlyClient() {
|
||||
testIteratorEntryReadOnly(newClientHeaders());
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testIteratorEntryReadOnlyServer() {
|
||||
testIteratorEntryReadOnly(newServerHeaders());
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testIteratorEntryReadOnlyTrailers() {
|
||||
testIteratorEntryReadOnly(newTrailers());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize() {
|
||||
Http2Headers headers = newTrailers();
|
||||
assertEquals(otherHeaders().length / 2, headers.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsNotEmpty() {
|
||||
Http2Headers headers = newTrailers();
|
||||
assertFalse(headers.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEmpty() {
|
||||
Http2Headers headers = ReadOnlyHttp2Headers.trailers(false);
|
||||
assertTrue(headers.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsName() {
|
||||
Http2Headers headers = newClientHeaders();
|
||||
assertTrue(headers.contains("Name1"));
|
||||
assertTrue(headers.contains(Http2Headers.PseudoHeaderName.PATH.value()));
|
||||
assertFalse(headers.contains(Http2Headers.PseudoHeaderName.STATUS.value()));
|
||||
assertFalse(headers.contains("a missing header"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsNameAndValue() {
|
||||
Http2Headers headers = newClientHeaders();
|
||||
assertTrue(headers.contains("Name1", "Value1"));
|
||||
assertTrue(headers.contains(Http2Headers.PseudoHeaderName.PATH.value(), "/foo"));
|
||||
assertFalse(headers.contains(Http2Headers.PseudoHeaderName.STATUS.value(), "200"));
|
||||
assertFalse(headers.contains("a missing header", "a missing value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() {
|
||||
Http2Headers headers = newClientHeaders();
|
||||
assertTrue(AsciiString.contentEqualsIgnoreCase("value1", headers.get("Name1")));
|
||||
assertTrue(AsciiString.contentEqualsIgnoreCase("/foo",
|
||||
headers.get(Http2Headers.PseudoHeaderName.PATH.value())));
|
||||
assertEquals(null, headers.get(Http2Headers.PseudoHeaderName.STATUS.value()));
|
||||
assertEquals(null, headers.get("a missing header"));
|
||||
}
|
||||
|
||||
private void testIteratorReadOnly(Http2Headers headers) {
|
||||
Iterator<Map.Entry<CharSequence, CharSequence>> itr = headers.iterator();
|
||||
assertTrue(itr.hasNext());
|
||||
itr.remove();
|
||||
}
|
||||
|
||||
private void testIteratorEntryReadOnly(Http2Headers headers) {
|
||||
Iterator<Map.Entry<CharSequence, CharSequence>> itr = headers.iterator();
|
||||
assertTrue(itr.hasNext());
|
||||
itr.next().setValue("foo");
|
||||
}
|
||||
|
||||
private ReadOnlyHttp2Headers newServerHeaders() {
|
||||
return ReadOnlyHttp2Headers.serverHeaders(false, new AsciiString("200"), otherHeaders());
|
||||
}
|
||||
|
||||
private ReadOnlyHttp2Headers newClientHeaders() {
|
||||
return ReadOnlyHttp2Headers.clientHeaders(false, new AsciiString("meth"), new AsciiString("/foo"),
|
||||
new AsciiString("schemer"), new AsciiString("respect_my_authority"), otherHeaders());
|
||||
}
|
||||
|
||||
private ReadOnlyHttp2Headers newTrailers() {
|
||||
return ReadOnlyHttp2Headers.trailers(false, otherHeaders());
|
||||
}
|
||||
|
||||
private AsciiString[] otherHeaders() {
|
||||
return new AsciiString[] {
|
||||
new AsciiString("name1"), new AsciiString("value1"),
|
||||
new AsciiString("name2"), new AsciiString("value2"),
|
||||
new AsciiString("name3"), new AsciiString("value3")
|
||||
};
|
||||
}
|
||||
}
|
@ -974,7 +974,7 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("read-only iterator");
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package io.netty.util.internal;
|
||||
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
@ -27,6 +29,7 @@ public final class EmptyArrays {
|
||||
public static final Object[] EMPTY_OBJECTS = {};
|
||||
public static final Class<?>[] EMPTY_CLASSES = {};
|
||||
public static final String[] EMPTY_STRINGS = {};
|
||||
public static final AsciiString[] EMPTY_ASCII_STRINGS = {};
|
||||
public static final StackTraceElement[] EMPTY_STACK_TRACE = {};
|
||||
public static final ByteBuffer[] EMPTY_BYTE_BUFFERS = {};
|
||||
public static final Certificate[] EMPTY_CERTIFICATES = {};
|
||||
|
@ -54,7 +54,7 @@ public class HeadersBenchmark extends AbstractMicrobenchmark {
|
||||
return (name.startsWith(":")) ? name.substring(1) : name;
|
||||
}
|
||||
|
||||
private static String toHttp2Name(String name) {
|
||||
static String toHttp2Name(String name) {
|
||||
name = name.toLowerCase();
|
||||
return (name.equals("host")) ? "xhost" : name;
|
||||
}
|
||||
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2016 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.microbench.headers;
|
||||
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpScheme;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
||||
import io.netty.handler.codec.http2.Http2Headers;
|
||||
import io.netty.handler.codec.http2.ReadOnlyHttp2Headers;
|
||||
import io.netty.microbench.util.AbstractMicrobenchmark;
|
||||
import io.netty.util.AsciiString;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Param;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Threads(1)
|
||||
@State(Scope.Benchmark)
|
||||
@Fork(2)
|
||||
@Warmup(iterations = 10)
|
||||
@Measurement(iterations = 10)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
public class ReadOnlyHttp2HeadersBenchmark extends AbstractMicrobenchmark {
|
||||
private AsciiString[] headerNames;
|
||||
private AsciiString[] headerValues;
|
||||
|
||||
@Param({ "1", "5", "10", "20" })
|
||||
public int headerCount;
|
||||
|
||||
private final AsciiString path = new AsciiString("/BigDynamicPayload");
|
||||
private final AsciiString authority = new AsciiString("io.netty");
|
||||
|
||||
@Setup
|
||||
public void setUp() throws Exception {
|
||||
headerNames = new AsciiString[headerCount];
|
||||
headerValues = new AsciiString[headerCount];
|
||||
for (int i = 0; i < headerCount; ++i) {
|
||||
headerNames[i] = new AsciiString("key-" + i);
|
||||
headerValues[i] = new AsciiString(UUID.randomUUID().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public int defaultTrailers() {
|
||||
Http2Headers headers = new DefaultHttp2Headers(false);
|
||||
for (int i = 0; i < headerCount; ++i) {
|
||||
headers.add(headerNames[i], headerValues[i]);
|
||||
}
|
||||
return iterate(headers);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public int readOnlyTrailers() {
|
||||
return iterate(ReadOnlyHttp2Headers.trailers(false, buildPairs()));
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public int defaultClientHeaders() {
|
||||
Http2Headers headers = new DefaultHttp2Headers(false);
|
||||
for (int i = 0; i < headerCount; ++i) {
|
||||
headers.add(headerNames[i], headerValues[i]);
|
||||
}
|
||||
headers.method(HttpMethod.POST.asciiName());
|
||||
headers.scheme(HttpScheme.HTTPS.name());
|
||||
headers.path(path);
|
||||
headers.authority(authority);
|
||||
return iterate(headers);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public int readOnlyClientHeaders() {
|
||||
return iterate(ReadOnlyHttp2Headers.clientHeaders(false, HttpMethod.POST.asciiName(), path,
|
||||
HttpScheme.HTTPS.name(), authority, buildPairs()));
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public int defaultServerHeaders() {
|
||||
Http2Headers headers = new DefaultHttp2Headers(false);
|
||||
for (int i = 0; i < headerCount; ++i) {
|
||||
headers.add(headerNames[i], headerValues[i]);
|
||||
}
|
||||
headers.status(HttpResponseStatus.OK.codeAsText());
|
||||
return iterate(headers);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public int readOnlyServerHeaders() {
|
||||
return iterate(ReadOnlyHttp2Headers.serverHeaders(false, HttpResponseStatus.OK.codeAsText(), buildPairs()));
|
||||
}
|
||||
|
||||
private static int iterate(Http2Headers headers) {
|
||||
int length = 0;
|
||||
for (Map.Entry<CharSequence, CharSequence> entry : headers) {
|
||||
length += entry.getKey().length() + entry.getValue().length();
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private AsciiString[] buildPairs() {
|
||||
AsciiString[] headerPairs = new AsciiString[headerCount * 2];
|
||||
for (int i = 0, j = 0; i < headerCount; ++i, ++j) {
|
||||
headerPairs[j] = headerNames[i];
|
||||
headerPairs[++j] = headerValues[i];
|
||||
}
|
||||
return headerPairs;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user