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:
Jakob Buchgraber 2015-04-14 21:14:00 -07:00 committed by Scott Mitchell
parent a1f0785605
commit d31fa31cdc
39 changed files with 2040 additions and 2354 deletions

View File

@ -36,7 +36,7 @@ public class DefaultFullHttpRequest extends DefaultHttpRequest implements FullHt
} }
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, boolean validateHeaders) { 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, public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri,

View File

@ -15,14 +15,18 @@
*/ */
package io.netty.handler.codec.http; package io.netty.handler.codec.http;
import io.netty.handler.codec.DefaultHeaders;
import io.netty.handler.codec.DefaultTextHeaders; import io.netty.handler.codec.DefaultTextHeaders;
import io.netty.handler.codec.TextHeaders; import io.netty.handler.codec.TextHeaders;
import io.netty.util.AsciiString; import io.netty.util.AsciiString;
import io.netty.util.ByteProcessor;
import io.netty.util.internal.PlatformDependent;
import java.util.Calendar; import java.util.Calendar;
import java.util.Comparator;
import java.util.Date; 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 { public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeaders {
@ -46,234 +50,25 @@ public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeader
LOOKUP_TABLE['='] = -1; 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() { public DefaultHttpHeaders() {
this(true); this(true);
} }
public DefaultHttpHeaders(boolean validate) { public DefaultHttpHeaders(boolean validate) {
this(true, validate? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER, false); this(validate, false);
} }
protected DefaultHttpHeaders(boolean validate, boolean singleHeaderFields) { 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) { boolean singleHeaderFields) {
super(true, validate ? VALIDATE_OBJECT_CONVERTER : NO_VALIDATE_OBJECT_CONVERTER, nameConverter, super(new TreeMap<CharSequence, Object>(AsciiString.CHARSEQUENCE_CASE_INSENSITIVE_ORDER),
singleHeaderFields); nameValidator,
validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE,
singleHeaderFields);
} }
@Override @Override
@ -479,4 +274,137 @@ public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeader
super.clear(); super.clear();
return this; 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;
}
}
} }

View File

@ -17,6 +17,7 @@ package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.handler.codec.DefaultHeaders;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import java.util.Map; 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 TrailingHttpHeaders extends DefaultHttpHeaders {
private static final class TrailingHttpHeadersNameConverter extends HttpHeadersNameConverter { private static final class TrailingHttpHeadersNameValidator implements
TrailingHttpHeadersNameConverter(boolean validate) { DefaultHeaders.NameValidator<CharSequence> {
super(validate);
} private static final TrailingHttpHeadersNameValidator INSTANCE = new TrailingHttpHeadersNameValidator();
@Override @Override
public CharSequence convertName(CharSequence name) { public void validate(CharSequence name) {
name = super.convertName(name); HeaderNameValidator.INSTANCE.validate(name);
if (validate) { if (HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(name)
if (HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(name) || HttpHeaderNames.TRANSFER_ENCODING.equalsIgnoreCase(name)
|| HttpHeaderNames.TRANSFER_ENCODING.equalsIgnoreCase(name) || HttpHeaderNames.TRAILER.equalsIgnoreCase(name)) {
|| HttpHeaderNames.TRAILER.equalsIgnoreCase(name)) { throw new IllegalArgumentException("prohibited trailing header: " + 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) { TrailingHttpHeaders(boolean validate) {
super(validate, validate ? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER, false); super(validate, validate ? TrailingHttpHeadersNameValidator.INSTANCE : NO_NAME_VALIDATOR, false);
} }
} }
} }

View File

@ -18,6 +18,7 @@ package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -230,7 +231,9 @@ public final class HttpHeaderUtil {
m.headers().add(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); m.headers().add(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
m.headers().remove(HttpHeaderNames.CONTENT_LENGTH); m.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
} else { } 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()) { if (values.isEmpty()) {
return; return;
} }

View File

@ -18,24 +18,14 @@ package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.handler.codec.TextHeaders.EntryVisitor;
import io.netty.util.AsciiString; import io.netty.util.AsciiString;
import java.util.Map.Entry; final class HttpHeadersEncoder {
final class HttpHeadersEncoder implements EntryVisitor { private HttpHeadersEncoder() {
private final ByteBuf buf;
HttpHeadersEncoder(ByteBuf buf) {
this.buf = buf;
} }
@Override public static void encoderHeader(CharSequence name, CharSequence value, ByteBuf buf) throws Exception {
public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception {
final CharSequence name = entry.getKey();
final CharSequence value = entry.getValue();
final ByteBuf buf = this.buf;
final int nameLen = name.length(); final int nameLen = name.length();
final int valueLen = value.length(); final int valueLen = value.length();
final int entryLen = nameLen + valueLen + 4; final int entryLen = nameLen + valueLen + 4;
@ -50,7 +40,6 @@ final class HttpHeadersEncoder implements EntryVisitor {
buf.setByte(offset ++, '\r'); buf.setByte(offset ++, '\r');
buf.setByte(offset ++, '\n'); buf.setByte(offset ++, '\n');
buf.writerIndex(offset); buf.writerIndex(offset);
return true;
} }
private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) { private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) {

View File

@ -24,6 +24,7 @@ import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import java.util.List; import java.util.List;
import java.util.Map.Entry;
import static io.netty.buffer.Unpooled.*; import static io.netty.buffer.Unpooled.*;
import static io.netty.handler.codec.http.HttpConstants.*; 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}. * Encode the {@link HttpHeaders} into a {@link ByteBuf}.
*/ */
protected void encodeHeaders(HttpHeaders headers, ByteBuf buf) throws Exception { 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) { private void encodeChunkedContent(ChannelHandlerContext ctx, Object msg, long contentLength, List<Object> out) {

View File

@ -16,44 +16,17 @@
package io.netty.handler.codec.spdy; package io.netty.handler.codec.spdy;
import io.netty.handler.codec.DefaultTextHeaders; import io.netty.handler.codec.DefaultTextHeaders;
import io.netty.handler.codec.Headers;
import io.netty.handler.codec.TextHeaders; 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 { 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() { public DefaultSpdyHeaders() {
super(true, SPDY_VALUE_CONVERTER, SPDY_NAME_CONVERTER); super(new LinkedHashMap<CharSequence, Object>(),
HeaderNameValidator.INSTANCE,
HeaderValueConverterAndValidator.INSTANCE,
false);
} }
@Override @Override
@ -259,4 +232,32 @@ public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeader
super.clear(); super.clear();
return this; 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;
}
}
} }

View File

@ -305,6 +305,9 @@ final class SpdyCodecUtil {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"name contains null character: " + name); "name contains null character: " + name);
} }
if (c >= 'A' && c <= 'Z') {
throw new IllegalArgumentException("name must be all lower case.");
}
if (c > 127) { if (c > 127) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"name contains non-ascii character: " + name); "name contains non-ascii character: " + name);

View File

@ -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());
}
}

