Faster and more memory efficient headers for HTTP, HTTP/2, STOMP and SPYD. Fixes #3600
Motivation: We noticed that the headers implementation in Netty for HTTP/2 uses quite a lot of memory and that also at least the performance of randomly accessing a header is quite poor. The main concern however was memory usage, as profiling has shown that a DefaultHttp2Headers not only use a lot of memory it also wastes a lot due to the underlying hashmaps having to be resized potentially several times as new headers are being inserted. This is tracked as issue #3600. Modifications: We redesigned the DefaultHeaders to simply take a Map object in its constructor and reimplemented the class using only the Map primitives. That way the implementation is very concise and hopefully easy to understand and it allows each concrete headers implementation to provide its own map or to even use a different headers implementation for processing requests and writing responses i.e. incoming headers need to provide fast random access while outgoing headers need fast insertion and fast iteration. The new implementation can support this with hardly any code changes. It also comes with the advantage that if the Netty project decides to add a third party collections library as a dependency, one can simply plug in one of those very fast and memory efficient map implementations and get faster and smaller headers for free. For now, we are using the JDK's TreeMap for HTTP and HTTP/2 default headers. Result: - Significantly fewer lines of code in the implementation. While the total commit is still roughly 400 lines less, the actual implementation is a lot less. I just added some more tests and microbenchmarks. - Overall performance is up. The current implementation should be significantly faster for insertion and retrieval. However, it is slower when it comes to iteration. There is simply no way a TreeMap can have the same iteration performance as a linked list (as used in the current headers implementation). That's totally fine though, because when looking at the benchmark results @ejona86 pointed out that the performance of the headers is completely dominated by insertion, that is insertion is so significantly faster in the new implementation that it does make up for several times the iteration speed. You can't iterate what you haven't inserted. I am demonstrating that in this spreadsheet [1]. (Actually, iteration performance is only down for HTTP, it's significantly improved for HTTP/2). - Memory is down. The implementation with TreeMap uses on avg ~30% less memory. It also does not produce any garbage while being resized. In load tests for GRPC we have seen a memory reduction of up to 1.2KB per RPC. I summarized the memory improvements in this spreadsheet [1]. The data was generated by [2] using JOL. - While it was my original intend to only improve the memory usage for HTTP/2, it should be similarly improved for HTTP, SPDY and STOMP as they all share a common implementation. [1] https://docs.google.com/spreadsheets/d/1ck3RQklyzEcCLlyJoqDXPCWRGVUuS-ArZf0etSXLVDQ/edit#gid=0 [2] https://gist.github.com/buchgr/4458a8bdb51dd58c82b4
This commit is contained in:
parent
a1f0785605
commit
d31fa31cdc
@ -36,7 +36,7 @@ public class DefaultFullHttpRequest extends DefaultHttpRequest implements FullHt
|
||||
}
|
||||
|
||||
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, boolean validateHeaders) {
|
||||
this(httpVersion, method, uri, Unpooled.buffer(0), true);
|
||||
this(httpVersion, method, uri, Unpooled.buffer(0), validateHeaders);
|
||||
}
|
||||
|
||||
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri,
|
||||
|
@ -15,14 +15,18 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.handler.codec.DefaultHeaders;
|
||||
import io.netty.handler.codec.DefaultTextHeaders;
|
||||
import io.netty.handler.codec.TextHeaders;
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.ByteProcessor;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeaders {
|
||||
|
||||
@ -46,234 +50,25 @@ public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeader
|
||||
LOOKUP_TABLE['='] = -1;
|
||||
}
|
||||
|
||||
private static final class HttpHeadersValidationConverter extends DefaultTextValueTypeConverter {
|
||||
private final boolean validate;
|
||||
|
||||
HttpHeadersValidationConverter(boolean validate) {
|
||||
this.validate = validate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException("value");
|
||||
}
|
||||
|
||||
CharSequence seq;
|
||||
if (value instanceof CharSequence) {
|
||||
seq = (CharSequence) value;
|
||||
} else if (value instanceof Number) {
|
||||
seq = value.toString();
|
||||
} else if (value instanceof Date) {
|
||||
seq = HttpHeaderDateFormat.get().format((Date) value);
|
||||
} else if (value instanceof Calendar) {
|
||||
seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
|
||||
} else {
|
||||
seq = value.toString();
|
||||
}
|
||||
|
||||
if (validate) {
|
||||
if (value instanceof AsciiString) {
|
||||
validateValue((AsciiString) seq);
|
||||
} else {
|
||||
validateValue(seq);
|
||||
}
|
||||
}
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
private static final class ValidateValueProcessor implements ByteProcessor {
|
||||
private final CharSequence seq;
|
||||
private int state;
|
||||
|
||||
public ValidateValueProcessor(CharSequence seq) {
|
||||
this.seq = seq;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
state = validateValueChar(state, (char) value, seq);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int state() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateValue(AsciiString seq) {
|
||||
ValidateValueProcessor processor = new ValidateValueProcessor(seq);
|
||||
try {
|
||||
seq.forEachByte(processor);
|
||||
} catch (Throwable t) {
|
||||
PlatformDependent.throwException(t);
|
||||
}
|
||||
|
||||
if (processor.state() != 0) {
|
||||
throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateValue(CharSequence seq) {
|
||||
int state = 0;
|
||||
// Start looping through each of the character
|
||||
for (int index = 0; index < seq.length(); index++) {
|
||||
state = validateValueChar(state, seq.charAt(index), seq);
|
||||
}
|
||||
|
||||
if (state != 0) {
|
||||
throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
|
||||
}
|
||||
}
|
||||
|
||||
private static int validateValueChar(int state, char c, CharSequence seq) {
|
||||
/*
|
||||
* State:
|
||||
* 0: Previous character was neither CR nor LF
|
||||
* 1: The previous character was CR
|
||||
* 2: The previous character was LF
|
||||
*/
|
||||
if ((c & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
|
||||
// Check the absolutely prohibited characters.
|
||||
switch (c) {
|
||||
case 0x0: // NULL
|
||||
throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
|
||||
case 0x0b: // Vertical tab
|
||||
throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq);
|
||||
case '\f':
|
||||
throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the CRLF (HT | SP) pattern
|
||||
switch (state) {
|
||||
case 0:
|
||||
switch (c) {
|
||||
case '\r':
|
||||
state = 1;
|
||||
break;
|
||||
case '\n':
|
||||
state = 2;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
switch (c) {
|
||||
case '\n':
|
||||
state = 2;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
switch (c) {
|
||||
case '\t':
|
||||
case ' ':
|
||||
state = 0;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
static class HttpHeadersNameConverter implements NameConverter<CharSequence> {
|
||||
protected final boolean validate;
|
||||
|
||||
private static final class ValidateNameProcessor implements ByteProcessor {
|
||||
private final CharSequence seq;
|
||||
|
||||
public ValidateNameProcessor(CharSequence seq) {
|
||||
this.seq = seq;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
// Check to see if the character is not an ASCII character.
|
||||
if (value < 0) {
|
||||
throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " + seq);
|
||||
}
|
||||
validateNameChar(value, seq);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
HttpHeadersNameConverter(boolean validate) {
|
||||
this.validate = validate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertName(CharSequence name) {
|
||||
if (validate) {
|
||||
if (name instanceof AsciiString) {
|
||||
validateName((AsciiString) name);
|
||||
} else {
|
||||
validateName(name);
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private static void validateName(AsciiString name) {
|
||||
try {
|
||||
name.forEachByte(new ValidateNameProcessor(name));
|
||||
} catch (Throwable t) {
|
||||
PlatformDependent.throwException(t);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateName(CharSequence name) {
|
||||
// Go through each characters in the name.
|
||||
for (int index = 0; index < name.length(); index++) {
|
||||
char c = name.charAt(index);
|
||||
|
||||
// Check to see if the character is not an ASCII character.
|
||||
if (c > 127) {
|
||||
throw new IllegalArgumentException("a header name cannot contain non-ASCII characters: " + name);
|
||||
}
|
||||
|
||||
// Check for prohibited characters.
|
||||
validateNameChar(c, name);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateNameChar(int character, CharSequence seq) {
|
||||
if ((character & HIGHEST_INVALID_NAME_CHAR_MASK) == 0 && LOOKUP_TABLE[character] != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
|
||||
seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final HttpHeadersValidationConverter
|
||||
VALIDATE_OBJECT_CONVERTER = new HttpHeadersValidationConverter(true);
|
||||
private static final HttpHeadersValidationConverter
|
||||
NO_VALIDATE_OBJECT_CONVERTER = new HttpHeadersValidationConverter(false);
|
||||
private static final HttpHeadersNameConverter VALIDATE_NAME_CONVERTER = new HttpHeadersNameConverter(true);
|
||||
private static final HttpHeadersNameConverter NO_VALIDATE_NAME_CONVERTER = new HttpHeadersNameConverter(false);
|
||||
|
||||
public DefaultHttpHeaders() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public DefaultHttpHeaders(boolean validate) {
|
||||
this(true, validate? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER, false);
|
||||
this(validate, false);
|
||||
}
|
||||
|
||||
protected DefaultHttpHeaders(boolean validate, boolean singleHeaderFields) {
|
||||
this(true, validate? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER, singleHeaderFields);
|
||||
this(true, validate ? HeaderNameValidator.INSTANCE : NO_NAME_VALIDATOR, singleHeaderFields);
|
||||
}
|
||||
|
||||
protected DefaultHttpHeaders(boolean validate, NameConverter<CharSequence> nameConverter,
|
||||
protected DefaultHttpHeaders(boolean validate,
|
||||
DefaultHeaders.NameValidator<CharSequence> nameValidator,
|
||||
boolean singleHeaderFields) {
|
||||
super(true, validate ? VALIDATE_OBJECT_CONVERTER : NO_VALIDATE_OBJECT_CONVERTER, nameConverter,
|
||||
singleHeaderFields);
|
||||
super(new TreeMap<CharSequence, Object>(AsciiString.CHARSEQUENCE_CASE_INSENSITIVE_ORDER),
|
||||
nameValidator,
|
||||
validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE,
|
||||
singleHeaderFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -479,4 +274,137 @@ public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeader
|
||||
super.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof HttpHeaders)) {
|
||||
return false;
|
||||
}
|
||||
HttpHeaders headers = (HttpHeaders) other;
|
||||
return DefaultHeaders.comparatorEquals(this, headers, AsciiString.CHARSEQUENCE_CASE_SENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
static final class HeaderNameValidator implements DefaultHeaders.NameValidator<CharSequence> {
|
||||
|
||||
public static final HeaderNameValidator INSTANCE = new HeaderNameValidator();
|
||||
|
||||
private HeaderNameValidator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(CharSequence name) {
|
||||
// Go through each character in the name
|
||||
for (int index = 0; index < name.length(); index++) {
|
||||
char character = name.charAt(index);
|
||||
|
||||
// Check to see if the character is not an ASCII character
|
||||
if (character > 127) {
|
||||
throw new IllegalArgumentException("a header name cannot contain non-ASCII characters: " + name);
|
||||
}
|
||||
|
||||
// Check for prohibited characters.
|
||||
if ((character & HIGHEST_INVALID_NAME_CHAR_MASK) == 0 && LOOKUP_TABLE[character] != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
|
||||
name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class HeaderValueConverter extends CharSequenceConverter {
|
||||
|
||||
public static final HeaderValueConverter INSTANCE = new HeaderValueConverter();
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
checkNotNull(value, "value");
|
||||
CharSequence seq;
|
||||
if (value instanceof CharSequence) {
|
||||
seq = (CharSequence) value;
|
||||
} else if (value instanceof Number) {
|
||||
seq = value.toString();
|
||||
} else if (value instanceof Date) {
|
||||
seq = HttpHeaderDateFormat.get().format((Date) value);
|
||||
} else if (value instanceof Calendar) {
|
||||
seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
|
||||
} else {
|
||||
seq = value.toString();
|
||||
}
|
||||
return seq;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HeaderValueConverterAndValidator extends HeaderValueConverter {
|
||||
|
||||
public static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
CharSequence seq = super.convertObject(value);
|
||||
int state = 0;
|
||||
// Start looping through each of the character
|
||||
for (int index = 0; index < seq.length(); index++) {
|
||||
state = validateValueChar(seq, state, seq.charAt(index));
|
||||
}
|
||||
|
||||
if (state != 0) {
|
||||
throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq);
|
||||
}
|
||||
return seq;
|
||||
}
|
||||
|
||||
private static int validateValueChar(CharSequence seq, int state, char character) {
|
||||
/*
|
||||
* State:
|
||||
* 0: Previous character was neither CR nor LF
|
||||
* 1: The previous character was CR
|
||||
* 2: The previous character was LF
|
||||
*/
|
||||
if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
|
||||
// Check the absolutely prohibited characters.
|
||||
switch (character) {
|
||||
case 0x0: // NULL
|
||||
throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq);
|
||||
case 0x0b: // Vertical tab
|
||||
throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq);
|
||||
case '\f':
|
||||
throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the CRLF (HT | SP) pattern
|
||||
switch (state) {
|
||||
case 0:
|
||||
switch (character) {
|
||||
case '\r':
|
||||
return 1;
|
||||
case '\n':
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
switch (character) {
|
||||
case '\n':
|
||||
return 2;
|
||||
default:
|
||||
throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq);
|
||||
}
|
||||
case 2:
|
||||
switch (character) {
|
||||
case '\t':
|
||||
case ' ':
|
||||
return 0;
|
||||
default:
|
||||
throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq);
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.DefaultHeaders;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
import java.util.Map;
|
||||
@ -107,32 +108,24 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
|
||||
}
|
||||
|
||||
private static final class TrailingHttpHeaders extends DefaultHttpHeaders {
|
||||
private static final class TrailingHttpHeadersNameConverter extends HttpHeadersNameConverter {
|
||||
TrailingHttpHeadersNameConverter(boolean validate) {
|
||||
super(validate);
|
||||
}
|
||||
private static final class TrailingHttpHeadersNameValidator implements
|
||||
DefaultHeaders.NameValidator<CharSequence> {
|
||||
|
||||
private static final TrailingHttpHeadersNameValidator INSTANCE = new TrailingHttpHeadersNameValidator();
|
||||
|
||||
@Override
|
||||
public CharSequence convertName(CharSequence name) {
|
||||
name = super.convertName(name);
|
||||
if (validate) {
|
||||
if (HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(name)
|
||||
|| HttpHeaderNames.TRANSFER_ENCODING.equalsIgnoreCase(name)
|
||||
|| HttpHeaderNames.TRAILER.equalsIgnoreCase(name)) {
|
||||
throw new IllegalArgumentException("prohibited trailing header: " + name);
|
||||
}
|
||||
public void validate(CharSequence name) {
|
||||
HeaderNameValidator.INSTANCE.validate(name);
|
||||
if (HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(name)
|
||||
|| HttpHeaderNames.TRANSFER_ENCODING.equalsIgnoreCase(name)
|
||||
|| HttpHeaderNames.TRAILER.equalsIgnoreCase(name)) {
|
||||
throw new IllegalArgumentException("prohibited trailing header: " + name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
private static final TrailingHttpHeadersNameConverter
|
||||
VALIDATE_NAME_CONVERTER = new TrailingHttpHeadersNameConverter(true);
|
||||
private static final TrailingHttpHeadersNameConverter
|
||||
NO_VALIDATE_NAME_CONVERTER = new TrailingHttpHeadersNameConverter(false);
|
||||
|
||||
TrailingHttpHeaders(boolean validate) {
|
||||
super(validate, validate ? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER, false);
|
||||
super(validate, validate ? TrailingHttpHeadersNameValidator.INSTANCE : NO_NAME_VALIDATOR, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@ -230,7 +231,9 @@ public final class HttpHeaderUtil {
|
||||
m.headers().add(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
|
||||
m.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
|
||||
} else {
|
||||
List<CharSequence> values = m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING);
|
||||
// Make a copy to be able to modify values while iterating
|
||||
List<CharSequence> values =
|
||||
new ArrayList<CharSequence>(m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING));
|
||||
if (values.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -18,24 +18,14 @@ package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.handler.codec.TextHeaders.EntryVisitor;
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
final class HttpHeadersEncoder {
|
||||
|
||||
final class HttpHeadersEncoder implements EntryVisitor {
|
||||
|
||||
private final ByteBuf buf;
|
||||
|
||||
HttpHeadersEncoder(ByteBuf buf) {
|
||||
this.buf = buf;
|
||||
private HttpHeadersEncoder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception {
|
||||
final CharSequence name = entry.getKey();
|
||||
final CharSequence value = entry.getValue();
|
||||
final ByteBuf buf = this.buf;
|
||||
public static void encoderHeader(CharSequence name, CharSequence value, ByteBuf buf) throws Exception {
|
||||
final int nameLen = name.length();
|
||||
final int valueLen = value.length();
|
||||
final int entryLen = nameLen + valueLen + 4;
|
||||
@ -50,7 +40,6 @@ final class HttpHeadersEncoder implements EntryVisitor {
|
||||
buf.setByte(offset ++, '\r');
|
||||
buf.setByte(offset ++, '\n');
|
||||
buf.writerIndex(offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) {
|
||||
|
@ -24,6 +24,7 @@ import io.netty.util.internal.PlatformDependent;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static io.netty.buffer.Unpooled.*;
|
||||
import static io.netty.handler.codec.http.HttpConstants.*;
|
||||
@ -137,7 +138,9 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
|
||||
* Encode the {@link HttpHeaders} into a {@link ByteBuf}.
|
||||
*/
|
||||
protected void encodeHeaders(HttpHeaders headers, ByteBuf buf) throws Exception {
|
||||
headers.forEachEntry(new HttpHeadersEncoder(buf));
|
||||
for (Entry<CharSequence, CharSequence> header : headers) {
|
||||
HttpHeadersEncoder.encoderHeader(header.getKey(), header.getValue(), buf);
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeChunkedContent(ChannelHandlerContext ctx, Object msg, long contentLength, List<Object> out) {
|
||||
|
@ -16,44 +16,17 @@
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.handler.codec.DefaultTextHeaders;
|
||||
import io.netty.handler.codec.Headers;
|
||||
import io.netty.handler.codec.TextHeaders;
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeaders {
|
||||
private static final Headers.ValueConverter<CharSequence> SPDY_VALUE_CONVERTER =
|
||||
new DefaultTextValueTypeConverter() {
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
CharSequence seq;
|
||||
if (value instanceof CharSequence) {
|
||||
seq = (CharSequence) value;
|
||||
} else {
|
||||
seq = value.toString();
|
||||
}
|
||||
|
||||
SpdyCodecUtil.validateHeaderValue(seq);
|
||||
return seq;
|
||||
}
|
||||
};
|
||||
|
||||
private static final NameConverter<CharSequence> SPDY_NAME_CONVERTER = new NameConverter<CharSequence>() {
|
||||
@Override
|
||||
public CharSequence convertName(CharSequence name) {
|
||||
if (name instanceof AsciiString) {
|
||||
name = ((AsciiString) name).toLowerCase();
|
||||
} else {
|
||||
name = name.toString().toLowerCase(Locale.US);
|
||||
}
|
||||
SpdyCodecUtil.validateHeaderName(name);
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
public DefaultSpdyHeaders() {
|
||||
super(true, SPDY_VALUE_CONVERTER, SPDY_NAME_CONVERTER);
|
||||
super(new LinkedHashMap<CharSequence, Object>(),
|
||||
HeaderNameValidator.INSTANCE,
|
||||
HeaderValueConverterAndValidator.INSTANCE,
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -259,4 +232,32 @@ public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeader
|
||||
super.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
private static class HeaderNameValidator implements NameValidator<CharSequence> {
|
||||
|
||||
public static final HeaderNameValidator INSTANCE = new HeaderNameValidator();
|
||||
|
||||
@Override
|
||||
public void validate(CharSequence name) {
|
||||
SpdyCodecUtil.validateHeaderName(name);
|
||||
}
|
||||
}
|
||||
|
||||
private static class HeaderValueConverterAndValidator extends CharSequenceConverter {
|
||||
|
||||
public static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
CharSequence seq;
|
||||
if (value instanceof CharSequence) {
|
||||
seq = (CharSequence) value;
|
||||
} else {
|
||||
seq = value.toString();
|
||||
}
|
||||
|
||||
SpdyCodecUtil.validateHeaderValue(seq);
|
||||
return seq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -305,6 +305,9 @@ final class SpdyCodecUtil {
|
||||
throw new IllegalArgumentException(
|
||||
"name contains null character: " + name);
|
||||
}
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
throw new IllegalArgumentException("name must be all lower case.");
|
||||
}
|
||||
if (c > 127) {
|
||||
throw new IllegalArgumentException(
|
||||
"name contains non-ascii character: " + name);
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2015 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.http;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class DefaultHttpHeadersTest {
|
||||
|
||||
@Test
|
||||
public void keysShouldBeCaseInsensitive() {
|
||||
DefaultHttpHeaders headers = new DefaultHttpHeaders();
|
||||
headers.add("Name", "value1");
|
||||
headers.add("name", "value2");
|
||||
headers.add("NAME", "value3");
|
||||
assertEquals(3, headers.size());
|
||||
|
||||
List<String> values = asList("value1", "value2", "value3");
|
||||
|
||||
assertEquals(values, headers.getAll("NAME"));
|
||||
assertEquals(values, headers.getAll("name"));
|
||||
assertEquals(values, headers.getAll("Name"));
|
||||
assertEquals(values, headers.getAll("nAmE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keysShouldBeCaseInsensitiveInHeadersEquals() {
|
||||
DefaultHttpHeaders headers1 = new DefaultHttpHeaders();
|
||||
headers1.add("name1", "value1", "value2", "value3");
|
||||
headers1.add("nAmE2", "value4");
|
||||
|
||||
DefaultHttpHeaders headers2 = new DefaultHttpHeaders();
|
||||
headers2.add("naMe1", "value1", "value2", "value3");
|
||||
headers2.add("NAME2", "value4");
|
||||
|
||||
assertEquals(headers1, headers1);
|
||||
assertEquals(headers2, headers2);
|
||||
assertEquals(headers1, headers2);
|
||||
assertEquals(headers2, headers1);
|
||||
assertEquals(headers1.hashCode(), headers2.hashCode());
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -83,7 +84,7 @@ public class SpdySessionHandlerTest {
|
||||
assertEquals(last, spdyHeadersFrame.isLast());
|
||||
for (CharSequence name: headers.names()) {
|
||||
List<CharSequence> expectedValues = headers.getAll(name);
|
||||
List<CharSequence> receivedValues = spdyHeadersFrame.headers().getAll(name);
|
||||
List<CharSequence> receivedValues = new ArrayList<CharSequence>(spdyHeadersFrame.headers().getAll(name));
|
||||
assertTrue(receivedValues.containsAll(expectedValues));
|
||||
receivedValues.removeAll(expectedValues);
|
||||
assertTrue(receivedValues.isEmpty());
|
||||
@ -105,7 +106,7 @@ public class SpdySessionHandlerTest {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0);
|
||||
spdySynStreamFrame.headers().set("Compression", "test");
|
||||
spdySynStreamFrame.headers().set("compression", "test");
|
||||
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId);
|
||||
spdyDataFrame.setLast(true);
|
||||
@ -138,8 +139,8 @@ public class SpdySessionHandlerTest {
|
||||
assertNull(sessionHandler.readOutbound());
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(localStreamId);
|
||||
|
||||
spdyHeadersFrame.headers().add("HEADER", "test1");
|
||||
spdyHeadersFrame.headers().add("HEADER", "test2");
|
||||
spdyHeadersFrame.headers().add("header", "test1");
|
||||
spdyHeadersFrame.headers().add("header", "test2");
|
||||
|
||||
sessionHandler.writeInbound(spdyHeadersFrame);
|
||||
assertHeaders(sessionHandler.readOutbound(), localStreamId, false, spdyHeadersFrame.headers());
|
||||
@ -245,7 +246,7 @@ public class SpdySessionHandlerTest {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0);
|
||||
spdySynStreamFrame.headers().set("Compression", "test");
|
||||
spdySynStreamFrame.headers().set("compression", "test");
|
||||
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId);
|
||||
spdyDataFrame.setLast(true);
|
||||
|
@ -14,86 +14,19 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.util.internal.StringUtil.UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET;
|
||||
import io.netty.handler.codec.BinaryHeaders;
|
||||
import io.netty.handler.codec.DefaultBinaryHeaders;
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.ByteProcessor;
|
||||
import io.netty.handler.codec.DefaultHeaders;
|
||||
import io.netty.util.ByteString;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import java.io.Serializable;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class DefaultHttp2Headers extends DefaultBinaryHeaders implements Http2Headers {
|
||||
private static final ByteProcessor HTTP2_ASCII_UPPERCASE_PROCESSOR = new ByteProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
return value < 'A' || value > 'Z';
|
||||
}
|
||||
};
|
||||
|
||||
private static final class Http2AsciiToLowerCaseConverter implements ByteProcessor {
|
||||
private final byte[] result;
|
||||
private int i;
|
||||
|
||||
public Http2AsciiToLowerCaseConverter(int length) {
|
||||
result = new byte[length];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
result[i++] = (value >= 'A' && value <= 'Z')
|
||||
? (byte) (value + UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET) : value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public byte[] result() {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
private static final NameConverter<ByteString> HTTP2_ASCII_TO_LOWER_CONVERTER = new NameConverter<ByteString>() {
|
||||
@Override
|
||||
public ByteString convertName(ByteString name) {
|
||||
if (name instanceof AsciiString) {
|
||||
return ((AsciiString) name).toLowerCase();
|
||||
}
|
||||
|
||||
try {
|
||||
if (name.forEachByte(HTTP2_ASCII_UPPERCASE_PROCESSOR) == -1) {
|
||||
return name;
|
||||
}
|
||||
|
||||
Http2AsciiToLowerCaseConverter converter = new Http2AsciiToLowerCaseConverter(name.length());
|
||||
name.forEachByte(converter);
|
||||
return new ByteString(converter.result(), false);
|
||||
} catch (Exception e) {
|
||||
PlatformDependent.throwException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an instance that will convert all header names to lowercase.
|
||||
*/
|
||||
public DefaultHttp2Headers() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance that can be configured to either do header field name conversion to
|
||||
* lowercase, or not do any conversion at all.
|
||||
* <p>
|
||||
*
|
||||
* <strong>Note</strong> that setting {@code forceKeyToLower} to {@code false} can violate the
|
||||
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2">HTTP/2 specification</a>
|
||||
* which specifies that a request or response containing an uppercase header field MUST be treated
|
||||
* as malformed. Only set {@code forceKeyToLower} to {@code false} if you are explicitly using lowercase
|
||||
* header field names and want to avoid the conversion to lowercase.
|
||||
*
|
||||
* @param forceKeyToLower if @{code false} no header name conversion will be performed
|
||||
*/
|
||||
public DefaultHttp2Headers(boolean forceKeyToLower) {
|
||||
super(forceKeyToLower ? HTTP2_ASCII_TO_LOWER_CONVERTER : IDENTITY_NAME_CONVERTER);
|
||||
super(new TreeMap<ByteString, Object>(Http2HeaderNameComparator.INSTANCE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -354,4 +287,45 @@ public class DefaultHttp2Headers extends DefaultBinaryHeaders implements Http2He
|
||||
public ByteString status() {
|
||||
return get(PseudoHeaderName.STATUS.value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof Http2Headers)) {
|
||||
return false;
|
||||
}
|
||||
Http2Headers headers = (Http2Headers) other;
|
||||
return DefaultHeaders.comparatorEquals(this, headers, ByteString.DEFAULT_COMPARATOR);
|
||||
}
|
||||
|
||||
private static class Http2HeaderNameComparator implements Comparator<ByteString>, Serializable {
|
||||
|
||||
public static final Http2HeaderNameComparator INSTANCE = new Http2HeaderNameComparator();
|
||||
private static final long serialVersionUID = 1109871697664666478L;
|
||||
|
||||
@Override
|
||||
public int compare(ByteString one, ByteString two) {
|
||||
// Reserved header names come first.
|
||||
final boolean isPseudoHeader1 = !one.isEmpty() && one.byteAt(0) == ':';
|
||||
final boolean isPseudoHeader2 = !two.isEmpty() && two.byteAt(0) == ':';
|
||||
if (isPseudoHeader1 != isPseudoHeader2) {
|
||||
return isPseudoHeader1 ? -1 : 1;
|
||||
}
|
||||
final int delta = one.hashCode() - two.hashCode();
|
||||
if (delta == 0) {
|
||||
// If the hash code matches it's very likely for the two strings to be equal
|
||||
// and thus we optimistically compare them with the much faster equals method.
|
||||
if (one.equals(two)) {
|
||||
return 0;
|
||||
} else {
|
||||
return ByteString.DEFAULT_COMPARATOR.compare(one, two);
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,9 +23,9 @@ import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.handler.codec.BinaryHeaders.EntryVisitor;
|
||||
import io.netty.util.ByteString;
|
||||
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@ -65,26 +65,9 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
|
||||
tableSizeChangeOutput.reset();
|
||||
}
|
||||
|
||||
// Write pseudo headers first as required by the HTTP/2 spec.
|
||||
for (Http2Headers.PseudoHeaderName pseudoHeader : Http2Headers.PseudoHeaderName.values()) {
|
||||
ByteString name = pseudoHeader.value();
|
||||
ByteString value = headers.get(name);
|
||||
if (value != null) {
|
||||
encodeHeader(name, value, stream);
|
||||
}
|
||||
for (Entry<ByteString, ByteString> header : headers) {
|
||||
encodeHeader(header.getKey(), header.getValue(), stream);
|
||||
}
|
||||
|
||||
headers.forEachEntry(new EntryVisitor() {
|
||||
@Override
|
||||
public boolean visit(Entry<ByteString, ByteString> entry) throws Exception {
|
||||
final ByteString name = entry.getKey();
|
||||
final ByteString value = entry.getValue();
|
||||
if (!Http2Headers.PseudoHeaderName.isPseudoHeader(name)) {
|
||||
encodeHeader(name, value, stream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} catch (Http2Exception e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
|
@ -20,6 +20,8 @@ import io.netty.util.ByteString;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -183,6 +185,14 @@ public interface Http2Headers extends BinaryHeaders {
|
||||
@Override
|
||||
Http2Headers clear();
|
||||
|
||||
/**
|
||||
* Returns an iterator over all HTTP/2 headers from this instance. The iteration order is as follows:
|
||||
* 1. All non-pseudo headers (in no particular order).
|
||||
* 2. Headers with multiple values will have their values appear in insertion order.
|
||||
*/
|
||||
@Override
|
||||
Iterator<Entry<ByteString, ByteString>> iterator();
|
||||
|
||||
/**
|
||||
* Sets the {@link PseudoHeaderName#METHOD} header or {@code null} if there is no such header
|
||||
*/
|
||||
|
@ -18,8 +18,6 @@ import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.streamError;
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import io.netty.handler.codec.BinaryHeaders;
|
||||
import io.netty.handler.codec.TextHeaders.EntryVisitor;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpMessage;
|
||||
@ -246,9 +244,11 @@ public final class HttpUtil {
|
||||
FullHttpMessage destinationMessage, boolean addToTrailer) throws Http2Exception {
|
||||
HttpHeaders headers = addToTrailer ? destinationMessage.trailingHeaders() : destinationMessage.headers();
|
||||
boolean request = destinationMessage instanceof HttpRequest;
|
||||
Http2ToHttpHeaderTranslator visitor = new Http2ToHttpHeaderTranslator(streamId, headers, request);
|
||||
Http2ToHttpHeaderTranslator translator = new Http2ToHttpHeaderTranslator(streamId, headers, request);
|
||||
try {
|
||||
sourceHeaders.forEachEntry(visitor);
|
||||
for (Entry<ByteString, ByteString> entry : sourceHeaders) {
|
||||
translator.translate(entry);
|
||||
}
|
||||
} catch (Http2Exception ex) {
|
||||
throw ex;
|
||||
} catch (Throwable t) {
|
||||
@ -315,29 +315,25 @@ public final class HttpUtil {
|
||||
|
||||
final Http2Headers out = new DefaultHttp2Headers();
|
||||
|
||||
inHeaders.forEachEntry(new EntryVisitor() {
|
||||
@Override
|
||||
public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception {
|
||||
final AsciiString aName = AsciiString.of(entry.getKey()).toLowerCase();
|
||||
if (!HTTP_TO_HTTP2_HEADER_BLACKLIST.contains(aName)) {
|
||||
AsciiString aValue = AsciiString.of(entry.getValue());
|
||||
// https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.2
|
||||
// makes a special exception for TE
|
||||
if (!aName.equalsIgnoreCase(HttpHeaderNames.TE) ||
|
||||
aValue.equalsIgnoreCase(HttpHeaderValues.TRAILERS)) {
|
||||
out.add(aName, aValue);
|
||||
}
|
||||
for (Entry<CharSequence, CharSequence> entry : inHeaders) {
|
||||
final AsciiString aName = AsciiString.of(entry.getKey()).toLowerCase();
|
||||
if (!HTTP_TO_HTTP2_HEADER_BLACKLIST.contains(aName)) {
|
||||
AsciiString aValue = AsciiString.of(entry.getValue());
|
||||
// https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.2
|
||||
// makes a special exception for TE
|
||||
if (!aName.equalsIgnoreCase(HttpHeaderNames.TE) ||
|
||||
aValue.equalsIgnoreCase(HttpHeaderValues.TRAILERS)) {
|
||||
out.add(aName, aValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* A visitor which translates HTTP/2 headers to HTTP/1 headers
|
||||
*/
|
||||
private static final class Http2ToHttpHeaderTranslator implements BinaryHeaders.EntryVisitor {
|
||||
private static final class Http2ToHttpHeaderTranslator {
|
||||
/**
|
||||
* Translations from HTTP/2 header name to the HTTP/1.x equivalent.
|
||||
*/
|
||||
@ -372,8 +368,7 @@ public final class HttpUtil {
|
||||
translations = request ? REQUEST_HEADER_TRANSLATIONS : RESPONSE_HEADER_TRANSLATIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(Entry<ByteString, ByteString> entry) throws Http2Exception {
|
||||
public void translate(Entry<ByteString, ByteString> entry) throws Http2Exception {
|
||||
final ByteString name = entry.getKey();
|
||||
final ByteString value = entry.getValue();
|
||||
ByteString translatedName = translations.get(name);
|
||||
@ -391,7 +386,6 @@ public final class HttpUtil {
|
||||
output.add(new AsciiString(translatedName, false), new AsciiString(value, false));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.TextHeaders.EntryVisitor;
|
||||
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
||||
import io.netty.handler.codec.http.FullHttpMessage;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
@ -137,13 +136,9 @@ public final class InboundHttp2ToHttpPriorityAdapter extends InboundHttp2ToHttpA
|
||||
*/
|
||||
private static void addHttpHeadersToHttp2Headers(HttpHeaders httpHeaders, final Http2Headers http2Headers) {
|
||||
try {
|
||||
httpHeaders.forEachEntry(new EntryVisitor() {
|
||||
@Override
|
||||
public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception {
|
||||
http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue()));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
for (Entry<CharSequence, CharSequence> entry : httpHeaders) {
|
||||
http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue()));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
PlatformDependent.throwException(ex);
|
||||
}
|
||||
|
@ -1,55 +1,57 @@
|
||||
/*
|
||||
* Copyright 2014 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:
|
||||
* 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
|
||||
* 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.
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.ByteString;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DefaultHttp2HeadersTest {
|
||||
|
||||
private static final byte[] NAME_BYTES = { 'T', 'E', 's', 'T' };
|
||||
private static final byte[] NAME_BYTES_LOWERCASE = { 't', 'e', 's', 't' };
|
||||
private static final ByteString NAME_BYTESTRING = new ByteString(NAME_BYTES);
|
||||
private static final AsciiString NAME_ASCIISTRING = new AsciiString("Test");
|
||||
private static final ByteString VALUE = new ByteString("some value", CharsetUtil.UTF_8);
|
||||
|
||||
@Test
|
||||
public void defaultLowercase() {
|
||||
Http2Headers headers = new DefaultHttp2Headers().set(NAME_BYTESTRING, VALUE);
|
||||
assertArrayEquals(NAME_BYTES_LOWERCASE, first(headers).toByteArray());
|
||||
public void pseudoHeadersMustComeFirstWhenIterating() {
|
||||
DefaultHttp2Headers headers = new DefaultHttp2Headers();
|
||||
headers.add(bs("name1"), bs("value1"), bs("value2"));
|
||||
headers.method(bs("POST"));
|
||||
headers.add(bs("2name"), bs("value3"));
|
||||
headers.path(bs("/index.html"));
|
||||
headers.status(bs("200"));
|
||||
headers.authority(bs("netty.io"));
|
||||
headers.add(bs("name3"), bs("value4"));
|
||||
headers.scheme(bs("https"));
|
||||
|
||||
Iterator<Entry<ByteString, ByteString>> iter = headers.iterator();
|
||||
List<ByteString> names = new ArrayList<ByteString>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
names.add(iter.next().getKey());
|
||||
}
|
||||
assertTrue(names.containsAll(asList(bs(":method"), bs(":status"), bs(":path"), bs(":scheme"),
|
||||
bs(":authority"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultLowercaseAsciiString() {
|
||||
Http2Headers headers = new DefaultHttp2Headers().set(NAME_ASCIISTRING, VALUE);
|
||||
assertEquals(NAME_ASCIISTRING.toLowerCase(), first(headers));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void caseInsensitive() {
|
||||
Http2Headers headers = new DefaultHttp2Headers(false).set(NAME_BYTESTRING, VALUE);
|
||||
assertArrayEquals(NAME_BYTES, first(headers).toByteArray());
|
||||
}
|
||||
|
||||
private static ByteString first(Http2Headers headers) {
|
||||
return headers.names().iterator().next();
|
||||
private static ByteString bs(String str) {
|
||||
return new ByteString(str, CharsetUtil.US_ASCII);
|
||||
}
|
||||
}
|
||||
|
@ -60,9 +60,9 @@ final class Http2TestUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte array into an {@link AsciiString}.
|
||||
* Converts a byte array into a {@link ByteString}.
|
||||
*/
|
||||
public static ByteString as(byte[] value) {
|
||||
public static ByteString bs(byte[] value) {
|
||||
return new ByteString(value);
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ final class Http2TestUtil {
|
||||
* Returns an {@link AsciiString} that wraps a randomly-filled byte array.
|
||||
*/
|
||||
public static ByteString randomString() {
|
||||
return as(randomBytes());
|
||||
return bs(randomBytes());
|
||||
}
|
||||
|
||||
private Http2TestUtil() {
|
||||
|
@ -19,8 +19,14 @@ package io.netty.handler.codec.stomp;
|
||||
import io.netty.handler.codec.DefaultTextHeaders;
|
||||
import io.netty.handler.codec.TextHeaders;
|
||||
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class DefaultStompHeaders extends DefaultTextHeaders implements StompHeaders {
|
||||
|
||||
public DefaultStompHeaders() {
|
||||
super(new TreeMap<CharSequence, Object>(), NO_NAME_VALIDATOR, CharSequenceConverter.INSTANCE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StompHeaders add(CharSequence name, CharSequence value) {
|
||||
super.add(name, value);
|
||||
|
@ -25,6 +25,7 @@ import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Encodes a {@link StompFrame} or a {@link StompSubframe} into a {@link ByteBuf}.
|
||||
@ -66,11 +67,9 @@ public class StompSubframeEncoder extends MessageToMessageEncoder<StompSubframe>
|
||||
|
||||
buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII));
|
||||
buf.writeByte(StompConstants.LF);
|
||||
try {
|
||||
frame.headers().forEachEntry(new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF));
|
||||
} catch (Exception ex) {
|
||||
buf.release();
|
||||
PlatformDependent.throwException(ex);
|
||||
AsciiHeadersEncoder headersEncoder = new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF);
|
||||
for (Entry<CharSequence, CharSequence> entry : frame.headers()) {
|
||||
headersEncoder.encode(entry);
|
||||
}
|
||||
buf.writeByte(StompConstants.LF);
|
||||
return buf;
|
||||
|
@ -18,8 +18,8 @@ package io.netty.handler.codec.stomp;
|
||||
public final class StompTestConstants {
|
||||
public static final String CONNECT_FRAME =
|
||||
"CONNECT\n" +
|
||||
"host:stomp.github.org\n" +
|
||||
"accept-version:1.1,1.2\n" +
|
||||
"host:stomp.github.org\n" +
|
||||
'\n' +
|
||||
'\0';
|
||||
public static final String CONNECTED_FRAME =
|
||||
|
@ -21,10 +21,9 @@ import java.util.Map.Entry;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.handler.codec.TextHeaders.EntryVisitor;
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
public final class AsciiHeadersEncoder implements EntryVisitor {
|
||||
public final class AsciiHeadersEncoder {
|
||||
|
||||
/**
|
||||
* The separator characters to insert between a header name and a header value.
|
||||
@ -78,8 +77,7 @@ public final class AsciiHeadersEncoder implements EntryVisitor {
|
||||
this.newlineType = newlineType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception {
|
||||
public void encode(Entry<CharSequence, CharSequence> entry) {
|
||||
final CharSequence name = entry.getKey();
|
||||
final CharSequence value = entry.getValue();
|
||||
final ByteBuf buf = this.buf;
|
||||
@ -119,7 +117,6 @@ public final class AsciiHeadersEncoder implements EntryVisitor {
|
||||
}
|
||||
|
||||
buf.writerIndex(offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) {
|
||||
|
@ -24,17 +24,6 @@ import io.netty.util.ByteString;
|
||||
* some additional utility when handling text data.
|
||||
*/
|
||||
public interface BinaryHeaders extends Headers<ByteString> {
|
||||
/**
|
||||
* Provides an abstraction to iterate over elements maintained in the {@link Headers} collection.
|
||||
*/
|
||||
interface EntryVisitor extends Headers.EntryVisitor<ByteString> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an abstraction to iterate over elements maintained in the {@link Headers} collection.
|
||||
*/
|
||||
interface NameVisitor extends Headers.NameVisitor<ByteString> {
|
||||
}
|
||||
|
||||
@Override
|
||||
BinaryHeaders add(ByteString name, ByteString value);
|
||||
|
@ -89,13 +89,6 @@ public interface ConvertibleHeaders<UnconvertedType, ConvertedType> extends Head
|
||||
*/
|
||||
List<ConvertedType> getAllAndRemoveAndConvert(UnconvertedType name);
|
||||
|
||||
/**
|
||||
* Invokes {@link Headers#entries()} and lazily does a conversion on the results as they are accessed
|
||||
*
|
||||
* @return The values corresponding to {@code name} and then lazily converted
|
||||
*/
|
||||
List<Map.Entry<ConvertedType, ConvertedType>> entriesConverted();
|
||||
|
||||
/**
|
||||
* Invokes {@link Headers#iterator()} and lazily does a conversion on the results as they are accessed
|
||||
*
|
||||
|
@ -20,128 +20,24 @@ import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.ParseException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class DefaultBinaryHeaders extends DefaultHeaders<ByteString> implements BinaryHeaders {
|
||||
private static final ValueConverter<ByteString> OBJECT_TO_BYTE = new ValueConverter<ByteString>() {
|
||||
private final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8;
|
||||
@Override
|
||||
public ByteString convertObject(Object value) {
|
||||
if (value instanceof ByteString) {
|
||||
return (ByteString) value;
|
||||
}
|
||||
if (value instanceof CharSequence) {
|
||||
return new ByteString((CharSequence) value, DEFAULT_CHARSET);
|
||||
}
|
||||
return new ByteString(value.toString(), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertInt(int value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertLong(long value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertDouble(double value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertChar(char value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertBoolean(boolean value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertFloat(float value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertToInt(ByteString value) {
|
||||
return value.parseAsciiInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToLong(ByteString value) {
|
||||
return value.parseAsciiLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertTimeMillis(long value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToTimeMillis(ByteString value) {
|
||||
try {
|
||||
return HeaderDateFormat.get().parse(value.toString());
|
||||
} catch (ParseException e) {
|
||||
PlatformDependent.throwException(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double convertToDouble(ByteString value) {
|
||||
return value.parseAsciiDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char convertToChar(ByteString value) {
|
||||
return value.parseChar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean convertToBoolean(ByteString value) {
|
||||
return value.byteAt(0) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float convertToFloat(ByteString value) {
|
||||
return value.parseAsciiFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertShort(short value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short convertToShort(ByteString value) {
|
||||
return value.parseAsciiShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertByte(byte value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte convertToByte(ByteString value) {
|
||||
return value.byteAt(0);
|
||||
}
|
||||
};
|
||||
|
||||
private static final HashCodeGenerator<ByteString> JAVA_HASH_CODE_GENERATOR =
|
||||
new JavaHashCodeGenerator<ByteString>();
|
||||
protected static final NameConverter<ByteString> IDENTITY_NAME_CONVERTER = new IdentityNameConverter<ByteString>();
|
||||
private static final NameValidator<ByteString> NO_NAME_VALIDATOR = DefaultHeaders.NoNameValidator.instance();
|
||||
|
||||
public DefaultBinaryHeaders() {
|
||||
this(IDENTITY_NAME_CONVERTER);
|
||||
this(new TreeMap<ByteString, Object>(ByteString.DEFAULT_COMPARATOR));
|
||||
}
|
||||
|
||||
public DefaultBinaryHeaders(NameConverter<ByteString> nameConverter) {
|
||||
super(ByteString.DEFAULT_COMPARATOR, ByteString.DEFAULT_COMPARATOR,
|
||||
JAVA_HASH_CODE_GENERATOR, OBJECT_TO_BYTE, nameConverter);
|
||||
public DefaultBinaryHeaders(Map<ByteString, Object> map) {
|
||||
this(map, NO_NAME_VALIDATOR);
|
||||
}
|
||||
|
||||
public DefaultBinaryHeaders(Map<ByteString, Object> map, NameValidator<ByteString> nameValidator) {
|
||||
super(map, nameValidator, ByteStringConverter.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -347,4 +243,119 @@ public class DefaultBinaryHeaders extends DefaultHeaders<ByteString> implements
|
||||
super.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
public static final class ByteStringConverter implements ValueConverter<ByteString> {
|
||||
|
||||
public static final ByteStringConverter INSTANCE = new ByteStringConverter();
|
||||
private static final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8;
|
||||
|
||||
private ByteStringConverter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertObject(Object value) {
|
||||
if (value instanceof ByteString) {
|
||||
return (ByteString) value;
|
||||
}
|
||||
if (value instanceof CharSequence) {
|
||||
return new ByteString((CharSequence) value, DEFAULT_CHARSET);
|
||||
}
|
||||
return new ByteString(value.toString(), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertInt(int value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertLong(long value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertDouble(double value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertChar(char value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertBoolean(boolean value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertFloat(float value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertToInt(ByteString value) {
|
||||
return value.parseAsciiInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToLong(ByteString value) {
|
||||
return value.parseAsciiLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertTimeMillis(long value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToTimeMillis(ByteString value) {
|
||||
try {
|
||||
return HeaderDateFormat.get().parse(value.toString());
|
||||
} catch (ParseException e) {
|
||||
PlatformDependent.throwException(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double convertToDouble(ByteString value) {
|
||||
return value.parseAsciiDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char convertToChar(ByteString value) {
|
||||
return value.parseChar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean convertToBoolean(ByteString value) {
|
||||
return value.byteAt(0) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float convertToFloat(ByteString value) {
|
||||
return value.parseAsciiFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertShort(short value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short convertToShort(ByteString value) {
|
||||
return value.parseAsciiShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertByte(byte value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte convertToByte(ByteString value) {
|
||||
return value.byteAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
@ -27,22 +28,11 @@ public class DefaultConvertibleHeaders<UnconvertedType, ConvertedType> extends D
|
||||
|
||||
private final TypeConverter<UnconvertedType, ConvertedType> typeConverter;
|
||||
|
||||
public DefaultConvertibleHeaders(Comparator<? super UnconvertedType> keyComparator,
|
||||
Comparator<? super UnconvertedType> valueComparator,
|
||||
HashCodeGenerator<UnconvertedType> hashCodeGenerator,
|
||||
ValueConverter<UnconvertedType> valueConverter,
|
||||
TypeConverter<UnconvertedType, ConvertedType> typeConverter) {
|
||||
super(keyComparator, valueComparator, hashCodeGenerator, valueConverter);
|
||||
this.typeConverter = typeConverter;
|
||||
}
|
||||
|
||||
public DefaultConvertibleHeaders(Comparator<? super UnconvertedType> keyComparator,
|
||||
Comparator<? super UnconvertedType> valueComparator,
|
||||
HashCodeGenerator<UnconvertedType> hashCodeGenerator,
|
||||
ValueConverter<UnconvertedType> valueConverter,
|
||||
TypeConverter<UnconvertedType, ConvertedType> typeConverter,
|
||||
NameConverter<UnconvertedType> nameConverter) {
|
||||
super(keyComparator, valueComparator, hashCodeGenerator, valueConverter, nameConverter);
|
||||
public DefaultConvertibleHeaders(Map<UnconvertedType, Object> map,
|
||||
NameValidator<UnconvertedType> nameValidator,
|
||||
ValueConverter<UnconvertedType> valueConverter,
|
||||
TypeConverter<UnconvertedType, ConvertedType> typeConverter) {
|
||||
super(map, nameValidator, valueConverter);
|
||||
this.typeConverter = typeConverter;
|
||||
}
|
||||
|
||||
@ -94,17 +84,6 @@ public class DefaultConvertibleHeaders<UnconvertedType, ConvertedType> extends D
|
||||
return allConverted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Entry<ConvertedType, ConvertedType>> entriesConverted() {
|
||||
List<Entry<UnconvertedType, UnconvertedType>> entries = entries();
|
||||
List<Entry<ConvertedType, ConvertedType>> entriesConverted = new ArrayList<Entry<ConvertedType, ConvertedType>>(
|
||||
entries.size());
|
||||
for (int i = 0; i < entries.size(); ++i) {
|
||||
entriesConverted.add(new ConvertedEntry(entries.get(i)));
|
||||
}
|
||||
return entriesConverted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<ConvertedType, ConvertedType>> iteratorConverted() {
|
||||
return new ConvertedIterator();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -24,184 +24,37 @@ import io.netty.util.internal.PlatformDependent;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence, String> implements TextHeaders {
|
||||
private static final HashCodeGenerator<CharSequence> CHARSEQUECE_CASE_INSENSITIVE_HASH_CODE_GENERATOR =
|
||||
new HashCodeGenerator<CharSequence>() {
|
||||
@Override
|
||||
public int generateHashCode(CharSequence name) {
|
||||
return AsciiString.caseInsensitiveHashCode(name);
|
||||
}
|
||||
};
|
||||
|
||||
private static final HashCodeGenerator<CharSequence> CHARSEQUECE_CASE_SENSITIVE_HASH_CODE_GENERATOR =
|
||||
new JavaHashCodeGenerator<CharSequence>();
|
||||
|
||||
public static class DefaultTextValueTypeConverter implements ValueConverter<CharSequence> {
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
if (value instanceof CharSequence) {
|
||||
return (CharSequence) value;
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertInt(int value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertLong(long value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertDouble(double value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertChar(char value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertBoolean(boolean value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertFloat(float value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean convertToBoolean(CharSequence value) {
|
||||
return Boolean.parseBoolean(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertByte(byte value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte convertToByte(CharSequence value) {
|
||||
return Byte.valueOf(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public char convertToChar(CharSequence value) {
|
||||
return value.charAt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertShort(short value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short convertToShort(CharSequence value) {
|
||||
return Short.valueOf(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertToInt(CharSequence value) {
|
||||
return Integer.parseInt(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToLong(CharSequence value) {
|
||||
return Long.parseLong(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsciiString convertTimeMillis(long value) {
|
||||
return new AsciiString(String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToTimeMillis(CharSequence value) {
|
||||
try {
|
||||
return HeaderDateFormat.get().parse(value.toString());
|
||||
} catch (ParseException e) {
|
||||
PlatformDependent.throwException(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float convertToFloat(CharSequence value) {
|
||||
return Float.valueOf(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double convertToDouble(CharSequence value) {
|
||||
return Double.valueOf(value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static final ValueConverter<CharSequence> CHARSEQUENCE_FROM_OBJECT_CONVERTER =
|
||||
new DefaultTextValueTypeConverter();
|
||||
private static final TypeConverter<CharSequence, String> CHARSEQUENCE_TO_STRING_CONVERTER =
|
||||
new TypeConverter<CharSequence, String>() {
|
||||
@Override
|
||||
public String toConvertedType(CharSequence value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toUnconvertedType(String value) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
private static final NameConverter<CharSequence> CHARSEQUENCE_IDENTITY_CONVERTER =
|
||||
new IdentityNameConverter<CharSequence>();
|
||||
/**
|
||||
* An estimate of the size of a header value.
|
||||
*/
|
||||
private static final int DEFAULT_VALUE_SIZE = 10;
|
||||
public static final NameValidator<CharSequence> NO_NAME_VALIDATOR = NoNameValidator.instance();
|
||||
|
||||
private final ValuesComposer valuesComposer;
|
||||
|
||||
public DefaultTextHeaders() {
|
||||
this(true);
|
||||
this(false);
|
||||
}
|
||||
|
||||
public DefaultTextHeaders(boolean ignoreCase) {
|
||||
this(ignoreCase, CHARSEQUENCE_FROM_OBJECT_CONVERTER, CHARSEQUENCE_IDENTITY_CONVERTER, false);
|
||||
public DefaultTextHeaders(boolean singleHeaderFields) {
|
||||
this(new TreeMap<CharSequence, Object>(CHARSEQUENCE_CASE_INSENSITIVE_ORDER), NO_NAME_VALIDATOR,
|
||||
CharSequenceConverter.INSTANCE, singleHeaderFields);
|
||||
}
|
||||
|
||||
public DefaultTextHeaders(boolean ignoreCase, boolean singleHeaderFields) {
|
||||
this(ignoreCase, CHARSEQUENCE_FROM_OBJECT_CONVERTER, CHARSEQUENCE_IDENTITY_CONVERTER, singleHeaderFields);
|
||||
}
|
||||
|
||||
protected DefaultTextHeaders(boolean ignoreCase, Headers.ValueConverter<CharSequence> valueConverter,
|
||||
NameConverter<CharSequence> nameConverter) {
|
||||
this(ignoreCase, valueConverter, nameConverter, false);
|
||||
}
|
||||
|
||||
public DefaultTextHeaders(boolean ignoreCase, ValueConverter<CharSequence> valueConverter,
|
||||
NameConverter<CharSequence> nameConverter, boolean singleHeaderFields) {
|
||||
super(comparator(ignoreCase), comparator(ignoreCase),
|
||||
ignoreCase ? CHARSEQUECE_CASE_INSENSITIVE_HASH_CODE_GENERATOR
|
||||
: CHARSEQUECE_CASE_SENSITIVE_HASH_CODE_GENERATOR, valueConverter,
|
||||
CHARSEQUENCE_TO_STRING_CONVERTER, nameConverter);
|
||||
public DefaultTextHeaders(Map<CharSequence, Object> map,
|
||||
NameValidator<CharSequence> nameValidator,
|
||||
ValueConverter<CharSequence> valueConverter,
|
||||
boolean singleHeaderFields) {
|
||||
super(map, nameValidator, valueConverter, CharSequenceToStringConverter.INSTANCE);
|
||||
valuesComposer = singleHeaderFields ? new SingleHeaderValuesComposer() : new MultipleFieldsValueComposer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
|
||||
return contains(name, value, comparator(ignoreCase));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsObject(CharSequence name, Object value, boolean ignoreCase) {
|
||||
return containsObject(name, value, comparator(ignoreCase));
|
||||
return contains(name, value,
|
||||
ignoreCase ? CHARSEQUENCE_CASE_INSENSITIVE_ORDER : CHARSEQUENCE_CASE_SENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -398,10 +251,6 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence,
|
||||
return this;
|
||||
}
|
||||
|
||||
private static Comparator<CharSequence> comparator(boolean ignoreCase) {
|
||||
return ignoreCase ? CHARSEQUENCE_CASE_INSENSITIVE_ORDER : CHARSEQUENCE_CASE_SENSITIVE_ORDER;
|
||||
}
|
||||
|
||||
/*
|
||||
* This interface enables different implementations for adding/setting header values.
|
||||
* Concrete implementations can control how values are added, for example to add all
|
||||
@ -413,6 +262,7 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence,
|
||||
TextHeaders add(CharSequence name, CharSequence... values);
|
||||
TextHeaders add(CharSequence name, Iterable<? extends CharSequence> values);
|
||||
|
||||
TextHeaders addObject(CharSequence name, Object value);
|
||||
TextHeaders addObject(CharSequence name, Iterable<?> values);
|
||||
TextHeaders addObject(CharSequence name, Object... values);
|
||||
|
||||
@ -446,6 +296,12 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence,
|
||||
return DefaultTextHeaders.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextHeaders addObject(CharSequence name, Object value) {
|
||||
DefaultTextHeaders.super.addObject(name, value);
|
||||
return DefaultTextHeaders.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextHeaders addObject(CharSequence name, Iterable<?> values) {
|
||||
DefaultTextHeaders.super.addObject(name, values);
|
||||
@ -534,6 +390,11 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence,
|
||||
return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextHeaders addObject(CharSequence name, Object value) {
|
||||
return addEscapedValue(name, objectEscaper().escape(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextHeaders addObject(CharSequence name, Iterable<?> values) {
|
||||
return addEscapedValue(name, commaSeparate(objectEscaper(), values));
|
||||
@ -579,7 +440,8 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence,
|
||||
}
|
||||
|
||||
private <T> CharSequence commaSeparate(CsvValueEscaper<T> escaper, T... values) {
|
||||
StringBuilder sb = new StringBuilder(values.length * DEFAULT_VALUE_SIZE);
|
||||
final int lengthEstimate = 10;
|
||||
StringBuilder sb = new StringBuilder(values.length * lengthEstimate);
|
||||
if (values.length > 0) {
|
||||
int end = values.length - 1;
|
||||
for (int i = 0; i < end; i++) {
|
||||
@ -625,4 +487,130 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence,
|
||||
*/
|
||||
CharSequence escape(T value);
|
||||
}
|
||||
|
||||
private static final class CharSequenceToStringConverter implements TypeConverter<CharSequence, String> {
|
||||
|
||||
private static final CharSequenceToStringConverter INSTANCE = new CharSequenceToStringConverter();
|
||||
|
||||
@Override
|
||||
public String toConvertedType(CharSequence value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toUnconvertedType(String value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class CharSequenceConverter implements ValueConverter<CharSequence> {
|
||||
|
||||
public static final CharSequenceConverter INSTANCE = new CharSequenceConverter();
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
if (value instanceof CharSequence) {
|
||||
return (CharSequence) value;
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertInt(int value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertLong(long value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertDouble(double value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertChar(char value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertBoolean(boolean value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertFloat(float value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean convertToBoolean(CharSequence value) {
|
||||
return Boolean.parseBoolean(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertByte(byte value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte convertToByte(CharSequence value) {
|
||||
return Byte.valueOf(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public char convertToChar(CharSequence value) {
|
||||
if (value.length() == 0) {
|
||||
throw new IllegalArgumentException("'value' is empty.");
|
||||
}
|
||||
return value.charAt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertShort(short value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short convertToShort(CharSequence value) {
|
||||
return Short.valueOf(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertToInt(CharSequence value) {
|
||||
return Integer.parseInt(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToLong(CharSequence value) {
|
||||
return Long.parseLong(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertTimeMillis(long value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToTimeMillis(CharSequence value) {
|
||||
try {
|
||||
return HeaderDateFormat.get().parse(value.toString());
|
||||
} catch (ParseException e) {
|
||||
PlatformDependent.throwException(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float convertToFloat(CharSequence value) {
|
||||
return Float.valueOf(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double convertToDouble(CharSequence value) {
|
||||
return Double.valueOf(value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,14 +54,10 @@ public class EmptyConvertibleHeaders<UnconvertedType, ConvertedType> extends
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Entry<ConvertedType, ConvertedType>> entriesConverted() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<ConvertedType, ConvertedType>> iteratorConverted() {
|
||||
return entriesConverted().iterator();
|
||||
List<Entry<ConvertedType, ConvertedType>> empty = Collections.emptyList();
|
||||
return empty.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -88,7 +88,7 @@ public class EmptyHeaders<T> implements Headers<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getInt(T name, short defaultValue) {
|
||||
public short getShort(T name, short defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@ -232,11 +232,6 @@ public class EmptyHeaders<T> implements Headers<T> {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Entry<T, T>> entries() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(T name) {
|
||||
return false;
|
||||
@ -298,24 +293,7 @@ public class EmptyHeaders<T> implements Headers<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(T name, T value, Comparator<? super T> comparator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(T name, T value,
|
||||
Comparator<? super T> keyComparator, Comparator<? super T> valueComparator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsObject(T name, Object value, Comparator<? super T> comparator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsObject(T name, Object value, Comparator<? super T> keyComparator,
|
||||
Comparator<? super T> valueComparator) {
|
||||
public boolean contains(T name, T value, Comparator<? super T> valueComparator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -334,11 +312,6 @@ public class EmptyHeaders<T> implements Headers<T> {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> namesList() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Headers<T> add(T name, T value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
@ -415,7 +388,7 @@ public class EmptyHeaders<T> implements Headers<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Headers<T> add(Headers<T> headers) {
|
||||
public Headers<T> add(Headers<? extends T> headers) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@ -495,12 +468,12 @@ public class EmptyHeaders<T> implements Headers<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Headers<T> set(Headers<T> headers) {
|
||||
public Headers<T> set(Headers<? extends T> headers) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Headers<T> setAll(Headers<T> headers) {
|
||||
public Headers<T> setAll(Headers<? extends T> headers) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@ -516,17 +489,8 @@ public class EmptyHeaders<T> implements Headers<T> {
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<T, T>> iterator() {
|
||||
return entries().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<T, T> forEachEntry(Headers.EntryVisitor<T> visitor) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T forEachName(Headers.NameVisitor<T> visitor) throws Exception {
|
||||
return null;
|
||||
List<Entry<T, T>> empty = Collections.emptyList();
|
||||
return empty.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,11 +25,6 @@ public class EmptyTextHeaders extends EmptyConvertibleHeaders<CharSequence, Stri
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsObject(CharSequence name, Object value, boolean ignoreCase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextHeaders add(CharSequence name, CharSequence value) {
|
||||
super.add(name, value);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -25,17 +25,6 @@ package io.netty.handler.codec;
|
||||
* .
|
||||
*/
|
||||
public interface TextHeaders extends ConvertibleHeaders<CharSequence, String> {
|
||||
/**
|
||||
* A visitor that helps reduce GC pressure while iterating over a collection of {@link Headers}.
|
||||
*/
|
||||
interface EntryVisitor extends Headers.EntryVisitor<CharSequence> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A visitor that helps reduce GC pressure while iterating over a collection of {@link Headers}.
|
||||
*/
|
||||
interface NameVisitor extends Headers.NameVisitor<CharSequence> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a header with the name and value exists.
|
||||
@ -45,14 +34,6 @@ public interface TextHeaders extends ConvertibleHeaders<CharSequence, String> {
|
||||
*/
|
||||
boolean contains(CharSequence name, CharSequence value, boolean ignoreCase);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a header with the name and value exists.
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
* @return {@code true} if it contains it {@code false} otherwise
|
||||
*/
|
||||
boolean containsObject(CharSequence name, Object value, boolean ignoreCase);
|
||||
|
||||
@Override
|
||||
TextHeaders add(CharSequence name, CharSequence value);
|
||||
|
||||
|
@ -14,20 +14,25 @@
|
||||
*/
|
||||
package io.netty.handler.codec;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import io.netty.util.AsciiString;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import io.netty.util.ByteString;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import io.netty.util.CharsetUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
@ -36,107 +41,186 @@ import org.junit.Test;
|
||||
public class DefaultBinaryHeadersTest {
|
||||
|
||||
@Test
|
||||
public void binaryHeadersWithSameValuesShouldBeEquivalent() {
|
||||
byte[] key1 = randomBytes();
|
||||
byte[] value1 = randomBytes();
|
||||
byte[] key2 = randomBytes();
|
||||
byte[] value2 = randomBytes();
|
||||
public void addShouldIncreaseAndRemoveShouldDecreaseTheSize() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
assertEquals(0, headers.size());
|
||||
headers.add(bs("name1"), bs("value1"), bs("value2"));
|
||||
assertEquals(2, headers.size());
|
||||
headers.add(bs("name2"), bs("value3"), bs("value4"));
|
||||
assertEquals(4, headers.size());
|
||||
headers.add(bs("name3"), bs("value5"));
|
||||
assertEquals(5, headers.size());
|
||||
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.set(as(key1), as(value1));
|
||||
h1.set(as(key2), as(value2));
|
||||
|
||||
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
|
||||
h2.set(as(key1), as(value1));
|
||||
h2.set(as(key2), as(value2));
|
||||
|
||||
assertTrue(h1.equals(h2));
|
||||
assertTrue(h2.equals(h1));
|
||||
assertTrue(h2.equals(h2));
|
||||
assertTrue(h1.equals(h1));
|
||||
headers.remove(bs("name3"));
|
||||
assertEquals(4, headers.size());
|
||||
headers.remove(bs("name1"));
|
||||
assertEquals(2, headers.size());
|
||||
headers.remove(bs("name2"));
|
||||
assertEquals(0, headers.size());
|
||||
assertTrue(headers.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void binaryHeadersWithSameDuplicateValuesShouldBeEquivalent() {
|
||||
byte[] k1 = randomBytes();
|
||||
byte[] k2 = randomBytes();
|
||||
byte[] v1 = randomBytes();
|
||||
byte[] v2 = randomBytes();
|
||||
byte[] v3 = randomBytes();
|
||||
byte[] v4 = randomBytes();
|
||||
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.set(as(k1), as(v1));
|
||||
h1.set(as(k2), as(v2));
|
||||
h1.add(as(k2), as(v3));
|
||||
h1.add(as(k1), as(v4));
|
||||
|
||||
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
|
||||
h2.set(as(k1), as(v1));
|
||||
h2.set(as(k2), as(v2));
|
||||
h2.add(as(k1), as(v4));
|
||||
h2.add(as(k2), as(v3));
|
||||
|
||||
assertTrue(h1.equals(h2));
|
||||
assertTrue(h2.equals(h1));
|
||||
assertTrue(h2.equals(h2));
|
||||
assertTrue(h1.equals(h1));
|
||||
public void afterClearHeadersShouldBeEmpty() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.add(bs("name1"), bs("value1"));
|
||||
headers.add(bs("name2"), bs("value2"));
|
||||
assertEquals(2, headers.size());
|
||||
headers.clear();
|
||||
assertEquals(0, headers.size());
|
||||
assertTrue(headers.isEmpty());
|
||||
assertFalse(headers.contains(bs("name1")));
|
||||
assertFalse(headers.contains(bs("name2")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void binaryHeadersWithDifferentValuesShouldNotBeEquivalent() {
|
||||
byte[] k1 = randomBytes();
|
||||
byte[] k2 = randomBytes();
|
||||
byte[] v1 = randomBytes();
|
||||
byte[] v2 = randomBytes();
|
||||
byte[] v3 = randomBytes();
|
||||
byte[] v4 = randomBytes();
|
||||
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.set(as(k1), as(v1));
|
||||
h1.set(as(k2), as(v2));
|
||||
h1.add(as(k2), as(v3));
|
||||
h1.add(as(k1), as(v4));
|
||||
|
||||
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
|
||||
h2.set(as(k1), as(v1));
|
||||
h2.set(as(k2), as(v2));
|
||||
h2.add(as(k1), as(v4));
|
||||
|
||||
assertFalse(h1.equals(h2));
|
||||
assertFalse(h2.equals(h1));
|
||||
assertTrue(h2.equals(h2));
|
||||
assertTrue(h1.equals(h1));
|
||||
public void removingANameForASecondTimeShouldReturnFalse() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.add(bs("name1"), bs("value1"));
|
||||
headers.add(bs("name2"), bs("value2"));
|
||||
assertTrue(headers.remove(bs("name2")));
|
||||
assertFalse(headers.remove(bs("name2")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void binarySetAllShouldMergeHeaders() {
|
||||
byte[] k1 = randomBytes();
|
||||
byte[] k2 = randomBytes();
|
||||
byte[] v1 = randomBytes();
|
||||
byte[] v2 = randomBytes();
|
||||
byte[] v3 = randomBytes();
|
||||
byte[] v4 = randomBytes();
|
||||
public void multipleValuesPerNameShouldBeAllowed() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.add(bs("name"), bs("value1"));
|
||||
headers.add(bs("name"), bs("value2"));
|
||||
headers.add(bs("name"), bs("value3"));
|
||||
assertEquals(3, headers.size());
|
||||
|
||||
List<ByteString> values = headers.getAll(bs("name"));
|
||||
assertEquals(3, values.size());
|
||||
assertTrue(values.containsAll(asList(bs("value1"), bs("value2"), bs("value3"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContains() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.addBoolean(bs("boolean"), true);
|
||||
assertTrue(headers.containsBoolean(bs("boolean"), true));
|
||||
assertFalse(headers.containsBoolean(bs("boolean"), false));
|
||||
|
||||
headers.addLong(bs("long"), Long.MAX_VALUE);
|
||||
assertTrue(headers.containsLong(bs("long"), Long.MAX_VALUE));
|
||||
assertFalse(headers.containsLong(bs("long"), Long.MIN_VALUE));
|
||||
|
||||
headers.addInt(bs("int"), Integer.MIN_VALUE);
|
||||
assertTrue(headers.containsInt(bs("int"), Integer.MIN_VALUE));
|
||||
assertFalse(headers.containsInt(bs("int"), Integer.MAX_VALUE));
|
||||
|
||||
headers.addShort(bs("short"), Short.MAX_VALUE);
|
||||
assertTrue(headers.containsShort(bs("short"), Short.MAX_VALUE));
|
||||
assertFalse(headers.containsShort(bs("short"), Short.MIN_VALUE));
|
||||
|
||||
headers.addChar(bs("char"), Character.MAX_VALUE);
|
||||
assertTrue(headers.containsChar(bs("char"), Character.MAX_VALUE));
|
||||
assertFalse(headers.containsChar(bs("char"), Character.MIN_VALUE));
|
||||
|
||||
headers.addByte(bs("byte"), Byte.MAX_VALUE);
|
||||
assertTrue(headers.containsByte(bs("byte"), Byte.MAX_VALUE));
|
||||
assertFalse(headers.containsLong(bs("byte"), Byte.MIN_VALUE));
|
||||
|
||||
headers.addDouble(bs("double"), Double.MAX_VALUE);
|
||||
assertTrue(headers.containsDouble(bs("double"), Double.MAX_VALUE));
|
||||
assertFalse(headers.containsDouble(bs("double"), Double.MIN_VALUE));
|
||||
|
||||
headers.addFloat(bs("float"), Float.MAX_VALUE);
|
||||
assertTrue(headers.containsFloat(bs("float"), Float.MAX_VALUE));
|
||||
assertFalse(headers.containsFloat(bs("float"), Float.MIN_VALUE));
|
||||
|
||||
long millis = System.currentTimeMillis();
|
||||
headers.addTimeMillis(bs("millis"), millis);
|
||||
assertTrue(headers.containsTimeMillis(bs("millis"), millis));
|
||||
// This test doesn't work on midnight, January 1, 1970 UTC
|
||||
assertFalse(headers.containsTimeMillis(bs("millis"), 0));
|
||||
|
||||
headers.addObject(bs("object"), "Hello World");
|
||||
assertTrue(headers.containsObject(bs("object"), "Hello World"));
|
||||
assertFalse(headers.containsObject(bs("object"), ""));
|
||||
|
||||
headers.add(bs("name"), bs("value"));
|
||||
assertTrue(headers.contains(bs("name"), bs("value")));
|
||||
assertFalse(headers.contains(bs("name"), bs("value1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canMixConvertedAndNormalValues() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.add(bs("name"), bs("value"));
|
||||
headers.addInt(bs("name"), 100);
|
||||
headers.addBoolean(bs("name"), false);
|
||||
|
||||
assertEquals(3, headers.size());
|
||||
assertTrue(headers.contains(bs("name")));
|
||||
assertTrue(headers.contains(bs("name"), bs("value")));
|
||||
assertTrue(headers.containsInt(bs("name"), 100));
|
||||
assertTrue(headers.containsBoolean(bs("name"), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAndRemove() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.add(bs("name1"), bs("value1"));
|
||||
headers.add(bs("name2"), bs("value2"), bs("value3"));
|
||||
headers.add(bs("name3"), bs("value4"), bs("value5"), bs("value6"));
|
||||
|
||||
assertEquals(bs("value1"), headers.getAndRemove(bs("name1"), bs("defaultvalue")));
|
||||
assertEquals(bs("value2"), headers.getAndRemove(bs("name2")));
|
||||
assertNull(headers.getAndRemove(bs("name2")));
|
||||
assertEquals(asList(bs("value4"), bs("value5"), bs("value6")), headers.getAllAndRemove(bs("name3")));
|
||||
assertEquals(0, headers.size());
|
||||
assertNull(headers.getAndRemove(bs("noname")));
|
||||
assertEquals(bs("defaultvalue"), headers.getAndRemove(bs("noname"), bs("defaultvalue")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenNameContainsMultipleValuesGetShouldReturnTheFirst() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.add(bs("name1"), bs("value1"), bs("value2"));
|
||||
assertEquals(bs("value1"), headers.get(bs("name1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWithDefaultValueWorks() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.add(bs("name1"), bs("value1"));
|
||||
|
||||
assertEquals(bs("value1"), headers.get(bs("name1"), bs("defaultvalue")));
|
||||
assertEquals(bs("defaultvalue"), headers.get(bs("noname"), bs("defaultvalue")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setShouldOverWritePreviousValue() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.set(bs("name"), bs("value1"));
|
||||
headers.set(bs("name"), bs("value2"));
|
||||
assertEquals(1, headers.size());
|
||||
assertEquals(1, headers.getAll(bs("name")).size());
|
||||
assertEquals(bs("value2"), headers.getAll(bs("name")).get(0));
|
||||
assertEquals(bs("value2"), headers.get(bs("name")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAllShouldOverwriteSomeAndLeaveOthersUntouched() {
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.set(as(k1), as(v1));
|
||||
h1.set(as(k2), as(v2));
|
||||
h1.add(as(k2), as(v3));
|
||||
h1.add(as(k1), as(v4));
|
||||
|
||||
h1.add(bs("name1"), bs("value1"));
|
||||
h1.add(bs("name2"), bs("value2"));
|
||||
h1.add(bs("name2"), bs("value3"));
|
||||
h1.add(bs("name3"), bs("value4"));
|
||||
|
||||
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
|
||||
h2.set(as(k1), as(v1));
|
||||
h2.set(as(k2), as(v2));
|
||||
h2.add(as(k1), as(v4));
|
||||
h2.add(bs("name1"), bs("value5"));
|
||||
h2.add(bs("name2"), bs("value6"));
|
||||
h2.add(bs("name1"), bs("value7"));
|
||||
|
||||
DefaultBinaryHeaders expected = new DefaultBinaryHeaders();
|
||||
expected.set(as(k1), as(v1));
|
||||
expected.set(as(k2), as(v2));
|
||||
expected.add(as(k2), as(v3));
|
||||
expected.add(as(k1), as(v4));
|
||||
expected.set(as(k1), as(v1));
|
||||
expected.set(as(k2), as(v2));
|
||||
expected.set(as(k1), as(v4));
|
||||
expected.add(bs("name1"), bs("value5"));
|
||||
expected.add(bs("name2"), bs("value6"));
|
||||
expected.add(bs("name1"), bs("value7"));
|
||||
expected.add(bs("name3"), bs("value4"));
|
||||
|
||||
h1.setAll(h2);
|
||||
|
||||
@ -144,118 +228,64 @@ public class DefaultBinaryHeadersTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void binarySetShouldReplacePreviousValues() {
|
||||
byte[] k1 = randomBytes();
|
||||
byte[] v1 = randomBytes();
|
||||
byte[] v2 = randomBytes();
|
||||
byte[] v3 = randomBytes();
|
||||
public void headersWithSameNamesAndValuesShouldBeEquivalent() {
|
||||
DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
|
||||
headers1.add(bs("name1"), bs("value1"));
|
||||
headers1.add(bs("name2"), bs("value2"));
|
||||
headers1.add(bs("name2"), bs("value3"));
|
||||
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.add(as(k1), as(v1));
|
||||
h1.add(as(k1), as(v2));
|
||||
assertEquals(2, h1.size());
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
headers2.add(bs("name1"), bs("value1"));
|
||||
headers2.add(bs("name2"), bs("value2"));
|
||||
headers2.add(bs("name2"), bs("value3"));
|
||||
|
||||
h1.set(as(k1), as(v3));
|
||||
assertEquals(1, h1.size());
|
||||
List<ByteString> list = h1.getAll(as(k1));
|
||||
assertEquals(1, list.size());
|
||||
assertEquals(as(v3), list.get(0));
|
||||
assertEquals(headers1, headers2);
|
||||
assertEquals(headers2, headers1);
|
||||
assertEquals(headers1, headers1);
|
||||
assertEquals(headers2, headers2);
|
||||
assertEquals(headers1.hashCode(), headers2.hashCode());
|
||||
assertEquals(headers1.hashCode(), headers1.hashCode());
|
||||
assertEquals(headers2.hashCode(), headers2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWithSameValuesShouldBeEquivalent() {
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.set(as("foo"), as("goo"));
|
||||
h1.set(as("foo2"), as("goo2"));
|
||||
public void emptyHeadersShouldBeEqual() {
|
||||
DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
assertNotSame(headers1, headers2);
|
||||
assertEquals(headers1, headers2);
|
||||
assertEquals(headers1.hashCode(), headers2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWithSameNamesButDifferentValuesShouldNotBeEquivalent() {
|
||||
DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
|
||||
headers1.add(bs("name1"), bs("value1"));
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
headers1.add(bs("name1"), bs("value2"));
|
||||
assertNotEquals(headers1, headers2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subsetOfHeadersShouldNotBeEquivalent() {
|
||||
DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
|
||||
headers1.add(bs("name1"), bs("value1"));
|
||||
headers1.add(bs("name2"), bs("value2"));
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
headers1.add(bs("name1"), bs("value1"));
|
||||
assertNotEquals(headers1, headers2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWithDifferentNamesAndValuesShouldNotBeEquivalent() {
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.set(bs("name1"), bs("value1"));
|
||||
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
|
||||
h2.set(as("foo"), as("goo"));
|
||||
h2.set(as("foo2"), as("goo2"));
|
||||
|
||||
assertTrue(h1.equals(h2));
|
||||
assertTrue(h2.equals(h1));
|
||||
assertTrue(h2.equals(h2));
|
||||
assertTrue(h1.equals(h1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWithSameDuplicateValuesShouldBeEquivalent() {
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.set(as("foo"), as("goo"));
|
||||
h1.set(as("foo2"), as("goo2"));
|
||||
h1.add(as("foo2"), as("goo3"));
|
||||
h1.add(as("foo"), as("goo4"));
|
||||
|
||||
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
|
||||
h2.set(as("foo"), as("goo"));
|
||||
h2.set(as("foo2"), as("goo2"));
|
||||
h2.add(as("foo"), as("goo4"));
|
||||
h2.add(as("foo2"), as("goo3"));
|
||||
|
||||
assertTrue(h1.equals(h2));
|
||||
assertTrue(h2.equals(h1));
|
||||
assertTrue(h2.equals(h2));
|
||||
assertTrue(h1.equals(h1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWithDifferentValuesShouldNotBeEquivalent() {
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.set(as("foo"), as("goo"));
|
||||
h1.set(as("foo2"), as("goo2"));
|
||||
h1.add(as("foo2"), as("goo3"));
|
||||
h1.add(as("foo"), as("goo4"));
|
||||
|
||||
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
|
||||
h2.set(as("foo"), as("goo"));
|
||||
h2.set(as("foo2"), as("goo2"));
|
||||
h2.add(as("foo"), as("goo4"));
|
||||
|
||||
assertFalse(h1.equals(h2));
|
||||
assertFalse(h2.equals(h1));
|
||||
assertTrue(h2.equals(h2));
|
||||
assertTrue(h1.equals(h1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAllShouldMergeHeaders() {
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.set(as("foo"), as("goo"));
|
||||
h1.set(as("foo2"), as("goo2"));
|
||||
h1.add(as("foo2"), as("goo3"));
|
||||
h1.add(as("foo"), as("goo4"));
|
||||
|
||||
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
|
||||
h2.set(as("foo"), as("goo"));
|
||||
h2.set(as("foo2"), as("goo2"));
|
||||
h2.add(as("foo"), as("goo4"));
|
||||
|
||||
DefaultBinaryHeaders expected = new DefaultBinaryHeaders();
|
||||
expected.set(as("foo"), as("goo"));
|
||||
expected.set(as("foo2"), as("goo2"));
|
||||
expected.add(as("foo2"), as("goo3"));
|
||||
expected.add(as("foo"), as("goo4"));
|
||||
expected.set(as("foo"), as("goo"));
|
||||
expected.set(as("foo2"), as("goo2"));
|
||||
expected.set(as("foo"), as("goo4"));
|
||||
|
||||
h1.setAll(h2);
|
||||
|
||||
assertEquals(expected, h1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setShouldReplacePreviousValues() {
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.add(as("foo"), as("goo"));
|
||||
h1.add(as("foo"), as("goo2"));
|
||||
assertEquals(2, h1.size());
|
||||
|
||||
h1.set(as("foo"), as("goo3"));
|
||||
assertEquals(1, h1.size());
|
||||
List<ByteString> list = h1.getAll(as("foo"));
|
||||
assertEquals(1, list.size());
|
||||
assertEquals(as("goo3"), list.get(0));
|
||||
h2.set(bs("name2"), bs("value2"));
|
||||
assertNotEquals(h1, h2);
|
||||
assertNotEquals(h2, h1);
|
||||
assertEquals(h1, h1);
|
||||
assertEquals(h2, h2);
|
||||
}
|
||||
|
||||
@Test(expected = NoSuchElementException.class)
|
||||
@ -266,53 +296,138 @@ public class DefaultBinaryHeadersTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iterateHeadersShouldReturnAllValues() {
|
||||
Set<String> headers = new HashSet<String>();
|
||||
headers.add("a:1");
|
||||
headers.add("a:2");
|
||||
headers.add("a:3");
|
||||
headers.add("b:1");
|
||||
headers.add("b:2");
|
||||
headers.add("c:1");
|
||||
public void iteratorShouldReturnAllNameValuePairs() {
|
||||
DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
|
||||
headers1.add(bs("name1"), bs("value1"), bs("value2"));
|
||||
headers1.add(bs("name2"), bs("value3"));
|
||||
headers1.add(bs("name3"), bs("value4"), bs("value5"), bs("value6"));
|
||||
headers1.add(bs("name1"), bs("value7"), bs("value8"));
|
||||
assertEquals(8, headers1.size());
|
||||
|
||||
// Build the headers from the input set.
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
for (String header : headers) {
|
||||
String[] parts = header.split(":");
|
||||
h1.add(as(parts[0]), as(parts[1]));
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
for (Entry<ByteString, ByteString> entry : headers1) {
|
||||
Object v = entry.getValue();
|
||||
headers2.add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
// Now iterate through the headers, removing them from the original set.
|
||||
for (Map.Entry<ByteString, ByteString> entry : h1) {
|
||||
assertTrue(headers.remove(entry.getKey().toString() + ':' + entry.getValue().toString()));
|
||||
}
|
||||
|
||||
// Make sure we removed them all.
|
||||
assertTrue(headers.isEmpty());
|
||||
assertEquals(headers1, headers2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAndRemoveShouldReturnFirstEntry() {
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
h1.add(as("foo"), as("goo"));
|
||||
h1.add(as("foo"), as("goo2"));
|
||||
assertEquals(as("goo"), h1.getAndRemove(as("foo")));
|
||||
assertEquals(0, h1.size());
|
||||
List<ByteString> values = h1.getAll(as("foo"));
|
||||
assertEquals(0, values.size());
|
||||
public void iteratorSetValueShouldChangeHeaderValue() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.add(bs("name1"), bs("value1"), bs("value2"), bs("value3"));
|
||||
headers.add(bs("name2"), bs("value4"));
|
||||
assertEquals(4, headers.size());
|
||||
|
||||
Iterator<Entry<ByteString, ByteString>> iter = headers.iterator();
|
||||
while (iter.hasNext()) {
|
||||
Entry<ByteString, ByteString> header = iter.next();
|
||||
if (bs("name1").equals(header.getKey()) && bs("value2").equals(header.getValue())) {
|
||||
header.setValue(bs("updatedvalue2"));
|
||||
assertEquals(bs("updatedvalue2"), header.getValue());
|
||||
}
|
||||
if (bs("name1").equals(header.getKey()) && bs("value3").equals(header.getValue())) {
|
||||
header.setValue(bs("updatedvalue3"));
|
||||
assertEquals(bs("updatedvalue3"), header.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(4, headers.size());
|
||||
assertTrue(headers.contains(bs("name1"), bs("updatedvalue2")));
|
||||
assertFalse(headers.contains(bs("name1"), bs("value2")));
|
||||
assertTrue(headers.contains(bs("name1"), bs("updatedvalue3")));
|
||||
assertFalse(headers.contains(bs("name1"), bs("value3")));
|
||||
}
|
||||
|
||||
private static byte[] randomBytes() {
|
||||
byte[] data = new byte[100];
|
||||
new Random().nextBytes(data);
|
||||
return data;
|
||||
@Test
|
||||
public void getAllReturnsEmptyListForUnknownName() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
assertEquals(0, headers.getAll(bs("noname")).size());
|
||||
}
|
||||
|
||||
private AsciiString as(byte[] bytes) {
|
||||
return new AsciiString(bytes);
|
||||
@Test
|
||||
public void canNotModifyTheListReturnedByGetAll() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
headers.add(bs("name1"), bs("value1"));
|
||||
headers.add(bs("name2"), bs("value2"), bs("value3"));
|
||||
|
||||
// Test for single value names.
|
||||
try {
|
||||
headers.getAll(bs("name1")).add(bs("value"));
|
||||
fail();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// for checkstyle
|
||||
}
|
||||
try {
|
||||
headers.getAll(bs("name1")).remove(0);
|
||||
fail();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// for checkstyle
|
||||
}
|
||||
|
||||
// Test for multi value names.
|
||||
try {
|
||||
headers.getAll(bs("name2")).add(bs("value"));
|
||||
fail();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// for checkstyle
|
||||
}
|
||||
try {
|
||||
headers.getAll(bs("name2")).remove(0);
|
||||
fail();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// for checkstyle
|
||||
}
|
||||
|
||||
// Test for names that don't exist.
|
||||
try {
|
||||
headers.getAll(bs("name3")).add(bs("value"));
|
||||
fail();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// for checkstyle
|
||||
}
|
||||
try {
|
||||
headers.getAll(bs("name3")).remove(0);
|
||||
fail();
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// for checkstyle
|
||||
}
|
||||
}
|
||||
|
||||
private AsciiString as(String value) {
|
||||
return new AsciiString(value);
|
||||
@Test
|
||||
public void setHeadersShouldClearAndOverwrite() {
|
||||
DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
|
||||
headers1.add(bs("name"), bs("value"));
|
||||
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
headers2.add(bs("name"), bs("newvalue"));
|
||||
headers2.add(bs("name1"), bs("value1"));
|
||||
|
||||
headers1.set(headers2);
|
||||
assertEquals(headers1, headers2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAllHeadersShouldOnlyOverwriteHeaders() {
|
||||
DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
|
||||
headers1.add(bs("name"), bs("value"));
|
||||
headers1.add(bs("name1"), bs("value1"));
|
||||
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
headers2.add(bs("name"), bs("newvalue"));
|
||||
headers2.add(bs("name2"), bs("value2"));
|
||||
|
||||
DefaultBinaryHeaders expected = new DefaultBinaryHeaders();
|
||||
expected.add(bs("name"), bs("newvalue"));
|
||||
expected.add(bs("name1"), bs("value1"));
|
||||
expected.add(bs("name2"), bs("value2"));
|
||||
|
||||
headers1.setAll(headers2);
|
||||
assertEquals(headers1, expected);
|
||||
}
|
||||
|
||||
private ByteString bs(String value) {
|
||||
return new ByteString(value, CharsetUtil.US_ASCII);
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,6 @@
|
||||
package io.netty.handler.codec;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static io.netty.util.internal.StringUtil.COMMA;
|
||||
import static io.netty.util.internal.StringUtil.DOUBLE_QUOTE;
|
||||
import java.util.Arrays;
|
||||
@ -31,88 +29,6 @@ public class DefaultTextHeadersTest {
|
||||
|
||||
private static final String HEADER_NAME = "testHeader";
|
||||
|
||||
@Test
|
||||
public void testEqualsMultipleHeaders() {
|
||||
DefaultTextHeaders h1 = new DefaultTextHeaders();
|
||||
h1.set("Foo", "goo");
|
||||
h1.set("foo2", "goo2");
|
||||
|
||||
DefaultTextHeaders h2 = new DefaultTextHeaders();
|
||||
h2.set("FoO", "goo");
|
||||
h2.set("fOO2", "goo2");
|
||||
|
||||
assertTrue(h1.equals(h2));
|
||||
assertTrue(h2.equals(h1));
|
||||
assertTrue(h2.equals(h2));
|
||||
assertTrue(h1.equals(h1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsDuplicateMultipleHeaders() {
|
||||
DefaultTextHeaders h1 = new DefaultTextHeaders();
|
||||
h1.set("FOO", "goo");
|
||||
h1.set("Foo2", "goo2");
|
||||
h1.add("fOo2", "goo3");
|
||||
h1.add("foo", "goo4");
|
||||
|
||||
DefaultTextHeaders h2 = new DefaultTextHeaders();
|
||||
h2.set("foo", "goo");
|
||||
h2.set("foo2", "goo2");
|
||||
h2.add("foo", "goo4");
|
||||
h2.add("foO2", "goo3");
|
||||
|
||||
assertTrue(h1.equals(h2));
|
||||
assertTrue(h2.equals(h1));
|
||||
assertTrue(h2.equals(h2));
|
||||
assertTrue(h1.equals(h1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEqualsDuplicateMultipleHeaders() {
|
||||
DefaultTextHeaders h1 = new DefaultTextHeaders();
|
||||
h1.set("FOO", "goo");
|
||||
h1.set("foo2", "goo2");
|
||||
h1.add("foo2", "goo3");
|
||||
h1.add("foo", "goo4");
|
||||
|
||||
DefaultTextHeaders h2 = new DefaultTextHeaders();
|
||||
h2.set("foo", "goo");
|
||||
h2.set("foo2", "goo2");
|
||||
h2.add("foo", "goo4");
|
||||
|
||||
assertFalse(h1.equals(h2));
|
||||
assertFalse(h2.equals(h1));
|
||||
assertTrue(h2.equals(h2));
|
||||
assertTrue(h1.equals(h1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAll() {
|
||||
DefaultTextHeaders h1 = new DefaultTextHeaders();
|
||||
h1.set("FOO", "goo");
|
||||
h1.set("foo2", "goo2");
|
||||
h1.add("foo2", "goo3");
|
||||
h1.add("foo", "goo4");
|
||||
|
||||
DefaultTextHeaders h2 = new DefaultTextHeaders();
|
||||
h2.set("foo", "goo");
|
||||
h2.set("foo2", "goo2");
|
||||
h2.add("foo", "goo4");
|
||||
|
||||
DefaultTextHeaders expected = new DefaultTextHeaders();
|
||||
expected.set("FOO", "goo");
|
||||
expected.set("foo2", "goo2");
|
||||
expected.add("foo2", "goo3");
|
||||
expected.add("foo", "goo4");
|
||||
expected.set("foo", "goo");
|
||||
expected.set("foo2", "goo2");
|
||||
expected.set("foo", "goo4");
|
||||
|
||||
h1.setAll(h2);
|
||||
|
||||
assertEquals(expected, h1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequences() {
|
||||
final TextHeaders headers = newDefaultTextHeaders();
|
||||
@ -325,11 +241,11 @@ public class DefaultTextHeadersTest {
|
||||
}
|
||||
|
||||
private static TextHeaders newDefaultTextHeaders() {
|
||||
return new DefaultTextHeaders();
|
||||
return new DefaultTextHeaders(false);
|
||||
}
|
||||
|
||||
private static TextHeaders newCsvTextHeaders() {
|
||||
return new DefaultTextHeaders(true, true);
|
||||
return new DefaultTextHeaders(true);
|
||||
}
|
||||
|
||||
private static void addValues(final TextHeaders headers, HeaderValue... headerValues) {
|
||||
|
@ -54,64 +54,53 @@ public final class AsciiString extends ByteString implements CharSequence, Compa
|
||||
};
|
||||
|
||||
public static final Comparator<CharSequence> CHARSEQUENCE_CASE_INSENSITIVE_ORDER = new Comparator<CharSequence>() {
|
||||
|
||||
@Override
|
||||
public int compare(CharSequence o1, CharSequence o2) {
|
||||
if (o1 == o2) {
|
||||
return 0;
|
||||
int len1 = o1.length();
|
||||
int delta = len1 - o2.length();
|
||||
if (delta != 0) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
AsciiString a1 = o1 instanceof AsciiString ? (AsciiString) o1 : null;
|
||||
AsciiString a2 = o2 instanceof AsciiString ? (AsciiString) o2 : null;
|
||||
|
||||
int result;
|
||||
int length1 = o1.length();
|
||||
int length2 = o2.length();
|
||||
int minLength = Math.min(length1, length2);
|
||||
if (a1 != null && a2 != null) {
|
||||
final int a1Len = minLength + a1.arrayOffset();
|
||||
if (o1.getClass().equals(AsciiString.class) && o2.getClass().equals(AsciiString.class)) {
|
||||
AsciiString a1 = (AsciiString) o1;
|
||||
AsciiString a2 = (AsciiString) o2;
|
||||
final int a1Len = a1.length() + a1.arrayOffset();
|
||||
for (int i = a1.arrayOffset(), j = a2.arrayOffset(); i < a1Len; i++, j++) {
|
||||
byte v1 = a1.value[i];
|
||||
byte v2 = a2.value[j];
|
||||
if (v1 == v2) {
|
||||
continue;
|
||||
}
|
||||
int c1 = toLowerCase(v1);
|
||||
int c2 = toLowerCase(v2);
|
||||
result = c1 - c2;
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else if (a1 != null) {
|
||||
for (int i = a1.arrayOffset(), j = 0; j < minLength; i++, j++) {
|
||||
int c1 = toLowerCase(a1.value[i]);
|
||||
int c2 = toLowerCase(o2.charAt(j));
|
||||
result = c1 - c2;
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else if (a2 != null) {
|
||||
for (int i = 0, j = a2.arrayOffset(); i < minLength; i++, j++) {
|
||||
int c1 = toLowerCase(o1.charAt(i));
|
||||
int c2 = toLowerCase(a2.value[j]);
|
||||
result = c1 - c2;
|
||||
if (result != 0) {
|
||||
return result;
|
||||
byte c1 = a1.value[i];
|
||||
byte c2 = a2.value[j];
|
||||
if (c1 != c2) {
|
||||
if (c1 >= 'A' && c1 <= 'Z') {
|
||||
c1 += 32;
|
||||
}
|
||||
if (c2 >= 'A' && c2 <= 'Z') {
|
||||
c2 += 32;
|
||||
}
|
||||
delta = c1 - c2;
|
||||
if (delta != 0) {
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < minLength; i++) {
|
||||
int c1 = toLowerCase(o1.charAt(i));
|
||||
int c2 = toLowerCase(o2.charAt(i));
|
||||
result = c1 - c2;
|
||||
if (result != 0) {
|
||||
return result;
|
||||
for (int i = len1 - 1; i >= 0; i --) {
|
||||
char c1 = o1.charAt(i);
|
||||
char c2 = o2.charAt(i);
|
||||
if (c1 != c2) {
|
||||
if (c1 >= 'A' && c1 <= 'Z') {
|
||||
c1 += 32;
|
||||
}
|
||||
if (c2 >= 'A' && c2 <= 'Z') {
|
||||
c2 += 32;
|
||||
}
|
||||
delta = c1 - c2;
|
||||
if (delta != 0) {
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return length1 - length2;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -45,6 +45,8 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@ -186,12 +188,17 @@ public final class HttpUploadClient {
|
||||
);
|
||||
|
||||
// send request
|
||||
List<Entry<String, String>> entries = headers.entriesConverted();
|
||||
channel.writeAndFlush(request);
|
||||
|
||||
// Wait for the server to close the connection.
|
||||
channel.closeFuture().sync();
|
||||
|
||||
// convert headers to list
|
||||
List<Entry<String, String>> entries = new ArrayList<Entry<String, String>>(headers.size());
|
||||
Iterator<Entry<String, String>> iterConverted = headers.iteratorConverted();
|
||||
while (iterConverted.hasNext()) {
|
||||
entries.add(iterConverted.next());
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2015 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 java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class ExampleHeaders {
|
||||
|
||||
public enum HeaderExample {
|
||||
THREE,
|
||||
FIVE,
|
||||
SIX,
|
||||
EIGHT,
|
||||
ELEVEN,
|
||||
TWENTYTWO,
|
||||
THIRTY
|
||||
}
|
||||
|
||||
public static final Map<HeaderExample, Map<String, String>> EXAMPLES =
|
||||
new EnumMap<HeaderExample, Map<String, String>>(HeaderExample.class);
|
||||
|
||||
static {
|
||||
Map<String, String> header = new HashMap<String, String>();
|
||||
header.put(":method", "GET");
|
||||
header.put(":scheme", "https");
|
||||
header.put(":path", "/index.html");
|
||||
EXAMPLES.put(HeaderExample.THREE, header);
|
||||
|
||||
// Headers used by Norman's HTTP benchmarks with wrk
|
||||
header = new HashMap<String, String>();
|
||||
header.put("Method", "GET");
|
||||
header.put("Path", "/plaintext");
|
||||
header.put("Host", "localhost");
|
||||
header.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
header.put("Connection", "keep-alive");
|
||||
EXAMPLES.put(HeaderExample.FIVE, header);
|
||||
|
||||
header = new HashMap<String, String>();
|
||||
header.put(":authority", "127.0.0.1:33333");
|
||||
header.put(":method", "POST");
|
||||
header.put(":path", "/grpc.testing.TestService/UnaryCall");
|
||||
header.put(":scheme", "http");
|
||||
header.put("content-type", "application/grpc");
|
||||
header.put("te", "trailers");
|
||||
EXAMPLES.put(HeaderExample.SIX, header);
|
||||
|
||||
header = new HashMap<String, String>();
|
||||
header.put(":method", "POST");
|
||||
header.put(":scheme", "http");
|
||||
header.put(":path", "/google.pubsub.v2.PublisherService/CreateTopic");
|
||||
header.put(":authority", "pubsub.googleapis.com");
|
||||
header.put("grpc-timeout", "1S");
|
||||
header.put("content-type", "application/grpc+proto");
|
||||
header.put("grpc-encoding", "gzip");
|
||||
header.put("authorization", "Bearer y235.wef315yfh138vh31hv93hv8h3v");
|
||||
EXAMPLES.put(HeaderExample.EIGHT, header);
|
||||
|
||||
header = new HashMap<String, String>();
|
||||
header.put(":host", "twitter.com");
|
||||
header.put(":method", "GET");
|
||||
header.put(":path", "/");
|
||||
header.put(":scheme", "https");
|
||||
header.put(":version", "HTTP/1.1");
|
||||
header.put("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||||
header.put("accept-encoding", "gzip, deflate, sdch");
|
||||
header.put("accept-language", "en-US,en;q=0.8");
|
||||
header.put("cache-control", "max-age=0");
|
||||
header.put("cookie:", "noneofyourbusiness");
|
||||
header.put("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)");
|
||||
EXAMPLES.put(HeaderExample.ELEVEN, header);
|
||||
|
||||
header = new HashMap<String, String>();
|
||||
header.put("cache-control", "no-cache, no-store, must-revalidate, pre-check=0, post-check=0");
|
||||
header.put("content-encoding", "gzip");
|
||||
header.put("content-security-policy", "default-src https:; connect-src https:;");
|
||||
header.put("content-type", "text/html;charset=utf-8");
|
||||
header.put("date", "Wed, 22 Apr 2015 00:40:28 GMT");
|
||||
header.put("expires", "Tue, 31 Mar 1981 05:00:00 GMT");
|
||||
header.put("last-modified", "Wed, 22 Apr 2015 00:40:28 GMT");
|
||||
header.put("ms", "ms");
|
||||
header.put("pragma", "no-cache");
|
||||
header.put("server", "tsa_b");
|
||||
header.put("set-cookie", "noneofyourbusiness");
|
||||
header.put("status", "200 OK");
|
||||
header.put("strict-transport-security", "max-age=631138519");
|
||||
header.put("version", "HTTP/1.1");
|
||||
header.put("x-connection-hash", "e176fe40accc1e2c613a34bc1941aa98");
|
||||
header.put("x-content-type-options", "nosniff");
|
||||
header.put("x-frame-options", "SAMEORIGIN");
|
||||
header.put("x-response-time", "22");
|
||||
header.put("x-transaction", "a54142ede693444d9");
|
||||
header.put("x-twitter-response-tags", "BouncerCompliant");
|
||||
header.put("x-ua-compatible", "IE=edge,chrome=1");
|
||||
header.put("x-xss-protection", "1; mode=block");
|
||||
EXAMPLES.put(HeaderExample.TWENTYTWO, header);
|
||||
|
||||
header = new HashMap<String, String>();
|
||||
header.put("Cache-Control", "no-cache");
|
||||
header.put("Content-Encoding", "gzip");
|
||||
header.put("Content-Security-Policy", "default-src *; script-src assets-cdn.github.com ...");
|
||||
header.put("Content-Type", "text/html; charset=utf-8");
|
||||
header.put("Date", "Fri, 10 Apr 2015 02:15:38 GMT");
|
||||
header.put("Server", "GitHub.com");
|
||||
header.put("Set-Cookie", "_gh_sess=eyJzZXNza...; path=/; secure; HttpOnly");
|
||||
header.put("Status", "200 OK");
|
||||
header.put("Strict-Transport-Security", "max-age=31536000; includeSubdomains; preload");
|
||||
header.put("Transfer-Encoding", "chunked");
|
||||
header.put("Vary", "X-PJAX");
|
||||
header.put("X-Content-Type-Options", "nosniff");
|
||||
header.put("X-Frame-Options", "deny");
|
||||
header.put("X-GitHub-Request-Id", "1");
|
||||
header.put("X-GitHub-Session-Id", "1");
|
||||
header.put("X-GitHub-User", "buchgr");
|
||||
header.put("X-Request-Id", "28f245e02fc872dcf7f31149e52931dd");
|
||||
header.put("X-Runtime", "0.082529");
|
||||
header.put("X-Served-By", "b9c2a233f7f3119b174dbd8be2");
|
||||
header.put("X-UA-Compatible", "IE=Edge,chrome=1");
|
||||
header.put("X-XSS-Protection", "1; mode=block");
|
||||
header.put("Via", "http/1.1 ir50.fp.bf1.yahoo.com (ApacheTrafficServer)");
|
||||
header.put("Content-Language", "en");
|
||||
header.put("Connection", "keep-alive");
|
||||
header.put("Pragma", "no-cache");
|
||||
header.put("Expires", "Sat, 01 Jan 2000 00:00:00 GMT");
|
||||
header.put("X-Moose", "majestic");
|
||||
header.put("x-ua-compatible", "IE=edge");
|
||||
header.put("CF-Cache-Status", "HIT");
|
||||
header.put("CF-RAY", "6a47f4f911e3-");
|
||||
EXAMPLES.put(HeaderExample.THIRTY, header);
|
||||
}
|
||||
|
||||
private ExampleHeaders() {
|
||||
}
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2015 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.DefaultHttpHeaders;
|
||||
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
||||
import io.netty.handler.codec.http2.Http2Headers;
|
||||
import io.netty.microbench.util.AbstractMicrobenchmark;
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.ByteString;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Level;
|
||||
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 org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Threads(1)
|
||||
@State(Scope.Benchmark)
|
||||
@Warmup(iterations = 5)
|
||||
@Measurement(iterations = 10)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
public class HeadersBenchmark extends AbstractMicrobenchmark {
|
||||
|
||||
@Param
|
||||
ExampleHeaders.HeaderExample exampleHeader;
|
||||
|
||||
AsciiString[] httpNames;
|
||||
AsciiString[] httpValues;
|
||||
|
||||
ByteString[] http2Names;
|
||||
ByteString[] http2Values;
|
||||
|
||||
DefaultHttpHeaders httpHeaders;
|
||||
DefaultHttp2Headers http2Headers;
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup() {
|
||||
Map<String, String> headers = ExampleHeaders.EXAMPLES.get(exampleHeader);
|
||||
httpNames = new AsciiString[headers.size()];
|
||||
httpValues = new AsciiString[headers.size()];
|
||||
http2Names = new ByteString[headers.size()];
|
||||
http2Values = new ByteString[headers.size()];
|
||||
httpHeaders = new DefaultHttpHeaders(false);
|
||||
http2Headers = new DefaultHttp2Headers();
|
||||
int idx = 0;
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
String name = header.getKey();
|
||||
String value = header.getValue();
|
||||
httpNames[idx] = new AsciiString(name);
|
||||
httpValues[idx] = new AsciiString(value);
|
||||
http2Names[idx] = new ByteString(name, CharsetUtil.US_ASCII);
|
||||
http2Values[idx] = new ByteString(value, CharsetUtil.US_ASCII);
|
||||
idx++;
|
||||
httpHeaders.add(new AsciiString(name), new AsciiString(value));
|
||||
http2Headers.add(new ByteString(name, CharsetUtil.US_ASCII), new ByteString(value, CharsetUtil.US_ASCII));
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void httpGet(Blackhole bh) {
|
||||
for (AsciiString name : httpNames) {
|
||||
bh.consume(httpHeaders.get(name));
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public DefaultHttpHeaders httpPut() {
|
||||
DefaultHttpHeaders headers = new DefaultHttpHeaders(false);
|
||||
for (int i = 0; i < httpNames.length; i++) {
|
||||
headers.add(httpNames[i], httpValues[i]);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void httpIterate(Blackhole bh) {
|
||||
for (Entry<CharSequence, CharSequence> entry : httpHeaders) {
|
||||
bh.consume(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void http2Get(Blackhole bh) {
|
||||
for (ByteString name : http2Names) {
|
||||
bh.consume(http2Headers.get(name));
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public DefaultHttp2Headers http2Put() {
|
||||
DefaultHttp2Headers headers = new DefaultHttp2Headers();
|
||||
for (int i = 0; i < httpNames.length; i++) {
|
||||
headers.add(httpNames[i], httpValues[i]);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void http2IterateNew(Blackhole bh) {
|
||||
for (Entry<ByteString, ByteString> entry : http2Headers) {
|
||||
bh.consume(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void http2IterateOld(Blackhole bh) {
|
||||
// This is how we had to iterate in the Http2HeadersEncoder when writing the frames on the wire
|
||||
// in order to ensure that reserved headers come first.
|
||||
for (Http2Headers.PseudoHeaderName pseudoHeader : Http2Headers.PseudoHeaderName.values()) {
|
||||
ByteString name = pseudoHeader.value();
|
||||
ByteString value = http2Headers.get(name);
|
||||
if (value != null) {
|
||||
bh.consume(value);
|
||||
}
|
||||
}
|
||||
for (Entry<ByteString, ByteString> entry : http2Headers) {
|
||||
final ByteString name = entry.getKey();
|
||||
final ByteString value = entry.getValue();
|
||||
if (!Http2Headers.PseudoHeaderName.isPseudoHeader(name)) {
|
||||
bh.consume(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
/**
|
||||
* Benchmarks for HTTP and HTTP/2 Headers.
|
||||
*/
|
||||
package io.netty.microbench.headers;
|
Loading…
Reference in New Issue
Block a user