View File

@ -22,6 +22,7 @@ import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -83,7 +84,7 @@ public class SpdySessionHandlerTest {
assertEquals(last, spdyHeadersFrame.isLast()); assertEquals(last, spdyHeadersFrame.isLast());
for (CharSequence name: headers.names()) { for (CharSequence name: headers.names()) {
List<CharSequence> expectedValues = headers.getAll(name); 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)); assertTrue(receivedValues.containsAll(expectedValues));
receivedValues.removeAll(expectedValues); receivedValues.removeAll(expectedValues);
assertTrue(receivedValues.isEmpty()); assertTrue(receivedValues.isEmpty());
@ -105,7 +106,7 @@ public class SpdySessionHandlerTest {
SpdySynStreamFrame spdySynStreamFrame = SpdySynStreamFrame spdySynStreamFrame =
new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0); new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0);
spdySynStreamFrame.headers().set("Compression", "test"); spdySynStreamFrame.headers().set("compression", "test");
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId); SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId);
spdyDataFrame.setLast(true); spdyDataFrame.setLast(true);
@ -138,8 +139,8 @@ public class SpdySessionHandlerTest {
assertNull(sessionHandler.readOutbound()); assertNull(sessionHandler.readOutbound());
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(localStreamId); SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(localStreamId);
spdyHeadersFrame.headers().add("HEADER", "test1"); spdyHeadersFrame.headers().add("header", "test1");
spdyHeadersFrame.headers().add("HEADER", "test2"); spdyHeadersFrame.headers().add("header", "test2");
sessionHandler.writeInbound(spdyHeadersFrame); sessionHandler.writeInbound(spdyHeadersFrame);
assertHeaders(sessionHandler.readOutbound(), localStreamId, false, spdyHeadersFrame.headers()); assertHeaders(sessionHandler.readOutbound(), localStreamId, false, spdyHeadersFrame.headers());
@ -245,7 +246,7 @@ public class SpdySessionHandlerTest {
SpdySynStreamFrame spdySynStreamFrame = SpdySynStreamFrame spdySynStreamFrame =
new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0); new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0);
spdySynStreamFrame.headers().set("Compression", "test"); spdySynStreamFrame.headers().set("compression", "test");
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId); SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId);
spdyDataFrame.setLast(true); spdyDataFrame.setLast(true);

View File

@ -14,86 +14,19 @@
*/ */
package io.netty.handler.codec.http2; 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.BinaryHeaders;
import io.netty.handler.codec.DefaultBinaryHeaders; import io.netty.handler.codec.DefaultBinaryHeaders;
import io.netty.util.AsciiString; import io.netty.handler.codec.DefaultHeaders;
import io.netty.util.ByteProcessor;
import io.netty.util.ByteString; 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 { 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() { public DefaultHttp2Headers() {
this(true); super(new TreeMap<ByteString, Object>(Http2HeaderNameComparator.INSTANCE));
}
/**
* 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);
} }
@Override @Override
@ -354,4 +287,45 @@ public class DefaultHttp2Headers extends DefaultBinaryHeaders implements Http2He
public ByteString status() { public ByteString status() {
return get(PseudoHeaderName.STATUS.value()); 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;
}
}
} }

View File

@ -23,9 +23,9 @@ import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.ByteBufOutputStream;
import io.netty.handler.codec.BinaryHeaders.EntryVisitor;
import io.netty.util.ByteString; import io.netty.util.ByteString;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -65,26 +65,9 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
tableSizeChangeOutput.reset(); tableSizeChangeOutput.reset();
} }
// Write pseudo headers first as required by the HTTP/2 spec. for (Entry<ByteString, ByteString> header : headers) {
for (Http2Headers.PseudoHeaderName pseudoHeader : Http2Headers.PseudoHeaderName.values()) { encodeHeader(header.getKey(), header.getValue(), stream);
ByteString name = pseudoHeader.value();
ByteString value = headers.get(name);
if (value != null) {
encodeHeader(name, value, 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) { } catch (Http2Exception e) {
throw e; throw e;
} catch (Throwable t) { } catch (Throwable t) {

View File

@ -20,6 +20,8 @@ import io.netty.util.ByteString;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
/** /**
@ -183,6 +185,14 @@ public interface Http2Headers extends BinaryHeaders {
@Override @Override
Http2Headers clear(); 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 * Sets the {@link PseudoHeaderName#METHOD} header or {@code null} if there is no such header
*/ */

View File

@ -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.connectionError;
import static io.netty.handler.codec.http2.Http2Exception.streamError; import static io.netty.handler.codec.http2.Http2Exception.streamError;
import static io.netty.util.internal.ObjectUtil.checkNotNull; 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.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpMessage; import io.netty.handler.codec.http.FullHttpMessage;
@ -246,9 +244,11 @@ public final class HttpUtil {
FullHttpMessage destinationMessage, boolean addToTrailer) throws Http2Exception { FullHttpMessage destinationMessage, boolean addToTrailer) throws Http2Exception {
HttpHeaders headers = addToTrailer ? destinationMessage.trailingHeaders() : destinationMessage.headers(); HttpHeaders headers = addToTrailer ? destinationMessage.trailingHeaders() : destinationMessage.headers();
boolean request = destinationMessage instanceof HttpRequest; boolean request = destinationMessage instanceof HttpRequest;
Http2ToHttpHeaderTranslator visitor = new Http2ToHttpHeaderTranslator(streamId, headers, request); Http2ToHttpHeaderTranslator translator = new Http2ToHttpHeaderTranslator(streamId, headers, request);
try { try {
sourceHeaders.forEachEntry(visitor); for (Entry<ByteString, ByteString> entry : sourceHeaders) {
translator.translate(entry);
}
} catch (Http2Exception ex) { } catch (Http2Exception ex) {
throw ex; throw ex;
} catch (Throwable t) { } catch (Throwable t) {
@ -315,29 +315,25 @@ public final class HttpUtil {
final Http2Headers out = new DefaultHttp2Headers(); final Http2Headers out = new DefaultHttp2Headers();
inHeaders.forEachEntry(new EntryVisitor() { for (Entry<CharSequence, CharSequence> entry : inHeaders) {
@Override final AsciiString aName = AsciiString.of(entry.getKey()).toLowerCase();
public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception { if (!HTTP_TO_HTTP2_HEADER_BLACKLIST.contains(aName)) {
final AsciiString aName = AsciiString.of(entry.getKey()).toLowerCase(); AsciiString aValue = AsciiString.of(entry.getValue());
if (!HTTP_TO_HTTP2_HEADER_BLACKLIST.contains(aName)) { // https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.2
AsciiString aValue = AsciiString.of(entry.getValue()); // makes a special exception for TE
// https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.2 if (!aName.equalsIgnoreCase(HttpHeaderNames.TE) ||
// makes a special exception for TE aValue.equalsIgnoreCase(HttpHeaderValues.TRAILERS)) {
if (!aName.equalsIgnoreCase(HttpHeaderNames.TE) || out.add(aName, aValue);
aValue.equalsIgnoreCase(HttpHeaderValues.TRAILERS)) {
out.add(aName, aValue);
}
} }
return true;
} }
}); }
return out; return out;
} }
/** /**
* A visitor which translates HTTP/2 headers to HTTP/1 headers * 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. * 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; translations = request ? REQUEST_HEADER_TRANSLATIONS : RESPONSE_HEADER_TRANSLATIONS;
} }
@Override public void translate(Entry<ByteString, ByteString> entry) throws Http2Exception {
public boolean visit(Entry<ByteString, ByteString> entry) throws Http2Exception {
final ByteString name = entry.getKey(); final ByteString name = entry.getKey();
final ByteString value = entry.getValue(); final ByteString value = entry.getValue();
ByteString translatedName = translations.get(name); ByteString translatedName = translations.get(name);
@ -391,7 +386,6 @@ public final class HttpUtil {
output.add(new AsciiString(translatedName, false), new AsciiString(value, false)); output.add(new AsciiString(translatedName, false), new AsciiString(value, false));
} }
} }
return true;
} }
} }
} }

View File

@ -20,7 +20,6 @@ import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import java.util.Map.Entry; import java.util.Map.Entry;
import io.netty.channel.ChannelHandlerContext; 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.DefaultHttpHeaders;
import io.netty.handler.codec.http.FullHttpMessage; import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.HttpHeaders; 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) { private static void addHttpHeadersToHttp2Headers(HttpHeaders httpHeaders, final Http2Headers http2Headers) {
try { try {
httpHeaders.forEachEntry(new EntryVisitor() { for (Entry<CharSequence, CharSequence> entry : httpHeaders) {
@Override http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue()));
public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception { }
http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue()));
return true;
}
});
} catch (Exception ex) { } catch (Exception ex) {
PlatformDependent.throwException(ex); PlatformDependent.throwException(ex);
} }

View File

@ -1,55 +1,57 @@
/* /*
* Copyright 2014 The Netty Project * Copyright 2014 The Netty Project
* *
* The Netty Project licenses this file to you under the Apache License, version * The Netty Project licenses this file to you under the Apache License,
* 2.0 (the "License"); you may not use this file except in compliance with the * version 2.0 (the "License"); you may not use this file except in compliance
* License. You may obtain a copy of the License at: * 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 * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations
* the License. * under the License.
*/ */
package io.netty.handler.codec.http2; 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.ByteString;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import org.junit.Test; 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 { 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 @Test
public void defaultLowercase() { public void pseudoHeadersMustComeFirstWhenIterating() {
Http2Headers headers = new DefaultHttp2Headers().set(NAME_BYTESTRING, VALUE); DefaultHttp2Headers headers = new DefaultHttp2Headers();
assertArrayEquals(NAME_BYTES_LOWERCASE, first(headers).toByteArray()); 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 private static ByteString bs(String str) {
public void defaultLowercaseAsciiString() { return new ByteString(str, CharsetUtil.US_ASCII);
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();
} }
} }

View File

@ -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); return new ByteString(value);
} }
@ -86,7 +86,7 @@ final class Http2TestUtil {
* Returns an {@link AsciiString} that wraps a randomly-filled byte array. * Returns an {@link AsciiString} that wraps a randomly-filled byte array.
*/ */
public static ByteString randomString() { public static ByteString randomString() {
return as(randomBytes()); return bs(randomBytes());
} }
private Http2TestUtil() { private Http2TestUtil() {

View File

@ -19,8 +19,14 @@ package io.netty.handler.codec.stomp;
import io.netty.handler.codec.DefaultTextHeaders; import io.netty.handler.codec.DefaultTextHeaders;
import io.netty.handler.codec.TextHeaders; import io.netty.handler.codec.TextHeaders;
import java.util.TreeMap;
public class DefaultStompHeaders extends DefaultTextHeaders implements StompHeaders { public class DefaultStompHeaders extends DefaultTextHeaders implements StompHeaders {
public DefaultStompHeaders() {
super(new TreeMap<CharSequence, Object>(), NO_NAME_VALIDATOR, CharSequenceConverter.INSTANCE, false);
}
@Override @Override
public StompHeaders add(CharSequence name, CharSequence value) { public StompHeaders add(CharSequence name, CharSequence value) {
super.add(name, value); super.add(name, value);

View File

@ -25,6 +25,7 @@ import io.netty.util.CharsetUtil;
import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.PlatformDependent;
import java.util.List; import java.util.List;
import java.util.Map.Entry;
/** /**
* Encodes a {@link StompFrame} or a {@link StompSubframe} into a {@link ByteBuf}. * 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.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII));
buf.writeByte(StompConstants.LF); buf.writeByte(StompConstants.LF);
try { AsciiHeadersEncoder headersEncoder = new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF);
frame.headers().forEachEntry(new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF)); for (Entry<CharSequence, CharSequence> entry : frame.headers()) {
} catch (Exception ex) { headersEncoder.encode(entry);
buf.release();
PlatformDependent.throwException(ex);
} }
buf.writeByte(StompConstants.LF); buf.writeByte(StompConstants.LF);
return buf; return buf;

View File

@ -18,8 +18,8 @@ package io.netty.handler.codec.stomp;
public final class StompTestConstants { public final class StompTestConstants {
public static final String CONNECT_FRAME = public static final String CONNECT_FRAME =
"CONNECT\n" + "CONNECT\n" +
"host:stomp.github.org\n" +
"accept-version:1.1,1.2\n" + "accept-version:1.1,1.2\n" +
"host:stomp.github.org\n" +
'\n' + '\n' +
'\0'; '\0';
public static final String CONNECTED_FRAME = public static final String CONNECTED_FRAME =

View File

@ -21,10 +21,9 @@ import java.util.Map.Entry;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.handler.codec.TextHeaders.EntryVisitor;
import io.netty.util.AsciiString; 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. * 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; this.newlineType = newlineType;
} }
@Override public void encode(Entry<CharSequence, CharSequence> entry) {
public boolean visit(Entry<CharSequence, CharSequence> entry) throws Exception {
final CharSequence name = entry.getKey(); final CharSequence name = entry.getKey();
final CharSequence value = entry.getValue(); final CharSequence value = entry.getValue();
final ByteBuf buf = this.buf; final ByteBuf buf = this.buf;
@ -119,7 +117,6 @@ public final class AsciiHeadersEncoder implements EntryVisitor {
} }
buf.writerIndex(offset); buf.writerIndex(offset);
return true;
} }
private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) { private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) {

View File

@ -24,17 +24,6 @@ import io.netty.util.ByteString;
* some additional utility when handling text data. * some additional utility when handling text data.
*/ */
public interface BinaryHeaders extends Headers<ByteString> { 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 @Override
BinaryHeaders add(ByteString name, ByteString value); BinaryHeaders add(ByteString name, ByteString value);

View File

@ -89,13 +89,6 @@ public interface ConvertibleHeaders<UnconvertedType, ConvertedType> extends Head
*/ */
List<ConvertedType> getAllAndRemoveAndConvert(UnconvertedType name); 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 * Invokes {@link Headers#iterator()} and lazily does a conversion on the results as they are accessed
* *

View File

@ -20,128 +20,24 @@ import io.netty.util.internal.PlatformDependent;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.ParseException; import java.text.ParseException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
public class DefaultBinaryHeaders extends DefaultHeaders<ByteString> implements BinaryHeaders { 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 private static final NameValidator<ByteString> NO_NAME_VALIDATOR = DefaultHeaders.NoNameValidator.instance();
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>();
public DefaultBinaryHeaders() { public DefaultBinaryHeaders() {
this(IDENTITY_NAME_CONVERTER); this(new TreeMap<ByteString, Object>(ByteString.DEFAULT_COMPARATOR));
} }
public DefaultBinaryHeaders(NameConverter<ByteString> nameConverter) { public DefaultBinaryHeaders(Map<ByteString, Object> map) {
super(ByteString.DEFAULT_COMPARATOR, ByteString.DEFAULT_COMPARATOR, this(map, NO_NAME_VALIDATOR);
JAVA_HASH_CODE_GENERATOR, OBJECT_TO_BYTE, nameConverter); }
public DefaultBinaryHeaders(Map<ByteString, Object> map, NameValidator<ByteString> nameValidator) {
super(map, nameValidator, ByteStringConverter.INSTANCE);
} }
@Override @Override
@ -347,4 +243,119 @@ public class DefaultBinaryHeaders extends DefaultHeaders<ByteString> implements
super.clear(); super.clear();
return this; 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);
}
}
} }

View File

@ -18,6 +18,7 @@ import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
@ -27,22 +28,11 @@ public class DefaultConvertibleHeaders<UnconvertedType, ConvertedType> extends D
private final TypeConverter<UnconvertedType, ConvertedType> typeConverter; private final TypeConverter<UnconvertedType, ConvertedType> typeConverter;
public DefaultConvertibleHeaders(Comparator<? super UnconvertedType> keyComparator, public DefaultConvertibleHeaders(Map<UnconvertedType, Object> map,
Comparator<? super UnconvertedType> valueComparator, NameValidator<UnconvertedType> nameValidator,
HashCodeGenerator<UnconvertedType> hashCodeGenerator, ValueConverter<UnconvertedType> valueConverter,
ValueConverter<UnconvertedType> valueConverter, TypeConverter<UnconvertedType, ConvertedType> typeConverter) {
TypeConverter<UnconvertedType, ConvertedType> typeConverter) { super(map, nameValidator, valueConverter);
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);
this.typeConverter = typeConverter; this.typeConverter = typeConverter;
} }
@ -94,17 +84,6 @@ public class DefaultConvertibleHeaders<UnconvertedType, ConvertedType> extends D
return allConverted; 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 @Override
public Iterator<Entry<ConvertedType, ConvertedType>> iteratorConverted() { public Iterator<Entry<ConvertedType, ConvertedType>> iteratorConverted() {
return new ConvertedIterator(); return new ConvertedIterator();

File diff suppressed because it is too large Load Diff

View File

@ -24,184 +24,37 @@ import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import java.text.ParseException; import java.text.ParseException;
import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence, String> implements TextHeaders { 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 = public static final NameValidator<CharSequence> NO_NAME_VALIDATOR = NoNameValidator.instance();
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;
private final ValuesComposer valuesComposer; private final ValuesComposer valuesComposer;
public DefaultTextHeaders() { public DefaultTextHeaders() {
this(true); this(false);
} }
public DefaultTextHeaders(boolean ignoreCase) { public DefaultTextHeaders(boolean singleHeaderFields) {
this(ignoreCase, CHARSEQUENCE_FROM_OBJECT_CONVERTER, CHARSEQUENCE_IDENTITY_CONVERTER, false); this(new TreeMap<CharSequence, Object>(CHARSEQUENCE_CASE_INSENSITIVE_ORDER), NO_NAME_VALIDATOR,
CharSequenceConverter.INSTANCE, singleHeaderFields);
} }
public DefaultTextHeaders(boolean ignoreCase, boolean singleHeaderFields) { public DefaultTextHeaders(Map<CharSequence, Object> map,
this(ignoreCase, CHARSEQUENCE_FROM_OBJECT_CONVERTER, CHARSEQUENCE_IDENTITY_CONVERTER, singleHeaderFields); NameValidator<CharSequence> nameValidator,
} ValueConverter<CharSequence> valueConverter,
boolean singleHeaderFields) {
protected DefaultTextHeaders(boolean ignoreCase, Headers.ValueConverter<CharSequence> valueConverter, super(map, nameValidator, valueConverter, CharSequenceToStringConverter.INSTANCE);
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);
valuesComposer = singleHeaderFields ? new SingleHeaderValuesComposer() : new MultipleFieldsValueComposer(); valuesComposer = singleHeaderFields ? new SingleHeaderValuesComposer() : new MultipleFieldsValueComposer();
} }
@Override @Override
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
return contains(name, value, comparator(ignoreCase)); return contains(name, value,
} ignoreCase ? CHARSEQUENCE_CASE_INSENSITIVE_ORDER : CHARSEQUENCE_CASE_SENSITIVE_ORDER);
@Override
public boolean containsObject(CharSequence name, Object value, boolean ignoreCase) {
return containsObject(name, value, comparator(ignoreCase));
} }
@Override @Override
@ -398,10 +251,6 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence,
return this; 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. * This interface enables different implementations for adding/setting header values.
* Concrete implementations can control how values are added, for example to add all * 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, CharSequence... values);
TextHeaders add(CharSequence name, Iterable<? extends 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, Iterable<?> values);
TextHeaders addObject(CharSequence name, Object... values); TextHeaders addObject(CharSequence name, Object... values);
@ -446,6 +296,12 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence,
return DefaultTextHeaders.this; return DefaultTextHeaders.this;
} }
@Override
public TextHeaders addObject(CharSequence name, Object value) {
DefaultTextHeaders.super.addObject(name, value);
return DefaultTextHeaders.this;
}
@Override @Override
public TextHeaders addObject(CharSequence name, Iterable<?> values) { public TextHeaders addObject(CharSequence name, Iterable<?> values) {
DefaultTextHeaders.super.addObject(name, values); DefaultTextHeaders.super.addObject(name, values);
@ -534,6 +390,11 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence,
return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values)); return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values));
} }
@Override
public TextHeaders addObject(CharSequence name, Object value) {
return addEscapedValue(name, objectEscaper().escape(value));
}
@Override @Override
public TextHeaders addObject(CharSequence name, Iterable<?> values) { public TextHeaders addObject(CharSequence name, Iterable<?> values) {
return addEscapedValue(name, commaSeparate(objectEscaper(), 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) { 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) { if (values.length > 0) {
int end = values.length - 1; int end = values.length - 1;
for (int i = 0; i < end; i++) { for (int i = 0; i < end; i++) {
@ -625,4 +487,130 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders<CharSequence,
*/ */
CharSequence escape(T value); 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());
}
}
} }

View File

@ -54,14 +54,10 @@ public class EmptyConvertibleHeaders<UnconvertedType, ConvertedType> extends
return Collections.emptyList(); return Collections.emptyList();
} }
@Override
public List<Entry<ConvertedType, ConvertedType>> entriesConverted() {
return Collections.emptyList();
}
@Override @Override
public Iterator<Entry<ConvertedType, ConvertedType>> iteratorConverted() { public Iterator<Entry<ConvertedType, ConvertedType>> iteratorConverted() {
return entriesConverted().iterator(); List<Entry<ConvertedType, ConvertedType>> empty = Collections.emptyList();
return empty.iterator();
} }
@Override @Override

View File

@ -88,7 +88,7 @@ public class EmptyHeaders<T> implements Headers<T> {
} }
@Override @Override
public short getInt(T name, short defaultValue) { public short getShort(T name, short defaultValue) {
return defaultValue; return defaultValue;
} }
@ -232,11 +232,6 @@ public class EmptyHeaders<T> implements Headers<T> {
return defaultValue; return defaultValue;
} }
@Override
public List<Entry<T, T>> entries() {
return Collections.emptyList();
}
@Override @Override
public boolean contains(T name) { public boolean contains(T name) {
return false; return false;
@ -298,24 +293,7 @@ public class EmptyHeaders<T> implements Headers<T> {
} }
@Override @Override
public boolean contains(T name, T value, Comparator<? super T> comparator) { public boolean contains(T name, T value, Comparator<? super T> valueComparator) {
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) {
return false; return false;
} }
@ -334,11 +312,6 @@ public class EmptyHeaders<T> implements Headers<T> {
return Collections.emptySet(); return Collections.emptySet();
} }
@Override
public List<T> namesList() {
return Collections.emptyList();
}
@Override @Override
public Headers<T> add(T name, T value) { public Headers<T> add(T name, T value) {
throw new UnsupportedOperationException("read only"); throw new UnsupportedOperationException("read only");
@ -415,7 +388,7 @@ public class EmptyHeaders<T> implements Headers<T> {
} }
@Override @Override
public Headers<T> add(Headers<T> headers) { public Headers<T> add(Headers<? extends T> headers) {
throw new UnsupportedOperationException("read only"); throw new UnsupportedOperationException("read only");
} }
@ -495,12 +468,12 @@ public class EmptyHeaders<T> implements Headers<T> {
} }
@Override @Override
public Headers<T> set(Headers<T> headers) { public Headers<T> set(Headers<? extends T> headers) {
throw new UnsupportedOperationException("read only"); throw new UnsupportedOperationException("read only");
} }
@Override @Override
public Headers<T> setAll(Headers<T> headers) { public Headers<T> setAll(Headers<? extends T> headers) {
throw new UnsupportedOperationException("read only"); throw new UnsupportedOperationException("read only");
} }
@ -516,17 +489,8 @@ public class EmptyHeaders<T> implements Headers<T> {
@Override @Override
public Iterator<Entry<T, T>> iterator() { public Iterator<Entry<T, T>> iterator() {
return entries().iterator(); List<Entry<T, T>> empty = Collections.emptyList();
} return empty.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;
} }
@Override @Override

View File

@ -25,11 +25,6 @@ public class EmptyTextHeaders extends EmptyConvertibleHeaders<CharSequence, Stri
return false; return false;
} }
@Override
public boolean containsObject(CharSequence name, Object value, boolean ignoreCase) {
return false;
}
@Override @Override
public TextHeaders add(CharSequence name, CharSequence value) { public TextHeaders add(CharSequence name, CharSequence value) {
super.add(name, value); super.add(name, value);

File diff suppressed because it is too large Load Diff

View File

@ -25,17 +25,6 @@ package io.netty.handler.codec;
* . * .
*/ */
public interface TextHeaders extends ConvertibleHeaders<CharSequence, String> { 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. * 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); 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 @Override
TextHeaders add(CharSequence name, CharSequence value); TextHeaders add(CharSequence name, CharSequence value);

View File

@ -14,20 +14,25 @@
*/ */
package io.netty.handler.codec; package io.netty.handler.codec;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; 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 static org.junit.Assert.assertTrue;
import io.netty.util.AsciiString; import static org.junit.Assert.fail;
import io.netty.util.ByteString; import io.netty.util.ByteString;
import java.util.HashSet; import java.text.ParsePosition;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import io.netty.util.CharsetUtil;
import org.junit.Test; import org.junit.Test;
/** /**
@ -36,107 +41,186 @@ import org.junit.Test;
public class DefaultBinaryHeadersTest { public class DefaultBinaryHeadersTest {
@Test @Test
public void binaryHeadersWithSameValuesShouldBeEquivalent() { public void addShouldIncreaseAndRemoveShouldDecreaseTheSize() {
byte[] key1 = randomBytes(); DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
byte[] value1 = randomBytes(); assertEquals(0, headers.size());
byte[] key2 = randomBytes(); headers.add(bs("name1"), bs("value1"), bs("value2"));
byte[] value2 = randomBytes(); 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(); headers.remove(bs("name3"));
h1.set(as(key1), as(value1)); assertEquals(4, headers.size());
h1.set(as(key2), as(value2)); headers.remove(bs("name1"));
assertEquals(2, headers.size());
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); headers.remove(bs("name2"));
h2.set(as(key1), as(value1)); assertEquals(0, headers.size());
h2.set(as(key2), as(value2)); assertTrue(headers.isEmpty());
assertTrue(h1.equals(h2));
assertTrue(h2.equals(h1));
assertTrue(h2.equals(h2));
assertTrue(h1.equals(h1));
} }
@Test @Test
public void binaryHeadersWithSameDuplicateValuesShouldBeEquivalent() { public void afterClearHeadersShouldBeEmpty() {
byte[] k1 = randomBytes(); DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
byte[] k2 = randomBytes(); headers.add(bs("name1"), bs("value1"));
byte[] v1 = randomBytes(); headers.add(bs("name2"), bs("value2"));
byte[] v2 = randomBytes(); assertEquals(2, headers.size());
byte[] v3 = randomBytes(); headers.clear();
byte[] v4 = randomBytes(); assertEquals(0, headers.size());
assertTrue(headers.isEmpty());
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); assertFalse(headers.contains(bs("name1")));
h1.set(as(k1), as(v1)); assertFalse(headers.contains(bs("name2")));
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));
} }
@Test @Test
public void binaryHeadersWithDifferentValuesShouldNotBeEquivalent() { public void removingANameForASecondTimeShouldReturnFalse() {
byte[] k1 = randomBytes(); DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
byte[] k2 = randomBytes(); headers.add(bs("name1"), bs("value1"));
byte[] v1 = randomBytes(); headers.add(bs("name2"), bs("value2"));
byte[] v2 = randomBytes(); assertTrue(headers.remove(bs("name2")));
byte[] v3 = randomBytes(); assertFalse(headers.remove(bs("name2")));
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));
} }
@Test @Test
public void binarySetAllShouldMergeHeaders() { public void multipleValuesPerNameShouldBeAllowed() {
byte[] k1 = randomBytes(); DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
byte[] k2 = randomBytes(); headers.add(bs("name"), bs("value1"));
byte[] v1 = randomBytes(); headers.add(bs("name"), bs("value2"));
byte[] v2 = randomBytes(); headers.add(bs("name"), bs("value3"));
byte[] v3 = randomBytes(); assertEquals(3, headers.size());
byte[] v4 = randomBytes();
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(); DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
h1.set(as(k1), as(v1));
h1.set(as(k2), as(v2)); h1.add(bs("name1"), bs("value1"));
h1.add(as(k2), as(v3)); h1.add(bs("name2"), bs("value2"));
h1.add(as(k1), as(v4)); h1.add(bs("name2"), bs("value3"));
h1.add(bs("name3"), bs("value4"));
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
h2.set(as(k1), as(v1)); h2.add(bs("name1"), bs("value5"));
h2.set(as(k2), as(v2)); h2.add(bs("name2"), bs("value6"));
h2.add(as(k1), as(v4)); h2.add(bs("name1"), bs("value7"));
DefaultBinaryHeaders expected = new DefaultBinaryHeaders(); DefaultBinaryHeaders expected = new DefaultBinaryHeaders();
expected.set(as(k1), as(v1)); expected.add(bs("name1"), bs("value5"));
expected.set(as(k2), as(v2)); expected.add(bs("name2"), bs("value6"));
expected.add(as(k2), as(v3)); expected.add(bs("name1"), bs("value7"));
expected.add(as(k1), as(v4)); expected.add(bs("name3"), bs("value4"));
expected.set(as(k1), as(v1));
expected.set(as(k2), as(v2));
expected.set(as(k1), as(v4));
h1.setAll(h2); h1.setAll(h2);
@ -144,118 +228,64 @@ public class DefaultBinaryHeadersTest {
} }
@Test @Test
public void binarySetShouldReplacePreviousValues() { public void headersWithSameNamesAndValuesShouldBeEquivalent() {
byte[] k1 = randomBytes(); DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
byte[] v1 = randomBytes(); headers1.add(bs("name1"), bs("value1"));
byte[] v2 = randomBytes(); headers1.add(bs("name2"), bs("value2"));
byte[] v3 = randomBytes(); headers1.add(bs("name2"), bs("value3"));
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
h1.add(as(k1), as(v1)); headers2.add(bs("name1"), bs("value1"));
h1.add(as(k1), as(v2)); headers2.add(bs("name2"), bs("value2"));
assertEquals(2, h1.size()); headers2.add(bs("name2"), bs("value3"));
h1.set(as(k1), as(v3)); assertEquals(headers1, headers2);
assertEquals(1, h1.size()); assertEquals(headers2, headers1);
List<ByteString> list = h1.getAll(as(k1)); assertEquals(headers1, headers1);
assertEquals(1, list.size()); assertEquals(headers2, headers2);
assertEquals(as(v3), list.get(0)); assertEquals(headers1.hashCode(), headers2.hashCode());
assertEquals(headers1.hashCode(), headers1.hashCode());
assertEquals(headers2.hashCode(), headers2.hashCode());
} }
@Test @Test
public void headersWithSameValuesShouldBeEquivalent() { public void emptyHeadersShouldBeEqual() {
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
h1.set(as("foo"), as("goo")); DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
h1.set(as("foo2"), as("goo2")); 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(); DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
h2.set(as("foo"), as("goo")); h2.set(bs("name2"), bs("value2"));
h2.set(as("foo2"), as("goo2")); assertNotEquals(h1, h2);
assertNotEquals(h2, h1);
assertTrue(h1.equals(h2)); assertEquals(h1, h1);
assertTrue(h2.equals(h1)); assertEquals(h2, h2);
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));
} }
@Test(expected = NoSuchElementException.class) @Test(expected = NoSuchElementException.class)
@ -266,53 +296,138 @@ public class DefaultBinaryHeadersTest {
} }
@Test @Test
public void iterateHeadersShouldReturnAllValues() { public void iteratorShouldReturnAllNameValuePairs() {
Set<String> headers = new HashSet<String>(); DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
headers.add("a:1"); headers1.add(bs("name1"), bs("value1"), bs("value2"));
headers.add("a:2"); headers1.add(bs("name2"), bs("value3"));
headers.add("a:3"); headers1.add(bs("name3"), bs("value4"), bs("value5"), bs("value6"));
headers.add("b:1"); headers1.add(bs("name1"), bs("value7"), bs("value8"));
headers.add("b:2"); assertEquals(8, headers1.size());
headers.add("c:1");
// Build the headers from the input set. DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); for (Entry<ByteString, ByteString> entry : headers1) {
for (String header : headers) { Object v = entry.getValue();
String[] parts = header.split(":"); headers2.add(entry.getKey(), entry.getValue());
h1.add(as(parts[0]), as(parts[1]));
} }
// Now iterate through the headers, removing them from the original set. assertEquals(headers1, headers2);
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());
} }
@Test @Test
public void getAndRemoveShouldReturnFirstEntry() { public void iteratorSetValueShouldChangeHeaderValue() {
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
h1.add(as("foo"), as("goo")); headers.add(bs("name1"), bs("value1"), bs("value2"), bs("value3"));
h1.add(as("foo"), as("goo2")); headers.add(bs("name2"), bs("value4"));
assertEquals(as("goo"), h1.getAndRemove(as("foo"))); assertEquals(4, headers.size());
assertEquals(0, h1.size());
List<ByteString> values = h1.getAll(as("foo")); Iterator<Entry<ByteString, ByteString>> iter = headers.iterator();
assertEquals(0, values.size()); 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() { @Test
byte[] data = new byte[100]; public void getAllReturnsEmptyListForUnknownName() {
new Random().nextBytes(data); DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
return data; assertEquals(0, headers.getAll(bs("noname")).size());
} }
private AsciiString as(byte[] bytes) { @Test
return new AsciiString(bytes); 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) { @Test
return new AsciiString(value); 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);
} }
} }

View File

@ -16,8 +16,6 @@
package io.netty.handler.codec; package io.netty.handler.codec;
import static org.junit.Assert.assertEquals; 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.COMMA;
import static io.netty.util.internal.StringUtil.DOUBLE_QUOTE; import static io.netty.util.internal.StringUtil.DOUBLE_QUOTE;
import java.util.Arrays; import java.util.Arrays;
@ -31,88 +29,6 @@ public class DefaultTextHeadersTest {
private static final String HEADER_NAME = "testHeader"; 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 @Test
public void addCharSequences() { public void addCharSequences() {
final TextHeaders headers = newDefaultTextHeaders(); final TextHeaders headers = newDefaultTextHeaders();
@ -325,11 +241,11 @@ public class DefaultTextHeadersTest {
} }
private static TextHeaders newDefaultTextHeaders() { private static TextHeaders newDefaultTextHeaders() {
return new DefaultTextHeaders(); return new DefaultTextHeaders(false);
} }
private static TextHeaders newCsvTextHeaders() { private static TextHeaders newCsvTextHeaders() {
return new DefaultTextHeaders(true, true); return new DefaultTextHeaders(true);
} }
private static void addValues(final TextHeaders headers, HeaderValue... headerValues) { private static void addValues(final TextHeaders headers, HeaderValue... headerValues) {

View File

@ -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>() { public static final Comparator<CharSequence> CHARSEQUENCE_CASE_INSENSITIVE_ORDER = new Comparator<CharSequence>() {
@Override @Override
public int compare(CharSequence o1, CharSequence o2) { public int compare(CharSequence o1, CharSequence o2) {
if (o1 == o2) { int len1 = o1.length();
return 0; int delta = len1 - o2.length();
if (delta != 0) {
return delta;
} }
if (o1.getClass().equals(AsciiString.class) && o2.getClass().equals(AsciiString.class)) {
AsciiString a1 = o1 instanceof AsciiString ? (AsciiString) o1 : null; AsciiString a1 = (AsciiString) o1;
AsciiString a2 = o2 instanceof AsciiString ? (AsciiString) o2 : null; AsciiString a2 = (AsciiString) o2;
final int a1Len = a1.length() + a1.arrayOffset();
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();
for (int i = a1.arrayOffset(), j = a2.arrayOffset(); i < a1Len; i++, j++) { for (int i = a1.arrayOffset(), j = a2.arrayOffset(); i < a1Len; i++, j++) {
byte v1 = a1.value[i]; byte c1 = a1.value[i];
byte v2 = a2.value[j]; byte c2 = a2.value[j];
if (v1 == v2) { if (c1 != c2) {
continue; if (c1 >= 'A' && c1 <= 'Z') {
} c1 += 32;
int c1 = toLowerCase(v1); }
int c2 = toLowerCase(v2); if (c2 >= 'A' && c2 <= 'Z') {
result = c1 - c2; c2 += 32;
if (result != 0) { }
return result; delta = c1 - c2;
} if (delta != 0) {
} return delta;
} 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;
} }
} }
} else { } else {
for (int i = 0; i < minLength; i++) { for (int i = len1 - 1; i >= 0; i --) {
int c1 = toLowerCase(o1.charAt(i)); char c1 = o1.charAt(i);
int c2 = toLowerCase(o2.charAt(i)); char c2 = o2.charAt(i);
result = c1 - c2; if (c1 != c2) {
if (result != 0) { if (c1 >= 'A' && c1 <= 'Z') {
return result; c1 += 32;
}
if (c2 >= 'A' && c2 <= 'Z') {
c2 += 32;
}
delta = c1 - c2;
if (delta != 0) {
return delta;
}
} }
} }
} }
return 0;
return length1 - length2;
} }
}; };

View File

@ -45,6 +45,8 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URI; import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -186,12 +188,17 @@ public final class HttpUploadClient {
); );
// send request // send request
List<Entry<String, String>> entries = headers.entriesConverted();
channel.writeAndFlush(request); channel.writeAndFlush(request);
// Wait for the server to close the connection. // Wait for the server to close the connection.
channel.closeFuture().sync(); 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; return entries;
} }

View File

@ -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() {
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;