diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpRequest.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpRequest.java index 1fbae3bdb5..5178d339c5 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpRequest.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpRequest.java @@ -36,7 +36,7 @@ public class DefaultFullHttpRequest extends DefaultHttpRequest implements FullHt } public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, boolean validateHeaders) { - this(httpVersion, method, uri, Unpooled.buffer(0), true); + this(httpVersion, method, uri, Unpooled.buffer(0), validateHeaders); } public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java index 704c1426c3..ab384e43cc 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java @@ -15,14 +15,18 @@ */ package io.netty.handler.codec.http; +import io.netty.handler.codec.DefaultHeaders; import io.netty.handler.codec.DefaultTextHeaders; import io.netty.handler.codec.TextHeaders; import io.netty.util.AsciiString; -import io.netty.util.ByteProcessor; -import io.netty.util.internal.PlatformDependent; import java.util.Calendar; +import java.util.Comparator; import java.util.Date; +import java.util.List; +import java.util.TreeMap; + +import static io.netty.util.internal.ObjectUtil.checkNotNull; public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeaders { @@ -46,234 +50,25 @@ public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeader LOOKUP_TABLE['='] = -1; } - private static final class HttpHeadersValidationConverter extends DefaultTextValueTypeConverter { - private final boolean validate; - - HttpHeadersValidationConverter(boolean validate) { - this.validate = validate; - } - - @Override - public CharSequence convertObject(Object value) { - if (value == null) { - throw new NullPointerException("value"); - } - - CharSequence seq; - if (value instanceof CharSequence) { - seq = (CharSequence) value; - } else if (value instanceof Number) { - seq = value.toString(); - } else if (value instanceof Date) { - seq = HttpHeaderDateFormat.get().format((Date) value); - } else if (value instanceof Calendar) { - seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime()); - } else { - seq = value.toString(); - } - - if (validate) { - if (value instanceof AsciiString) { - validateValue((AsciiString) seq); - } else { - validateValue(seq); - } - } - - return seq; - } - - private static final class ValidateValueProcessor implements ByteProcessor { - private final CharSequence seq; - private int state; - - public ValidateValueProcessor(CharSequence seq) { - this.seq = seq; - } - - @Override - public boolean process(byte value) throws Exception { - state = validateValueChar(state, (char) value, seq); - return true; - } - - public int state() { - return state; - } - } - - private static void validateValue(AsciiString seq) { - ValidateValueProcessor processor = new ValidateValueProcessor(seq); - try { - seq.forEachByte(processor); - } catch (Throwable t) { - PlatformDependent.throwException(t); - } - - if (processor.state() != 0) { - throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq); - } - } - - private static void validateValue(CharSequence seq) { - int state = 0; - // Start looping through each of the character - for (int index = 0; index < seq.length(); index++) { - state = validateValueChar(state, seq.charAt(index), seq); - } - - if (state != 0) { - throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq); - } - } - - private static int validateValueChar(int state, char c, CharSequence seq) { - /* - * State: - * 0: Previous character was neither CR nor LF - * 1: The previous character was CR - * 2: The previous character was LF - */ - if ((c & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) { - // Check the absolutely prohibited characters. - switch (c) { - case 0x0: // NULL - throw new IllegalArgumentException("a header value contains a prohibited character '\0': " + seq); - case 0x0b: // Vertical tab - throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq); - case '\f': - throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq); - } - } - - // Check the CRLF (HT | SP) pattern - switch (state) { - case 0: - switch (c) { - case '\r': - state = 1; - break; - case '\n': - state = 2; - break; - } - break; - case 1: - switch (c) { - case '\n': - state = 2; - break; - default: - throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq); - } - break; - case 2: - switch (c) { - case '\t': - case ' ': - state = 0; - break; - default: - throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq); - } - } - return state; - } - } - - static class HttpHeadersNameConverter implements NameConverter { - protected final boolean validate; - - private static final class ValidateNameProcessor implements ByteProcessor { - private final CharSequence seq; - - public ValidateNameProcessor(CharSequence seq) { - this.seq = seq; - } - - @Override - public boolean process(byte value) throws Exception { - // Check to see if the character is not an ASCII character. - if (value < 0) { - throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " + seq); - } - validateNameChar(value, seq); - return true; - } - } - - HttpHeadersNameConverter(boolean validate) { - this.validate = validate; - } - - @Override - public CharSequence convertName(CharSequence name) { - if (validate) { - if (name instanceof AsciiString) { - validateName((AsciiString) name); - } else { - validateName(name); - } - } - - return name; - } - - private static void validateName(AsciiString name) { - try { - name.forEachByte(new ValidateNameProcessor(name)); - } catch (Throwable t) { - PlatformDependent.throwException(t); - } - } - - private static void validateName(CharSequence name) { - // Go through each characters in the name. - for (int index = 0; index < name.length(); index++) { - char c = name.charAt(index); - - // Check to see if the character is not an ASCII character. - if (c > 127) { - throw new IllegalArgumentException("a header name cannot contain non-ASCII characters: " + name); - } - - // Check for prohibited characters. - validateNameChar(c, name); - } - } - - private static void validateNameChar(int character, CharSequence seq) { - if ((character & HIGHEST_INVALID_NAME_CHAR_MASK) == 0 && LOOKUP_TABLE[character] != 0) { - throw new IllegalArgumentException( - "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " + - seq); - } - } - } - - private static final HttpHeadersValidationConverter - VALIDATE_OBJECT_CONVERTER = new HttpHeadersValidationConverter(true); - private static final HttpHeadersValidationConverter - NO_VALIDATE_OBJECT_CONVERTER = new HttpHeadersValidationConverter(false); - private static final HttpHeadersNameConverter VALIDATE_NAME_CONVERTER = new HttpHeadersNameConverter(true); - private static final HttpHeadersNameConverter NO_VALIDATE_NAME_CONVERTER = new HttpHeadersNameConverter(false); - public DefaultHttpHeaders() { this(true); } public DefaultHttpHeaders(boolean validate) { - this(true, validate? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER, false); + this(validate, false); } protected DefaultHttpHeaders(boolean validate, boolean singleHeaderFields) { - this(true, validate? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER, singleHeaderFields); + this(true, validate ? HeaderNameValidator.INSTANCE : NO_NAME_VALIDATOR, singleHeaderFields); } - protected DefaultHttpHeaders(boolean validate, NameConverter nameConverter, + protected DefaultHttpHeaders(boolean validate, + DefaultHeaders.NameValidator nameValidator, boolean singleHeaderFields) { - super(true, validate ? VALIDATE_OBJECT_CONVERTER : NO_VALIDATE_OBJECT_CONVERTER, nameConverter, - singleHeaderFields); + super(new TreeMap(AsciiString.CHARSEQUENCE_CASE_INSENSITIVE_ORDER), + nameValidator, + validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE, + singleHeaderFields); } @Override @@ -479,4 +274,137 @@ public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeader super.clear(); return this; } + + @Override + public int hashCode() { + return size(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof HttpHeaders)) { + return false; + } + HttpHeaders headers = (HttpHeaders) other; + return DefaultHeaders.comparatorEquals(this, headers, AsciiString.CHARSEQUENCE_CASE_SENSITIVE_ORDER); + } + + static final class HeaderNameValidator implements DefaultHeaders.NameValidator { + + 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; + } + } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java index 935ec61a9c..3836e0b059 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java @@ -17,6 +17,7 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.handler.codec.DefaultHeaders; import io.netty.util.internal.StringUtil; import java.util.Map; @@ -107,32 +108,24 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt } private static final class TrailingHttpHeaders extends DefaultHttpHeaders { - private static final class TrailingHttpHeadersNameConverter extends HttpHeadersNameConverter { - TrailingHttpHeadersNameConverter(boolean validate) { - super(validate); - } + private static final class TrailingHttpHeadersNameValidator implements + DefaultHeaders.NameValidator { + + private static final TrailingHttpHeadersNameValidator INSTANCE = new TrailingHttpHeadersNameValidator(); @Override - public CharSequence convertName(CharSequence name) { - name = super.convertName(name); - if (validate) { - if (HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(name) - || HttpHeaderNames.TRANSFER_ENCODING.equalsIgnoreCase(name) - || HttpHeaderNames.TRAILER.equalsIgnoreCase(name)) { - throw new IllegalArgumentException("prohibited trailing header: " + name); - } + public void validate(CharSequence name) { + HeaderNameValidator.INSTANCE.validate(name); + if (HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(name) + || HttpHeaderNames.TRANSFER_ENCODING.equalsIgnoreCase(name) + || HttpHeaderNames.TRAILER.equalsIgnoreCase(name)) { + throw new IllegalArgumentException("prohibited trailing header: " + name); } - return name; } } - private static final TrailingHttpHeadersNameConverter - VALIDATE_NAME_CONVERTER = new TrailingHttpHeadersNameConverter(true); - private static final TrailingHttpHeadersNameConverter - NO_VALIDATE_NAME_CONVERTER = new TrailingHttpHeadersNameConverter(false); - TrailingHttpHeaders(boolean validate) { - super(validate, validate ? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER, false); + super(validate, validate ? TrailingHttpHeadersNameValidator.INSTANCE : NO_NAME_VALIDATOR, false); } } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderUtil.java index 3fd220db50..6aeff9a37d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderUtil.java @@ -18,6 +18,7 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -230,7 +231,9 @@ public final class HttpHeaderUtil { m.headers().add(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); m.headers().remove(HttpHeaderNames.CONTENT_LENGTH); } else { - List values = m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING); + // Make a copy to be able to modify values while iterating + List values = + new ArrayList(m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING)); if (values.isEmpty()) { return; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java index 559c6f3ecb..544a3c5302 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java @@ -18,24 +18,14 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; -import io.netty.handler.codec.TextHeaders.EntryVisitor; import io.netty.util.AsciiString; -import java.util.Map.Entry; +final class HttpHeadersEncoder { -final class HttpHeadersEncoder implements EntryVisitor { - - private final ByteBuf buf; - - HttpHeadersEncoder(ByteBuf buf) { - this.buf = buf; + private HttpHeadersEncoder() { } - @Override - public boolean visit(Entry entry) throws Exception { - final CharSequence name = entry.getKey(); - final CharSequence value = entry.getValue(); - final ByteBuf buf = this.buf; + public static void encoderHeader(CharSequence name, CharSequence value, ByteBuf buf) throws Exception { final int nameLen = name.length(); final int valueLen = value.length(); final int entryLen = nameLen + valueLen + 4; @@ -50,7 +40,6 @@ final class HttpHeadersEncoder implements EntryVisitor { buf.setByte(offset ++, '\r'); buf.setByte(offset ++, '\n'); buf.writerIndex(offset); - return true; } private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java index 1affe26155..8eff14fa51 100755 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java @@ -24,6 +24,7 @@ import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import java.util.List; +import java.util.Map.Entry; import static io.netty.buffer.Unpooled.*; import static io.netty.handler.codec.http.HttpConstants.*; @@ -137,7 +138,9 @@ public abstract class HttpObjectEncoder extends MessageTo * Encode the {@link HttpHeaders} into a {@link ByteBuf}. */ protected void encodeHeaders(HttpHeaders headers, ByteBuf buf) throws Exception { - headers.forEachEntry(new HttpHeadersEncoder(buf)); + for (Entry header : headers) { + HttpHeadersEncoder.encoderHeader(header.getKey(), header.getValue(), buf); + } } private void encodeChunkedContent(ChannelHandlerContext ctx, Object msg, long contentLength, List out) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java index 53c00b087a..270812c713 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java @@ -16,44 +16,17 @@ package io.netty.handler.codec.spdy; import io.netty.handler.codec.DefaultTextHeaders; -import io.netty.handler.codec.Headers; import io.netty.handler.codec.TextHeaders; -import io.netty.util.AsciiString; -import java.util.Locale; +import java.util.LinkedHashMap; public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeaders { - private static final Headers.ValueConverter 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 SPDY_NAME_CONVERTER = new NameConverter() { - @Override - public CharSequence convertName(CharSequence name) { - if (name instanceof AsciiString) { - name = ((AsciiString) name).toLowerCase(); - } else { - name = name.toString().toLowerCase(Locale.US); - } - SpdyCodecUtil.validateHeaderName(name); - return name; - } - }; public DefaultSpdyHeaders() { - super(true, SPDY_VALUE_CONVERTER, SPDY_NAME_CONVERTER); + super(new LinkedHashMap(), + HeaderNameValidator.INSTANCE, + HeaderValueConverterAndValidator.INSTANCE, + false); } @Override @@ -259,4 +232,32 @@ public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeader super.clear(); return this; } + + private static class HeaderNameValidator implements NameValidator { + + 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; + } + } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java index 612ebdbcb2..b25167c3a9 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java @@ -305,6 +305,9 @@ final class SpdyCodecUtil { throw new IllegalArgumentException( "name contains null character: " + name); } + if (c >= 'A' && c <= 'Z') { + throw new IllegalArgumentException("name must be all lower case."); + } if (c > 127) { throw new IllegalArgumentException( "name contains non-ascii character: " + name); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java new file mode 100644 index 0000000000..e19a5b4d48 --- /dev/null +++ b/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java @@ -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 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()); + } +} diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java index 4c8a54bf85..a249b9d80c 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java @@ -22,6 +22,7 @@ import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import org.junit.Test; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -83,7 +84,7 @@ public class SpdySessionHandlerTest { assertEquals(last, spdyHeadersFrame.isLast()); for (CharSequence name: headers.names()) { List expectedValues = headers.getAll(name); - List receivedValues = spdyHeadersFrame.headers().getAll(name); + List receivedValues = new ArrayList(spdyHeadersFrame.headers().getAll(name)); assertTrue(receivedValues.containsAll(expectedValues)); receivedValues.removeAll(expectedValues); assertTrue(receivedValues.isEmpty()); @@ -105,7 +106,7 @@ public class SpdySessionHandlerTest { SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0); - spdySynStreamFrame.headers().set("Compression", "test"); + spdySynStreamFrame.headers().set("compression", "test"); SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId); spdyDataFrame.setLast(true); @@ -138,8 +139,8 @@ public class SpdySessionHandlerTest { assertNull(sessionHandler.readOutbound()); SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(localStreamId); - spdyHeadersFrame.headers().add("HEADER", "test1"); - spdyHeadersFrame.headers().add("HEADER", "test2"); + spdyHeadersFrame.headers().add("header", "test1"); + spdyHeadersFrame.headers().add("header", "test2"); sessionHandler.writeInbound(spdyHeadersFrame); assertHeaders(sessionHandler.readOutbound(), localStreamId, false, spdyHeadersFrame.headers()); @@ -245,7 +246,7 @@ public class SpdySessionHandlerTest { SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0); - spdySynStreamFrame.headers().set("Compression", "test"); + spdySynStreamFrame.headers().set("compression", "test"); SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId); spdyDataFrame.setLast(true); diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java index 1b515de968..a62d32ad2b 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Headers.java @@ -14,86 +14,19 @@ */ package io.netty.handler.codec.http2; -import static io.netty.util.internal.StringUtil.UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET; import io.netty.handler.codec.BinaryHeaders; import io.netty.handler.codec.DefaultBinaryHeaders; -import io.netty.util.AsciiString; -import io.netty.util.ByteProcessor; +import io.netty.handler.codec.DefaultHeaders; import io.netty.util.ByteString; -import io.netty.util.internal.PlatformDependent; +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; +import java.util.TreeMap; public class DefaultHttp2Headers extends DefaultBinaryHeaders implements Http2Headers { - private static final ByteProcessor HTTP2_ASCII_UPPERCASE_PROCESSOR = new ByteProcessor() { - @Override - public boolean process(byte value) throws Exception { - return value < 'A' || value > 'Z'; - } - }; - private static final class Http2AsciiToLowerCaseConverter implements ByteProcessor { - private final byte[] result; - private int i; - - public Http2AsciiToLowerCaseConverter(int length) { - result = new byte[length]; - } - - @Override - public boolean process(byte value) throws Exception { - result[i++] = (value >= 'A' && value <= 'Z') - ? (byte) (value + UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET) : value; - return true; - } - - public byte[] result() { - return result; - } - }; - - private static final NameConverter HTTP2_ASCII_TO_LOWER_CONVERTER = new NameConverter() { - @Override - public ByteString convertName(ByteString name) { - if (name instanceof AsciiString) { - return ((AsciiString) name).toLowerCase(); - } - - try { - if (name.forEachByte(HTTP2_ASCII_UPPERCASE_PROCESSOR) == -1) { - return name; - } - - Http2AsciiToLowerCaseConverter converter = new Http2AsciiToLowerCaseConverter(name.length()); - name.forEachByte(converter); - return new ByteString(converter.result(), false); - } catch (Exception e) { - PlatformDependent.throwException(e); - return null; - } - } - }; - - /** - * Creates an instance that will convert all header names to lowercase. - */ public DefaultHttp2Headers() { - this(true); - } - - /** - * Creates an instance that can be configured to either do header field name conversion to - * lowercase, or not do any conversion at all. - *

- * - * Note that setting {@code forceKeyToLower} to {@code false} can violate the - * HTTP/2 specification - * which specifies that a request or response containing an uppercase header field MUST be treated - * as malformed. Only set {@code forceKeyToLower} to {@code false} if you are explicitly using lowercase - * header field names and want to avoid the conversion to lowercase. - * - * @param forceKeyToLower if @{code false} no header name conversion will be performed - */ - public DefaultHttp2Headers(boolean forceKeyToLower) { - super(forceKeyToLower ? HTTP2_ASCII_TO_LOWER_CONVERTER : IDENTITY_NAME_CONVERTER); + super(new TreeMap(Http2HeaderNameComparator.INSTANCE)); } @Override @@ -354,4 +287,45 @@ public class DefaultHttp2Headers extends DefaultBinaryHeaders implements Http2He public ByteString status() { return get(PseudoHeaderName.STATUS.value()); } + + @Override + public int hashCode() { + return size(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Http2Headers)) { + return false; + } + Http2Headers headers = (Http2Headers) other; + return DefaultHeaders.comparatorEquals(this, headers, ByteString.DEFAULT_COMPARATOR); + } + + private static class Http2HeaderNameComparator implements Comparator, 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; + } + } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java index 28be6e898f..f0af6d5244 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoder.java @@ -23,9 +23,9 @@ import static io.netty.handler.codec.http2.Http2Exception.connectionError; import static io.netty.util.internal.ObjectUtil.checkNotNull; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; -import io.netty.handler.codec.BinaryHeaders.EntryVisitor; import io.netty.util.ByteString; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -65,26 +65,9 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea tableSizeChangeOutput.reset(); } - // Write pseudo headers first as required by the HTTP/2 spec. - for (Http2Headers.PseudoHeaderName pseudoHeader : Http2Headers.PseudoHeaderName.values()) { - ByteString name = pseudoHeader.value(); - ByteString value = headers.get(name); - if (value != null) { - encodeHeader(name, value, stream); - } + for (Entry header : headers) { + encodeHeader(header.getKey(), header.getValue(), stream); } - - headers.forEachEntry(new EntryVisitor() { - @Override - public boolean visit(Entry entry) throws Exception { - final ByteString name = entry.getKey(); - final ByteString value = entry.getValue(); - if (!Http2Headers.PseudoHeaderName.isPseudoHeader(name)) { - encodeHeader(name, value, stream); - } - return true; - } - }); } catch (Http2Exception e) { throw e; } catch (Throwable t) { diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Headers.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Headers.java index 5b30fc713a..61a8bed800 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Headers.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Headers.java @@ -20,6 +20,8 @@ import io.netty.util.ByteString; import io.netty.util.CharsetUtil; import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; import java.util.Set; /** @@ -183,6 +185,14 @@ public interface Http2Headers extends BinaryHeaders { @Override Http2Headers clear(); + /** + * Returns an iterator over all HTTP/2 headers from this instance. The iteration order is as follows: + * 1. All non-pseudo headers (in no particular order). + * 2. Headers with multiple values will have their values appear in insertion order. + */ + @Override + Iterator> iterator(); + /** * Sets the {@link PseudoHeaderName#METHOD} header or {@code null} if there is no such header */ diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java index 61c7bbb9db..86fb67cece 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/HttpUtil.java @@ -18,8 +18,6 @@ import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; import static io.netty.handler.codec.http2.Http2Exception.connectionError; import static io.netty.handler.codec.http2.Http2Exception.streamError; import static io.netty.util.internal.ObjectUtil.checkNotNull; -import io.netty.handler.codec.BinaryHeaders; -import io.netty.handler.codec.TextHeaders.EntryVisitor; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpMessage; @@ -246,9 +244,11 @@ public final class HttpUtil { FullHttpMessage destinationMessage, boolean addToTrailer) throws Http2Exception { HttpHeaders headers = addToTrailer ? destinationMessage.trailingHeaders() : destinationMessage.headers(); boolean request = destinationMessage instanceof HttpRequest; - Http2ToHttpHeaderTranslator visitor = new Http2ToHttpHeaderTranslator(streamId, headers, request); + Http2ToHttpHeaderTranslator translator = new Http2ToHttpHeaderTranslator(streamId, headers, request); try { - sourceHeaders.forEachEntry(visitor); + for (Entry entry : sourceHeaders) { + translator.translate(entry); + } } catch (Http2Exception ex) { throw ex; } catch (Throwable t) { @@ -315,29 +315,25 @@ public final class HttpUtil { final Http2Headers out = new DefaultHttp2Headers(); - inHeaders.forEachEntry(new EntryVisitor() { - @Override - public boolean visit(Entry entry) throws Exception { - final AsciiString aName = AsciiString.of(entry.getKey()).toLowerCase(); - if (!HTTP_TO_HTTP2_HEADER_BLACKLIST.contains(aName)) { - AsciiString aValue = AsciiString.of(entry.getValue()); - // https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.2 - // makes a special exception for TE - if (!aName.equalsIgnoreCase(HttpHeaderNames.TE) || - aValue.equalsIgnoreCase(HttpHeaderValues.TRAILERS)) { - out.add(aName, aValue); - } + for (Entry entry : inHeaders) { + final AsciiString aName = AsciiString.of(entry.getKey()).toLowerCase(); + if (!HTTP_TO_HTTP2_HEADER_BLACKLIST.contains(aName)) { + AsciiString aValue = AsciiString.of(entry.getValue()); + // https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.2 + // makes a special exception for TE + if (!aName.equalsIgnoreCase(HttpHeaderNames.TE) || + aValue.equalsIgnoreCase(HttpHeaderValues.TRAILERS)) { + out.add(aName, aValue); } - return true; } - }); + } return out; } /** * A visitor which translates HTTP/2 headers to HTTP/1 headers */ - private static final class Http2ToHttpHeaderTranslator implements BinaryHeaders.EntryVisitor { + private static final class Http2ToHttpHeaderTranslator { /** * Translations from HTTP/2 header name to the HTTP/1.x equivalent. */ @@ -372,8 +368,7 @@ public final class HttpUtil { translations = request ? REQUEST_HEADER_TRANSLATIONS : RESPONSE_HEADER_TRANSLATIONS; } - @Override - public boolean visit(Entry entry) throws Http2Exception { + public void translate(Entry entry) throws Http2Exception { final ByteString name = entry.getKey(); final ByteString value = entry.getValue(); ByteString translatedName = translations.get(name); @@ -391,7 +386,6 @@ public final class HttpUtil { output.add(new AsciiString(translatedName, false), new AsciiString(value, false)); } } - return true; } } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/InboundHttp2ToHttpPriorityAdapter.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/InboundHttp2ToHttpPriorityAdapter.java index d4e4a0a1d3..6d047b6af3 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/InboundHttp2ToHttpPriorityAdapter.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/InboundHttp2ToHttpPriorityAdapter.java @@ -20,7 +20,6 @@ import static io.netty.handler.codec.http2.Http2Exception.connectionError; import java.util.Map.Entry; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.TextHeaders.EntryVisitor; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.FullHttpMessage; import io.netty.handler.codec.http.HttpHeaders; @@ -137,13 +136,9 @@ public final class InboundHttp2ToHttpPriorityAdapter extends InboundHttp2ToHttpA */ private static void addHttpHeadersToHttp2Headers(HttpHeaders httpHeaders, final Http2Headers http2Headers) { try { - httpHeaders.forEachEntry(new EntryVisitor() { - @Override - public boolean visit(Entry entry) throws Exception { - http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue())); - return true; - } - }); + for (Entry entry : httpHeaders) { + http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue())); + } } catch (Exception ex) { PlatformDependent.throwException(ex); } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java index 52dd37bb8f..4c4abc0a5b 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersTest.java @@ -1,55 +1,57 @@ /* * Copyright 2014 The Netty Project * - * The Netty Project licenses this file to you under the Apache License, version - * 2.0 (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * License for the specific language governing permissions and limitations + * under the License. */ + package io.netty.handler.codec.http2; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import io.netty.util.AsciiString; import io.netty.util.ByteString; import io.netty.util.CharsetUtil; - import org.junit.Test; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertTrue; + public class DefaultHttp2HeadersTest { - private static final byte[] NAME_BYTES = { 'T', 'E', 's', 'T' }; - private static final byte[] NAME_BYTES_LOWERCASE = { 't', 'e', 's', 't' }; - private static final ByteString NAME_BYTESTRING = new ByteString(NAME_BYTES); - private static final AsciiString NAME_ASCIISTRING = new AsciiString("Test"); - private static final ByteString VALUE = new ByteString("some value", CharsetUtil.UTF_8); - @Test - public void defaultLowercase() { - Http2Headers headers = new DefaultHttp2Headers().set(NAME_BYTESTRING, VALUE); - assertArrayEquals(NAME_BYTES_LOWERCASE, first(headers).toByteArray()); + public void pseudoHeadersMustComeFirstWhenIterating() { + DefaultHttp2Headers headers = new DefaultHttp2Headers(); + headers.add(bs("name1"), bs("value1"), bs("value2")); + headers.method(bs("POST")); + headers.add(bs("2name"), bs("value3")); + headers.path(bs("/index.html")); + headers.status(bs("200")); + headers.authority(bs("netty.io")); + headers.add(bs("name3"), bs("value4")); + headers.scheme(bs("https")); + + Iterator> iter = headers.iterator(); + List names = new ArrayList(); + for (int i = 0; i < 5; i++) { + names.add(iter.next().getKey()); + } + assertTrue(names.containsAll(asList(bs(":method"), bs(":status"), bs(":path"), bs(":scheme"), + bs(":authority")))); } - @Test - public void defaultLowercaseAsciiString() { - Http2Headers headers = new DefaultHttp2Headers().set(NAME_ASCIISTRING, VALUE); - assertEquals(NAME_ASCIISTRING.toLowerCase(), first(headers)); - } - - @Test - public void caseInsensitive() { - Http2Headers headers = new DefaultHttp2Headers(false).set(NAME_BYTESTRING, VALUE); - assertArrayEquals(NAME_BYTES, first(headers).toByteArray()); - } - - private static ByteString first(Http2Headers headers) { - return headers.names().iterator().next(); + private static ByteString bs(String str) { + return new ByteString(str, CharsetUtil.US_ASCII); } } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2TestUtil.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2TestUtil.java index 2547e4cb67..3bfa13432e 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2TestUtil.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2TestUtil.java @@ -60,9 +60,9 @@ final class Http2TestUtil { } /** - * Converts a byte array into an {@link AsciiString}. + * Converts a byte array into a {@link ByteString}. */ - public static ByteString as(byte[] value) { + public static ByteString bs(byte[] value) { return new ByteString(value); } @@ -86,7 +86,7 @@ final class Http2TestUtil { * Returns an {@link AsciiString} that wraps a randomly-filled byte array. */ public static ByteString randomString() { - return as(randomBytes()); + return bs(randomBytes()); } private Http2TestUtil() { diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java index eaf4ca60ff..a3bb2312ff 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java @@ -19,8 +19,14 @@ package io.netty.handler.codec.stomp; import io.netty.handler.codec.DefaultTextHeaders; import io.netty.handler.codec.TextHeaders; +import java.util.TreeMap; + public class DefaultStompHeaders extends DefaultTextHeaders implements StompHeaders { + public DefaultStompHeaders() { + super(new TreeMap(), NO_NAME_VALIDATOR, CharSequenceConverter.INSTANCE, false); + } + @Override public StompHeaders add(CharSequence name, CharSequence value) { super.add(name, value); diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java index 668bd48f37..e57aa5e607 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java @@ -25,6 +25,7 @@ import io.netty.util.CharsetUtil; import io.netty.util.internal.PlatformDependent; import java.util.List; +import java.util.Map.Entry; /** * Encodes a {@link StompFrame} or a {@link StompSubframe} into a {@link ByteBuf}. @@ -66,11 +67,9 @@ public class StompSubframeEncoder extends MessageToMessageEncoder buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII)); buf.writeByte(StompConstants.LF); - try { - frame.headers().forEachEntry(new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF)); - } catch (Exception ex) { - buf.release(); - PlatformDependent.throwException(ex); + AsciiHeadersEncoder headersEncoder = new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF); + for (Entry entry : frame.headers()) { + headersEncoder.encode(entry); } buf.writeByte(StompConstants.LF); return buf; diff --git a/codec-stomp/src/test/java/io/netty/handler/codec/stomp/StompTestConstants.java b/codec-stomp/src/test/java/io/netty/handler/codec/stomp/StompTestConstants.java index a427b944cc..e3e49a7daf 100644 --- a/codec-stomp/src/test/java/io/netty/handler/codec/stomp/StompTestConstants.java +++ b/codec-stomp/src/test/java/io/netty/handler/codec/stomp/StompTestConstants.java @@ -18,8 +18,8 @@ package io.netty.handler.codec.stomp; public final class StompTestConstants { public static final String CONNECT_FRAME = "CONNECT\n" + - "host:stomp.github.org\n" + "accept-version:1.1,1.2\n" + + "host:stomp.github.org\n" + '\n' + '\0'; public static final String CONNECTED_FRAME = diff --git a/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java b/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java index 0531809e81..e73518234f 100644 --- a/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java +++ b/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java @@ -21,10 +21,9 @@ import java.util.Map.Entry; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; -import io.netty.handler.codec.TextHeaders.EntryVisitor; import io.netty.util.AsciiString; -public final class AsciiHeadersEncoder implements EntryVisitor { +public final class AsciiHeadersEncoder { /** * The separator characters to insert between a header name and a header value. @@ -78,8 +77,7 @@ public final class AsciiHeadersEncoder implements EntryVisitor { this.newlineType = newlineType; } - @Override - public boolean visit(Entry entry) throws Exception { + public void encode(Entry entry) { final CharSequence name = entry.getKey(); final CharSequence value = entry.getValue(); final ByteBuf buf = this.buf; @@ -119,7 +117,6 @@ public final class AsciiHeadersEncoder implements EntryVisitor { } buf.writerIndex(offset); - return true; } private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) { diff --git a/codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java b/codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java index b220320da3..f2a4000836 100644 --- a/codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java @@ -24,17 +24,6 @@ import io.netty.util.ByteString; * some additional utility when handling text data. */ public interface BinaryHeaders extends Headers { - /** - * Provides an abstraction to iterate over elements maintained in the {@link Headers} collection. - */ - interface EntryVisitor extends Headers.EntryVisitor { - } - - /** - * Provides an abstraction to iterate over elements maintained in the {@link Headers} collection. - */ - interface NameVisitor extends Headers.NameVisitor { - } @Override BinaryHeaders add(ByteString name, ByteString value); diff --git a/codec/src/main/java/io/netty/handler/codec/ConvertibleHeaders.java b/codec/src/main/java/io/netty/handler/codec/ConvertibleHeaders.java index 6ef16561b5..ca3eae694a 100644 --- a/codec/src/main/java/io/netty/handler/codec/ConvertibleHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/ConvertibleHeaders.java @@ -89,13 +89,6 @@ public interface ConvertibleHeaders extends Head */ List 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> entriesConverted(); - /** * Invokes {@link Headers#iterator()} and lazily does a conversion on the results as they are accessed * diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java index ae1b32b507..2183e5d71d 100644 --- a/codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java @@ -20,128 +20,24 @@ import io.netty.util.internal.PlatformDependent; import java.nio.charset.Charset; import java.text.ParseException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; public class DefaultBinaryHeaders extends DefaultHeaders implements BinaryHeaders { - private static final ValueConverter OBJECT_TO_BYTE = new ValueConverter() { - private final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; - @Override - public ByteString convertObject(Object value) { - if (value instanceof ByteString) { - return (ByteString) value; - } - if (value instanceof CharSequence) { - return new ByteString((CharSequence) value, DEFAULT_CHARSET); - } - return new ByteString(value.toString(), DEFAULT_CHARSET); - } - @Override - public ByteString convertInt(int value) { - return new ByteString(String.valueOf(value), DEFAULT_CHARSET); - } - - @Override - public ByteString convertLong(long value) { - return new ByteString(String.valueOf(value), DEFAULT_CHARSET); - } - - @Override - public ByteString convertDouble(double value) { - return new ByteString(String.valueOf(value), DEFAULT_CHARSET); - } - - @Override - public ByteString convertChar(char value) { - return new ByteString(String.valueOf(value), DEFAULT_CHARSET); - } - - @Override - public ByteString convertBoolean(boolean value) { - return new ByteString(String.valueOf(value), DEFAULT_CHARSET); - } - - @Override - public ByteString convertFloat(float value) { - return new ByteString(String.valueOf(value), DEFAULT_CHARSET); - } - - @Override - public int convertToInt(ByteString value) { - return value.parseAsciiInt(); - } - - @Override - public long convertToLong(ByteString value) { - return value.parseAsciiLong(); - } - - @Override - public ByteString convertTimeMillis(long value) { - return new ByteString(String.valueOf(value), DEFAULT_CHARSET); - } - - @Override - public long convertToTimeMillis(ByteString value) { - try { - return HeaderDateFormat.get().parse(value.toString()); - } catch (ParseException e) { - PlatformDependent.throwException(e); - } - return 0; - } - - @Override - public double convertToDouble(ByteString value) { - return value.parseAsciiDouble(); - } - - @Override - public char convertToChar(ByteString value) { - return value.parseChar(); - } - - @Override - public boolean convertToBoolean(ByteString value) { - return value.byteAt(0) != 0; - } - - @Override - public float convertToFloat(ByteString value) { - return value.parseAsciiFloat(); - } - - @Override - public ByteString convertShort(short value) { - return new ByteString(String.valueOf(value), DEFAULT_CHARSET); - } - - @Override - public short convertToShort(ByteString value) { - return value.parseAsciiShort(); - } - - @Override - public ByteString convertByte(byte value) { - return new ByteString(String.valueOf(value), DEFAULT_CHARSET); - } - - @Override - public byte convertToByte(ByteString value) { - return value.byteAt(0); - } - }; - - private static final HashCodeGenerator JAVA_HASH_CODE_GENERATOR = - new JavaHashCodeGenerator(); - protected static final NameConverter IDENTITY_NAME_CONVERTER = new IdentityNameConverter(); + private static final NameValidator NO_NAME_VALIDATOR = DefaultHeaders.NoNameValidator.instance(); public DefaultBinaryHeaders() { - this(IDENTITY_NAME_CONVERTER); + this(new TreeMap(ByteString.DEFAULT_COMPARATOR)); } - public DefaultBinaryHeaders(NameConverter nameConverter) { - super(ByteString.DEFAULT_COMPARATOR, ByteString.DEFAULT_COMPARATOR, - JAVA_HASH_CODE_GENERATOR, OBJECT_TO_BYTE, nameConverter); + public DefaultBinaryHeaders(Map map) { + this(map, NO_NAME_VALIDATOR); + } + + public DefaultBinaryHeaders(Map map, NameValidator nameValidator) { + super(map, nameValidator, ByteStringConverter.INSTANCE); } @Override @@ -347,4 +243,119 @@ public class DefaultBinaryHeaders extends DefaultHeaders implements super.clear(); return this; } + + public static final class ByteStringConverter implements ValueConverter { + + 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); + } + } } diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultConvertibleHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultConvertibleHeaders.java index 7a18943d93..3c7863799d 100644 --- a/codec/src/main/java/io/netty/handler/codec/DefaultConvertibleHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/DefaultConvertibleHeaders.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; @@ -27,22 +28,11 @@ public class DefaultConvertibleHeaders extends D private final TypeConverter typeConverter; - public DefaultConvertibleHeaders(Comparator keyComparator, - Comparator valueComparator, - HashCodeGenerator hashCodeGenerator, - ValueConverter valueConverter, - TypeConverter typeConverter) { - super(keyComparator, valueComparator, hashCodeGenerator, valueConverter); - this.typeConverter = typeConverter; - } - - public DefaultConvertibleHeaders(Comparator keyComparator, - Comparator valueComparator, - HashCodeGenerator hashCodeGenerator, - ValueConverter valueConverter, - TypeConverter typeConverter, - NameConverter nameConverter) { - super(keyComparator, valueComparator, hashCodeGenerator, valueConverter, nameConverter); + public DefaultConvertibleHeaders(Map map, + NameValidator nameValidator, + ValueConverter valueConverter, + TypeConverter typeConverter) { + super(map, nameValidator, valueConverter); this.typeConverter = typeConverter; } @@ -94,17 +84,6 @@ public class DefaultConvertibleHeaders extends D return allConverted; } - @Override - public List> entriesConverted() { - List> entries = entries(); - List> entriesConverted = new ArrayList>( - entries.size()); - for (int i = 0; i < entries.size(); ++i) { - entriesConverted.add(new ConvertedEntry(entries.get(i))); - } - return entriesConverted; - } - @Override public Iterator> iteratorConverted() { return new ConvertedIterator(); diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java index 37b7ba45a8..6e08fcac90 100644 --- a/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java @@ -14,154 +14,59 @@ */ package io.netty.handler.codec; -import io.netty.util.collection.IntObjectHashMap; -import io.netty.util.collection.IntObjectMap; import io.netty.util.concurrent.FastThreadLocal; -import io.netty.util.internal.PlatformDependent; import java.text.DateFormat; import java.text.ParseException; -import java.text.ParsePosition; import java.text.SimpleDateFormat; +import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; -import java.util.NoSuchElementException; import java.util.Set; import java.util.TimeZone; -import java.util.TreeSet; +import java.util.Date; +import java.util.TreeMap; -import static io.netty.util.internal.ObjectUtil.*; +import static io.netty.util.internal.ObjectUtil.checkNotNull; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableSet; + +/** + * Default implementation of {@link Headers}; + * + * @param the type of the header name and value. + */ public class DefaultHeaders implements Headers { - /** - * Allows users of this interface to specify a hash code other than the default {@link Object#hashCode()} - */ - public interface HashCodeGenerator { - /** - * Obtain the hash code for the given {@code name} - * - * @param name The name to generate the hash code for - * @return The hash code for {@code name} - */ - int generateHashCode(T name); - } - /** - * Allows users to convert the {@code name} elements before being processed by this map - */ - public interface NameConverter { - /** - * Convert the {@code name} to some other form of the same object type - * - * @param name The object to convert - * @return The results of the conversion - */ - T convertName(T name); - } - - /** - * Uses the {@link #hashCode()} method to generate the hash code. - */ - public static final class JavaHashCodeGenerator implements HashCodeGenerator { - @Override - public int generateHashCode(T name) { - return name.hashCode(); - } - } - - /** - * A name converted which does not covert but instead just returns this {@code name} unchanged - */ - public static final class IdentityNameConverter implements NameConverter { - @Override - public T convertName(T name) { - return name; - } - } - - private static final int HASH_CODE_PRIME = 31; - private static final int DEFAULT_BUCKET_SIZE = 17; - private static final int DEFAULT_MAP_SIZE = 4; - private static final NameConverter DEFAULT_NAME_CONVERTER = new IdentityNameConverter(); - - private final IntObjectMap entries; - private final IntObjectMap tailEntries; - private final HeaderEntry head; - private final Comparator keyComparator; - private final Comparator valueComparator; - private final HashCodeGenerator hashCodeGenerator; + private final Map map; + private final NameValidator nameValidator; private final ValueConverter valueConverter; - private final NameConverter nameConverter; - private final int bucketSize; int size; - @SuppressWarnings("unchecked") - public DefaultHeaders(Comparator keyComparator, Comparator valueComparator, - HashCodeGenerator hashCodeGenerator, ValueConverter typeConverter) { - this(keyComparator, valueComparator, hashCodeGenerator, typeConverter, - (NameConverter) DEFAULT_NAME_CONVERTER); - } + boolean hasMultipleValues; - public DefaultHeaders(Comparator keyComparator, Comparator valueComparator, - HashCodeGenerator hashCodeGenerator, ValueConverter typeConverter, NameConverter nameConverter) { - this(keyComparator, valueComparator, hashCodeGenerator, typeConverter, nameConverter, DEFAULT_BUCKET_SIZE, - DEFAULT_MAP_SIZE); - } - - public DefaultHeaders(Comparator keyComparator, Comparator valueComparator, - HashCodeGenerator hashCodeGenerator, ValueConverter valueConverter, NameConverter nameConverter, - int bucketSize, int initialMapSize) { - if (keyComparator == null) { - throw new NullPointerException("keyComparator"); - } - if (valueComparator == null) { - throw new NullPointerException("valueComparator"); - } - if (hashCodeGenerator == null) { - throw new NullPointerException("hashCodeGenerator"); - } - if (valueConverter == null) { - throw new NullPointerException("valueConverter"); - } - if (nameConverter == null) { - throw new NullPointerException("nameConverter"); - } - if (bucketSize < 1) { - throw new IllegalArgumentException("bucketSize must be a positive integer"); - } - head = new HeaderEntry(); - head.before = head.after = head; - this.keyComparator = keyComparator; - this.valueComparator = valueComparator; - this.hashCodeGenerator = hashCodeGenerator; - this.valueConverter = valueConverter; - this.nameConverter = nameConverter; - this.bucketSize = bucketSize; - entries = new IntObjectHashMap(initialMapSize); - tailEntries = new IntObjectHashMap(initialMapSize); + public DefaultHeaders(Map map, NameValidator nameValidator, ValueConverter valueConverter) { + this.map = checkNotNull(map, "map"); + this.nameValidator = checkNotNull(nameValidator, "nameValidator"); + this.valueConverter = checkNotNull(valueConverter, "valueConverter"); } @Override + @SuppressWarnings("unchecked") public T get(T name) { - checkNotNull(name, "name"); - - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - HeaderEntry e = entries.get(i); - while (e != null) { - if (e.hash == h && keyComparator.compare(e.name, name) == 0) { - return e.value; - } - e = e.next; + Object value = map.get(name); + if (isList(value)) { + return ((List) value).get(0); } - return null; + return (T) value; } @Override @@ -174,56 +79,19 @@ public class DefaultHeaders implements Headers { } @Override + @SuppressWarnings("unchecked") public T getAndRemove(T name) { - checkNotNull(name, "name"); - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - HeaderEntry e = entries.get(i); - if (e == null) { + Object value = map.remove(name); + if (value == null) { return null; } - - T value = null; - for (;;) { - if (e.hash == h && keyComparator.compare(e.name, name) == 0) { - if (value == null) { - value = e.value; - } - e.remove(); - HeaderEntry next = e.next; - if (next != null) { - entries.put(i, next); - e = next; - } else { - entries.remove(i); - tailEntries.remove(i); - return value; - } - } else { - break; - } + if (isList(value)) { + List value0 = (List) value; + size -= value0.size(); + return value0.get(0); } - - for (;;) { - HeaderEntry next = e.next; - if (next == null) { - break; - } - if (next.hash == h && keyComparator.compare(e.name, name) == 0) { - if (value == null) { - value = next.value; - } - e.next = next.next; - if (e.next == null) { - tailEntries.put(i, e); - } - next.remove(); - } else { - e = next; - } - } - - return value; + size--; + return (T) value; } @Override @@ -236,84 +104,23 @@ public class DefaultHeaders implements Headers { } @Override + @SuppressWarnings("unchecked") public List getAll(T name) { - checkNotNull(name, "name"); - List values = new ArrayList(4); - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - HeaderEntry e = entries.get(i); - while (e != null) { - if (e.hash == h && keyComparator.compare(e.name, name) == 0) { - values.add(e.value); - } - e = e.next; + Object value = map.get(name); + if (isList(value)) { + return unmodifiableList((List) value); } - - return values; + if (value == null) { + return emptyList(); + } + return singletonList((T) value); } @Override public List getAllAndRemove(T name) { - checkNotNull(name, "name"); - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - HeaderEntry e = entries.get(i); - if (e == null) { - return null; - } - - List values = new ArrayList(4); - for (;;) { - if (e.hash == h && keyComparator.compare(e.name, name) == 0) { - values.add(e.value); - e.remove(); - HeaderEntry next = e.next; - if (next != null) { - entries.put(i, next); - e = next; - } else { - entries.remove(i); - tailEntries.remove(i); - return values; - } - } else { - break; - } - } - - for (;;) { - HeaderEntry next = e.next; - if (next == null) { - break; - } - if (next.hash == h && keyComparator.compare(next.name, name) == 0) { - values.add(next.value); - e.next = next.next; - if (e.next == null) { - tailEntries.put(i, e); - } - next.remove(); - } else { - e = next; - } - } - - return values; - } - - @Override - public List> entries() { - final int size = size(); - List> localEntries = new ArrayList>(size); - - HeaderEntry e = head.after; - while (e != head) { - localEntries.add(e); - e = e.after; - } - - assert size == localEntries.size(); - return localEntries; + List all = getAll(name); + remove(name); + return all; } @Override @@ -322,8 +129,13 @@ public class DefaultHeaders implements Headers { } @Override + @SuppressWarnings("unchecked") public boolean contains(T name, T value) { - return contains(name, value, keyComparator, valueComparator); + Object values = map.get(name); + if (isList(values)) { + return ((List) values).contains(value); + } + return values != null && values.equals(value); } @Override @@ -377,41 +189,19 @@ public class DefaultHeaders implements Headers { } @Override - public boolean contains(T name, T value, Comparator comparator) { - return contains(name, value, comparator, comparator); - } - - @Override - public boolean contains(T name, T value, - Comparator keyComparator, Comparator valueComparator) { - checkNotNull(name, "name"); - checkNotNull(value, "value"); - checkNotNull(keyComparator, "keyComparator"); - checkNotNull(valueComparator, "valueComparator"); - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - HeaderEntry e = entries.get(i); - while (e != null) { - if (e.hash == h && - keyComparator.compare(e.name, name) == 0 && - valueComparator.compare(e.value, value) == 0) { - return true; + @SuppressWarnings("unchecked") + public boolean contains(T name, T value, Comparator valueComparator) { + Object values = map.get(name); + if (isList(values)) { + List values0 = (List) values; + for (int i = 0; i < values0.size(); i++) { + if (valueComparator.compare(value, values0.get(i)) == 0) { + return true; + } } - e = e.next; + return false; } - return false; - } - - @Override - public boolean containsObject(T name, Object value, Comparator comparator) { - return containsObject(name, value, comparator, comparator); - } - - @Override - public boolean containsObject(T name, Object value, Comparator keyComparator, - Comparator valueComparator) { - return contains( - name, valueConverter.convertObject(checkNotNull(value, "value")), keyComparator, valueComparator); + return values != null && valueComparator.compare((T) values, value) == 0; } @Override @@ -421,73 +211,54 @@ public class DefaultHeaders implements Headers { @Override public boolean isEmpty() { - return head == head.after; + return map.isEmpty(); } @Override public Set names() { - final Set names = new TreeSet(keyComparator); - - HeaderEntry e = head.after; - while (e != head) { - names.add(e.name); - e = e.after; - } - - return names; - } - - @Override - public List namesList() { - final List names = new ArrayList(size()); - - HeaderEntry e = head.after; - while (e != head) { - names.add(e.name); - e = e.after; - } - - return names; + return unmodifiableSet(map.keySet()); } @Override public Headers add(T name, T value) { - name = convertName(name); + validateName(name); checkNotNull(value, "value"); - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - add0(h, i, name, value); + Object prevValue = map.put(name, value); + size++; + if (prevValue != null) { + appendValue(name, value, prevValue); + } return this; } + @SuppressWarnings("unchecked") + private void appendValue(T name, T value, Object prevValue) { + hasMultipleValues = true; + if (isList(prevValue)) { + ((List) prevValue).add(value); + map.put(name, prevValue); + } else { + List values = newList(); + values.add((T) prevValue); + values.add(value); + map.put(name, values); + } + } + @Override public Headers add(T name, Iterable values) { - name = convertName(name); checkNotNull(values, "values"); - - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - for (T v : values) { - if (v == null) { - break; - } - add0(h, i, name, v); + for (T value : values) { + add(name, value); } return this; } @Override public Headers add(T name, T... values) { - name = convertName(name); checkNotNull(values, "values"); - - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - for (T v : values) { - if (v == null) { - break; - } - add0(h, i, name, v); + for (int i = 0; i < values.length; i++) { + add(name, values[i]); } return this; } @@ -499,36 +270,18 @@ public class DefaultHeaders implements Headers { @Override public Headers addObject(T name, Iterable values) { - name = convertName(name); checkNotNull(values, "values"); - - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - for (Object o : values) { - if (o == null) { - break; - } - T converted = valueConverter.convertObject(o); - checkNotNull(converted, "converted"); - add0(h, i, name, converted); + for (Object value : values) { + addObject(name, value); } return this; } @Override public Headers addObject(T name, Object... values) { - name = convertName(name); checkNotNull(values, "values"); - - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - for (Object o : values) { - if (o == null) { - break; - } - T converted = valueConverter.convertObject(o); - checkNotNull(converted, "converted"); - add0(h, i, name, converted); + for (int i = 0; i < values.length; i++) { + addObject(name, values[i]); } return this; } @@ -579,102 +332,90 @@ public class DefaultHeaders implements Headers { } @Override - public Headers add(Headers headers) { + public Headers add(Headers headers) { checkNotNull(headers, "headers"); - - add0(headers); + if (headers == this) { + throw new IllegalArgumentException("can't add to itself."); + } + for (Entry header : headers) { + add(header.getKey(), header.getValue()); + } return this; } @Override public Headers set(T name, T value) { - name = convertName(name); + validateName(name); checkNotNull(value, "value"); - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - remove0(h, i, name); - add0(h, i, name, value); + Object oldValue = map.put(name, value); + updateSizeAfterSet(oldValue, 1); return this; } @Override public Headers set(T name, Iterable values) { - name = convertName(name); + validateName(name); checkNotNull(values, "values"); - - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - remove0(h, i, name); - for (T v : values) { - if (v == null) { - break; - } - add0(h, i, name, v); + List list = newList(); + for (T value : values) { + list.add(checkNotNull(value, "value")); } - + Object oldValue = map.put(name, list); + updateSizeAfterSet(oldValue, list.size()); + hasMultipleValues = true; return this; } @Override public Headers set(T name, T... values) { - name = convertName(name); + validateName(name); checkNotNull(values, "values"); - - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - remove0(h, i, name); - for (T v : values) { - if (v == null) { - break; - } - add0(h, i, name, v); + List list = newList(values.length); + for (int i = 0; i < values.length; i++) { + list.add(checkNotNull(values[i], "value")); } - + Object oldValue = map.put(name, list); + updateSizeAfterSet(oldValue, values.length); + hasMultipleValues = true; return this; } @Override public Headers setObject(T name, Object value) { - return set(name, valueConverter.convertObject(checkNotNull(value, "value"))); + checkNotNull(value, "value"); + T convertedValue = checkNotNull(valueConverter.convertObject(value), "convertedValue"); + return set(name, convertedValue); } @Override public Headers setObject(T name, Iterable values) { - name = convertName(name); + validateName(name); checkNotNull(values, "values"); - - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - remove0(h, i, name); - for (Object o : values) { - if (o == null) { - break; - } - T converted = valueConverter.convertObject(o); - checkNotNull(converted, "converted"); - add0(h, i, name, converted); + List list = newList(); + for (Object value : values) { + value = checkNotNull(value, "value"); + T convertedValue = checkNotNull(valueConverter.convertObject(value), "convertedValue"); + list.add(convertedValue); } - + Object oldValue = map.put(name, list); + updateSizeAfterSet(oldValue, list.size()); + hasMultipleValues = true; return this; } @Override public Headers setObject(T name, Object... values) { - name = convertName(name); + validateName(name); checkNotNull(values, "values"); - - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - remove0(h, i, name); - for (Object o : values) { - if (o == null) { - break; - } - T converted = valueConverter.convertObject(o); - checkNotNull(converted, "converted"); - add0(h, i, name, converted); + List list = newList(values.length); + for (int i = 0; i < values.length; i++) { + Object value = checkNotNull(values[i], "value"); + T convertedValue = checkNotNull(valueConverter.convertObject(value), "convertedValue"); + list.add(convertedValue); } - + Object oldValue = map.put(name, list); + updateSizeAfterSet(oldValue, list.size()); + hasMultipleValues = true; return this; } @@ -724,80 +465,50 @@ public class DefaultHeaders implements Headers { } @Override - public Headers set(Headers headers) { + public Headers set(Headers headers) { checkNotNull(headers, "headers"); - + if (headers == this) { + return this; + } clear(); - add0(headers); + add(headers); return this; } @Override - public Headers setAll(Headers headers) { + public Headers setAll(Headers headers) { checkNotNull(headers, "headers"); - - if (headers instanceof DefaultHeaders) { - DefaultHeaders m = (DefaultHeaders) headers; - HeaderEntry e = m.head.after; - while (e != m.head) { - set(e.name, e.value); - e = e.after; - } - } else { - try { - headers.forEachEntry(setAllVisitor()); - } catch (Exception ex) { - PlatformDependent.throwException(ex); - } + if (headers == this) { + return this; + } + for (T key : headers.names()) { + remove(key); + } + for (Entry entry : headers) { + add(entry.getKey(), entry.getValue()); } - return this; } @Override public boolean remove(T name) { - checkNotNull(name, "name"); - int h = hashCodeGenerator.generateHashCode(name); - int i = index(h); - return remove0(h, i, name); + return getAndRemove(name) != null; } @Override public Headers clear() { - entries.clear(); - tailEntries.clear(); - head.before = head.after = head; + map.clear(); + hasMultipleValues = false; size = 0; return this; } @Override + @SuppressWarnings("unchecked") public Iterator> iterator() { - return new KeyValueHeaderIterator(); - } - - @Override - public Map.Entry forEachEntry(EntryVisitor visitor) throws Exception { - HeaderEntry e = head.after; - while (e != head) { - if (!visitor.visit(e)) { - return e; - } - e = e.after; - } - return null; - } - - @Override - public T forEachName(NameVisitor visitor) throws Exception { - HeaderEntry e = head.after; - while (e != head) { - if (!visitor.visit(e.name)) { - return e.name; - } - e = e.after; - } - return null; + Object iter = map.entrySet().iterator(); + return !hasMultipleValues ? (Iterator>) iter + : new NameValueIterator((Iterator>) iter); } @Override @@ -871,7 +582,7 @@ public class DefaultHeaders implements Headers { } @Override - public short getInt(T name, short defaultValue) { + public short getShort(T name, short defaultValue) { Short v = getShort(name); return v == null ? defaultValue : v; } @@ -1143,85 +854,43 @@ public class DefaultHeaders implements Headers { } @Override + @SuppressWarnings("unchecked") public boolean equals(Object o) { if (!(o instanceof DefaultHeaders)) { return false; } - - @SuppressWarnings("unchecked") - DefaultHeaders h2 = (DefaultHeaders) o; - // First, check that the set of names match. Don't use a TreeSet for comparison - // because we want to force the keyComparator to be used for all comparisons - List namesList = namesList(); - List otherNamesList = h2.namesList(); - if (!equals(namesList, otherNamesList, keyComparator)) { - return false; - } - - // Compare the values for each name. Don't use a TreeSet for comparison - // because we want to force the valueComparator to be used for all comparisons - Set names = new TreeSet(keyComparator); - names.addAll(namesList); - for (T name : names) { - if (!equals(getAll(name), h2.getAll(name), valueComparator)) { - return false; - } - } - - return true; + DefaultHeaders other = (DefaultHeaders) o; + return size() == other.size() && map.equals(other.map); } /** - * Compare two lists using the {@code comparator} for all comparisons (not using the equals() operator) - * @param lhs Left hand side - * @param rhs Right hand side - * @param comparator Comparator which will be used for all comparisons (equals() on objects will not be used) - * @return True if {@code lhs} == {@code rhs} according to {@code comparator}. False otherwise. + * This method is purposefully kept simple and returns {@link #size()} as the hash code. + * There are two compelling reasons for keeping {@link #hashCode()} simple: + * 1) It's difficult to get it right as the hash code mostly depends on the {@link Map} + * implementation. Simply using {@link Map#hashCode()} doesn't work as for + * example {@link TreeMap#hashCode()} does not fulfill the contract between {@link Object#hashCode()} and + * {@link Object#equals(Object)} when it's used with a {@link Comparator} that is not consistent with + * {@link Object#equals(Object)}. + * 2) The {@link #hashCode()} function doesn't appear to be important for {@link Headers} implementations. It's + * solely there because we override {@link Object#equals(Object)}, which makes it easier to use {@link Headers} + * in tests, but also has little practical use outside of tests. */ - private static boolean equals(List lhs, List rhs, Comparator comparator) { - final int lhsSize = lhs.size(); - if (lhsSize != rhs.size()) { - return false; - } - - // Don't use a TreeSet to do the comparison. We want to force the comparator - // to be used instead of the object's equals() - Collections.sort(lhs, comparator); - Collections.sort(rhs, comparator); - for (int i = 0; i < lhsSize; ++i) { - if (comparator.compare(lhs.get(i), rhs.get(i)) != 0) { - return false; - } - } - return true; - } - @Override public int hashCode() { - int result = 1; - for (T name : names()) { - result = HASH_CODE_PRIME * result + name.hashCode(); - List values = getAll(name); - Collections.sort(values, valueComparator); - for (int i = 0; i < values.size(); ++i) { - result = HASH_CODE_PRIME * result + hashCodeGenerator.generateHashCode(values.get(i)); - } - } - return result; + return size(); } @Override public String toString() { StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('['); + String separator = ""; for (T name : names()) { List values = getAll(name); - Collections.sort(values, valueComparator); for (int i = 0; i < values.size(); ++i) { - builder.append(name).append(": ").append(values.get(i)).append(", "); + builder.append(separator); + builder.append(name).append(": ").append(values.get(i)); } - } - if (builder.length() > 2) { // remove the trailing ", " - builder.setLength(builder.length() - 2); + separator = ", "; } return builder.append(']').toString(); } @@ -1230,200 +899,75 @@ public class DefaultHeaders implements Headers { return valueConverter; } - private T convertName(T name) { - return nameConverter.convertName(checkNotNull(name, "name")); + @SuppressWarnings("unchecked") + private void updateSizeAfterSet(Object oldValue, int newSize) { + if (isList(oldValue)) { + size -= ((List) oldValue).size(); + } else if (oldValue != null) { + size--; + } + size += newSize; } - private int index(int hash) { - return Math.abs(hash % bucketSize); + private void validateName(T name) { + checkNotNull(name, "name"); + nameValidator.validate(name); } - private void add0(Headers headers) { - if (headers.isEmpty()) { - return; - } - - if (headers instanceof DefaultHeaders) { - DefaultHeaders m = (DefaultHeaders) headers; - HeaderEntry e = m.head.after; - while (e != m.head) { - add(e.name, e.value); - e = e.after; - } - } else { - try { - headers.forEachEntry(addAllVisitor()); - } catch (Exception ex) { - PlatformDependent.throwException(ex); - } - } + private static boolean isList(Object value) { + return value != null && value.getClass().equals(ValuesList.class); } - private void add0(int h, int i, T name, T value) { - // Update the per-bucket hash table linked list - HeaderEntry newEntry = new HeaderEntry(h, name, value); - HeaderEntry oldTail = tailEntries.get(i); - if (oldTail == null) { - entries.put(i, newEntry); - } else { - oldTail.next = newEntry; - } - tailEntries.put(i, newEntry); - - // Update the overall insertion order linked list - newEntry.addBefore(head); + private List newList() { + return newList(4); } - private boolean remove0(int h, int i, T name) { - HeaderEntry e = entries.get(i); - if (e == null) { - return false; - } - - boolean removed = false; - for (;;) { - if (e.hash == h && keyComparator.compare(e.name, name) == 0) { - e.remove(); - HeaderEntry next = e.next; - if (next != null) { - entries.put(i, next); - e = next; - } else { - entries.remove(i); - tailEntries.remove(i); - return true; - } - removed = true; - } else { - break; - } - } - - for (;;) { - HeaderEntry next = e.next; - if (next == null) { - break; - } - if (next.hash == h && keyComparator.compare(next.name, name) == 0) { - e.next = next.next; - if (e.next == null) { - tailEntries.put(i, e); - } - next.remove(); - removed = true; - } else { - e = next; - } - } - - return removed; + private List newList(int initialSize) { + return new ValuesList(initialSize); } - private EntryVisitor setAllVisitor() { - return new EntryVisitor() { - @Override - public boolean visit(Entry entry) { - set(entry.getKey(), entry.getValue()); - return true; - } - }; - } + private final class NameValueIterator implements Iterator> { + private final Iterator> iter; + private T name; + private List values; + private int valuesIdx; - private EntryVisitor addAllVisitor() { - return new EntryVisitor() { - @Override - public boolean visit(Entry entry) { - add(entry.getKey(), entry.getValue()); - return true; - } - }; - } - - private final class HeaderEntry implements Map.Entry { - final int hash; - final T name; - T value; - /** - * In bucket linked list - */ - HeaderEntry next; - /** - * Overall insertion order linked list - */ - HeaderEntry before, after; - - HeaderEntry(int hash, T name, T value) { - this.hash = hash; - this.name = name; - this.value = value; + NameValueIterator(Iterator> iter) { + this.iter = iter; } - HeaderEntry() { - hash = -1; - name = null; - value = null; - } - - void remove() { - before.after = after; - after.before = before; - --size; - } - - void addBefore(HeaderEntry e) { - after = e; - before = e.before; - before.after = this; - after.before = this; - ++size; - } - - @Override - public T getKey() { - return name; - } - - @Override - public T getValue() { - return value; - } - - @Override - public T setValue(T value) { - checkNotNull(value, "value"); - T oldValue = this.value; - this.value = value; - return oldValue; - } - - @Override - public String toString() { - return new StringBuilder() - .append(name) - .append('=') - .append(value) - .toString(); - } - } - - protected final class KeyValueHeaderIterator implements Iterator> { - - private HeaderEntry current = head; - @Override public boolean hasNext() { - return current.after != head; + return iter.hasNext() || values != null; } @Override + @SuppressWarnings("unchecked") public Entry next() { - current = current.after; - - if (current == head) { - throw new NoSuchElementException(); + if (name == null) { + Entry entry = iter.next(); + if (!isList(entry.getValue())) { + return (Entry) entry; + } + initListIterator(entry); } + return fetchNextEntryFromList(); + } - return current; + @SuppressWarnings("unchecked") + private void initListIterator(Entry entry) { + name = entry.getKey(); + values = (List) entry.getValue(); + valuesIdx = 0; + } + + private Entry fetchNextEntryFromList() { + Entry next = new SimpleListEntry(name, values, valuesIdx++); + if (values.size() == valuesIdx) { + name = null; + values = null; + } + return next; } @Override @@ -1432,8 +976,36 @@ public class DefaultHeaders implements Headers { } } + private static class SimpleListEntry extends SimpleEntry { + private static final long serialVersionUID = 542062057061955051L; + private final List values; + private final int idx; + + SimpleListEntry(T name, List values, int idx) { + super(name, values.get(idx)); + this.values = values; + this.idx = idx; + } + + @Override + public T setValue(T value) { + T oldValue = super.setValue(value); + values.set(idx, value); + return oldValue; + } + } + + private static final class ValuesList extends ArrayList { + private static final long serialVersionUID = -2434101246756843900L; + + ValuesList(int initialSize) { + super(initialSize); + } + } + /** - * This DateFormat decodes 3 formats of {@link java.util.Date}, but only encodes the one, the first: + * This {@link DateFormat} decodes 3 formats of {@link Date}. + * *
    *
  • Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with valid generation
  • *
  • Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification
  • @@ -1441,14 +1013,13 @@ public class DefaultHeaders implements Headers { *
*/ static final class HeaderDateFormat { - private static final ParsePosition parsePos = new ParsePosition(0); private static final FastThreadLocal dateFormatThreadLocal = new FastThreadLocal() { - @Override - protected HeaderDateFormat initialValue() { - return new HeaderDateFormat(); - } - }; + @Override + protected HeaderDateFormat initialValue() { + return new HeaderDateFormat(); + } + }; static HeaderDateFormat get() { return dateFormatThreadLocal.get(); @@ -1462,6 +1033,7 @@ public class DefaultHeaders implements Headers { * */ private final DateFormat dateFormat1 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH); + /** * First obsolete format: * @@ -1470,6 +1042,7 @@ public class DefaultHeaders implements Headers { * */ private final DateFormat dateFormat2 = new SimpleDateFormat("E, dd-MMM-yy HH:mm:ss z", Locale.ENGLISH); + /** * Second obsolete format * @@ -1487,31 +1060,58 @@ public class DefaultHeaders implements Headers { } long parse(String text) throws ParseException { - Date date = dateFormat1.parse(text, parsePos); + Date date = dateFormat1.parse(text); if (date == null) { - date = dateFormat2.parse(text, parsePos); + date = dateFormat2.parse(text); } if (date == null) { - date = dateFormat3.parse(text, parsePos); + date = dateFormat3.parse(text); } if (date == null) { throw new ParseException(text, 0); } return date.getTime(); } + } - long parse(String text, long defaultValue) { - Date date = dateFormat1.parse(text, parsePos); - if (date == null) { - date = dateFormat2.parse(text, parsePos); - } - if (date == null) { - date = dateFormat3.parse(text, parsePos); - } - if (date == null) { - return defaultValue; - } - return date.getTime(); + public interface NameValidator { + void validate(T name); + } + + public static final class NoNameValidator implements NameValidator { + + @SuppressWarnings("rawtypes") + private static final NameValidator INSTANCE = new NoNameValidator(); + + private NoNameValidator() { + } + + @SuppressWarnings("unchecked") + public static NameValidator instance() { + return (NameValidator) INSTANCE; + } + + @Override + public void validate(T name) { } } + + public static boolean comparatorEquals(Headers a, Headers b, Comparator valueComparator) { + if (a.size() != b.size()) { + return false; + } + for (V name : a.names()) { + List otherValues = b.getAll(name); + List values = a.getAll(name); + if (otherValues.size() != values.size()) { + return false; + } + for (int i = 0; i < otherValues.size(); i++) { + if (valueComparator.compare(otherValues.get(i), values.get(i)) != 0) { + return false; + } + } + } + return true; + } } diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java index 2f1d75ed39..633488cb90 100644 --- a/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java @@ -24,184 +24,37 @@ import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import java.text.ParseException; -import java.util.Comparator; import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; public class DefaultTextHeaders extends DefaultConvertibleHeaders implements TextHeaders { - private static final HashCodeGenerator CHARSEQUECE_CASE_INSENSITIVE_HASH_CODE_GENERATOR = - new HashCodeGenerator() { - @Override - public int generateHashCode(CharSequence name) { - return AsciiString.caseInsensitiveHashCode(name); - } - }; - private static final HashCodeGenerator CHARSEQUECE_CASE_SENSITIVE_HASH_CODE_GENERATOR = - new JavaHashCodeGenerator(); - - public static class DefaultTextValueTypeConverter implements ValueConverter { - @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_FROM_OBJECT_CONVERTER = - new DefaultTextValueTypeConverter(); - private static final TypeConverter CHARSEQUENCE_TO_STRING_CONVERTER = - new TypeConverter() { - @Override - public String toConvertedType(CharSequence value) { - return value.toString(); - } - - @Override - public CharSequence toUnconvertedType(String value) { - return value; - } - }; - - private static final NameConverter CHARSEQUENCE_IDENTITY_CONVERTER = - new IdentityNameConverter(); - /** - * An estimate of the size of a header value. - */ - private static final int DEFAULT_VALUE_SIZE = 10; + public static final NameValidator NO_NAME_VALIDATOR = NoNameValidator.instance(); private final ValuesComposer valuesComposer; public DefaultTextHeaders() { - this(true); + this(false); } - public DefaultTextHeaders(boolean ignoreCase) { - this(ignoreCase, CHARSEQUENCE_FROM_OBJECT_CONVERTER, CHARSEQUENCE_IDENTITY_CONVERTER, false); + public DefaultTextHeaders(boolean singleHeaderFields) { + this(new TreeMap(CHARSEQUENCE_CASE_INSENSITIVE_ORDER), NO_NAME_VALIDATOR, + CharSequenceConverter.INSTANCE, singleHeaderFields); } - public DefaultTextHeaders(boolean ignoreCase, boolean singleHeaderFields) { - this(ignoreCase, CHARSEQUENCE_FROM_OBJECT_CONVERTER, CHARSEQUENCE_IDENTITY_CONVERTER, singleHeaderFields); - } - - protected DefaultTextHeaders(boolean ignoreCase, Headers.ValueConverter valueConverter, - NameConverter nameConverter) { - this(ignoreCase, valueConverter, nameConverter, false); - } - - public DefaultTextHeaders(boolean ignoreCase, ValueConverter valueConverter, - NameConverter nameConverter, boolean singleHeaderFields) { - super(comparator(ignoreCase), comparator(ignoreCase), - ignoreCase ? CHARSEQUECE_CASE_INSENSITIVE_HASH_CODE_GENERATOR - : CHARSEQUECE_CASE_SENSITIVE_HASH_CODE_GENERATOR, valueConverter, - CHARSEQUENCE_TO_STRING_CONVERTER, nameConverter); + public DefaultTextHeaders(Map map, + NameValidator nameValidator, + ValueConverter valueConverter, + boolean singleHeaderFields) { + super(map, nameValidator, valueConverter, CharSequenceToStringConverter.INSTANCE); valuesComposer = singleHeaderFields ? new SingleHeaderValuesComposer() : new MultipleFieldsValueComposer(); } @Override public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { - return contains(name, value, comparator(ignoreCase)); - } - - @Override - public boolean containsObject(CharSequence name, Object value, boolean ignoreCase) { - return containsObject(name, value, comparator(ignoreCase)); + return contains(name, value, + ignoreCase ? CHARSEQUENCE_CASE_INSENSITIVE_ORDER : CHARSEQUENCE_CASE_SENSITIVE_ORDER); } @Override @@ -398,10 +251,6 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders comparator(boolean ignoreCase) { - return ignoreCase ? CHARSEQUENCE_CASE_INSENSITIVE_ORDER : CHARSEQUENCE_CASE_SENSITIVE_ORDER; - } - /* * This interface enables different implementations for adding/setting header values. * Concrete implementations can control how values are added, for example to add all @@ -413,6 +262,7 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders values); + TextHeaders addObject(CharSequence name, Object value); TextHeaders addObject(CharSequence name, Iterable values); TextHeaders addObject(CharSequence name, Object... values); @@ -446,6 +296,12 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders values) { DefaultTextHeaders.super.addObject(name, values); @@ -534,6 +390,11 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders values) { return addEscapedValue(name, commaSeparate(objectEscaper(), values)); @@ -579,7 +440,8 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders CharSequence commaSeparate(CsvValueEscaper escaper, T... values) { - StringBuilder sb = new StringBuilder(values.length * DEFAULT_VALUE_SIZE); + final int lengthEstimate = 10; + StringBuilder sb = new StringBuilder(values.length * lengthEstimate); if (values.length > 0) { int end = values.length - 1; for (int i = 0; i < end; i++) { @@ -625,4 +487,130 @@ public class DefaultTextHeaders extends DefaultConvertibleHeaders { + + 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 { + + 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()); + } + } } diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java index 9a4ce83876..ea3b077bbe 100644 --- a/codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java @@ -54,14 +54,10 @@ public class EmptyConvertibleHeaders extends return Collections.emptyList(); } - @Override - public List> entriesConverted() { - return Collections.emptyList(); - } - @Override public Iterator> iteratorConverted() { - return entriesConverted().iterator(); + List> empty = Collections.emptyList(); + return empty.iterator(); } @Override diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyHeaders.java index 48c5e52c3f..47e9c5e266 100644 --- a/codec/src/main/java/io/netty/handler/codec/EmptyHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/EmptyHeaders.java @@ -88,7 +88,7 @@ public class EmptyHeaders implements Headers { } @Override - public short getInt(T name, short defaultValue) { + public short getShort(T name, short defaultValue) { return defaultValue; } @@ -232,11 +232,6 @@ public class EmptyHeaders implements Headers { return defaultValue; } - @Override - public List> entries() { - return Collections.emptyList(); - } - @Override public boolean contains(T name) { return false; @@ -298,24 +293,7 @@ public class EmptyHeaders implements Headers { } @Override - public boolean contains(T name, T value, Comparator comparator) { - return false; - } - - @Override - public boolean contains(T name, T value, - Comparator keyComparator, Comparator valueComparator) { - return false; - } - - @Override - public boolean containsObject(T name, Object value, Comparator comparator) { - return false; - } - - @Override - public boolean containsObject(T name, Object value, Comparator keyComparator, - Comparator valueComparator) { + public boolean contains(T name, T value, Comparator valueComparator) { return false; } @@ -334,11 +312,6 @@ public class EmptyHeaders implements Headers { return Collections.emptySet(); } - @Override - public List namesList() { - return Collections.emptyList(); - } - @Override public Headers add(T name, T value) { throw new UnsupportedOperationException("read only"); @@ -415,7 +388,7 @@ public class EmptyHeaders implements Headers { } @Override - public Headers add(Headers headers) { + public Headers add(Headers headers) { throw new UnsupportedOperationException("read only"); } @@ -495,12 +468,12 @@ public class EmptyHeaders implements Headers { } @Override - public Headers set(Headers headers) { + public Headers set(Headers headers) { throw new UnsupportedOperationException("read only"); } @Override - public Headers setAll(Headers headers) { + public Headers setAll(Headers headers) { throw new UnsupportedOperationException("read only"); } @@ -516,17 +489,8 @@ public class EmptyHeaders implements Headers { @Override public Iterator> iterator() { - return entries().iterator(); - } - - @Override - public Entry forEachEntry(Headers.EntryVisitor visitor) throws Exception { - return null; - } - - @Override - public T forEachName(Headers.NameVisitor visitor) throws Exception { - return null; + List> empty = Collections.emptyList(); + return empty.iterator(); } @Override diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java index a221086860..d9db4e31cf 100644 --- a/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java @@ -25,11 +25,6 @@ public class EmptyTextHeaders extends EmptyConvertibleHeaders extends Iterable> { - /** - * Provides an abstraction to iterate over elements maintained in the {@link Headers} collection. - */ - interface EntryVisitor { - /** - * @return
    - *
  • {@code true} if the processor wants to continue the loop and handle the entry.
  • - *
  • {@code false} if the processor wants to stop handling headers and abort the loop.
  • - *
- */ - boolean visit(Map.Entry entry) throws Exception; - } +public interface Headers extends Iterable> { /** - * Provides an abstraction to iterate over elements maintained in the {@link Headers} collection. - */ - interface NameVisitor { - /** - * @return
    - *
  • {@code true} if the processor wants to continue the loop and handle the entry.
  • - *
  • {@code false} if the processor wants to stop handling headers and abort the loop.
  • - *
- */ - boolean visit(T name) throws Exception; - } - - /** - * Converts to/from a generic object to the type of the name for this map + * Converts to/from a generic object to the type of the headers. */ interface ValueConverter { T convertObject(Object value); @@ -92,458 +66,469 @@ public interface Headers extends Iterable> { } /** - * Returns the value of a header with the specified name. If there are more than one values for the specified name, - * the first value is returned. + * Returns the value of a header with the specified name. If there is more than one value for the specified name, + * the first value in insertion order is returned. * - * @param name the name of the header to search - * @return the first header value if the header is found. {@code null} if there's no such header. + * @param name the name of the header to retrieve + * @return the first header value if the header is found. {@code null} if there's no such header */ T get(T name); /** - * Returns the value of a header with the specified name. If there are more than one values for the specified name, - * the first value is returned. + * Returns the value of a header with the specified name. If there is more than one value for the specified name, + * the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @param defaultValue the default value - * @return the first header value if the header is found. {@code defaultValue} if there's no such header. + * @return the first header value or {@code defaultValue} if there is no such header */ T get(T name, T defaultValue); /** - * Returns and removes the value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the value of a header with the specified name and removes it from this object. If there is more than + * one value for the specified name, the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @return the first header value or {@code null} if there is no such header */ T getAndRemove(T name); /** - * Returns and removes the value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the value of a header with the specified name and removes it from this object. If there is more than + * one value for the specified name, the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @param defaultValue the default value * @return the first header value or {@code defaultValue} if there is no such header */ T getAndRemove(T name, T defaultValue); /** - * Returns the values of headers with the specified name + * Returns all values for the header with the specified name. The returned {@link List} can't be modified. * - * @param name The name of the headers to search - * @return A {@link List} of header values which will be empty if no values are found + * @param name the name of the header to retrieve + * @return a {@link List} of header values or an empty {@link List} if no values are found. */ List getAll(T name); /** - * Returns and Removes the values of headers with the specified name + * Returns all values for the header with the specified name and removes them from this object. + * The returned {@link List} can't be modified. * - * @param name The name of the headers to search - * @return A {@link List} of header values which will be empty if no values are found + * @param name the name of the header to retrieve + * @return a {@link List} of header values or an empty {@link List} if no values are found. */ List getAllAndRemove(T name); /** - * Returns the boolean value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the {@code boolean} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is a boolean. {@code null} if there's no such - * header or its value is not a boolean. + * @param name the name of the header to retrieve + * @return the {@code boolean} value of the first value in insertion order or {@code null} if there is no such + * value or it can't be converted to {@code boolean}. */ Boolean getBoolean(T name); /** - * Returns the boolean value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the {@code boolean} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @param defaultValue the default value - * @return the first header value if the header is found and its value is a boolean. {@code defaultValue} if there's - * no such header or its value is not a boolean. + * @return the {@code boolean} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code boolean}. */ boolean getBoolean(T name, boolean defaultValue); /** - * Returns the byte value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code byte} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is a byte. {@code null} if there's no such - * header or its value is not a byte. + * @param name the name of the header to retrieve + * @return the {@code byte} value of the first value in insertion order or {@code null} if there is no such + * value or it can't be converted to {@code byte}. */ Byte getByte(T name); /** - * Returns the byte value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code byte} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @param defaultValue the default value - * @return the first header value if the header is found and its value is a byte. {@code defaultValue} if there's no - * such header or its value is not a byte. + * @return the {@code byte} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code byte}. */ byte getByte(T name, byte defaultValue); /** - * Returns the char value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code char} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is a char. {@code null} if there's no such - * header or its value is not a char. + * @param name the name of the header to retrieve + * @return the {@code char} value of the first value in insertion order or {@code null} if there is no such + * value or it can't be converted to {@code char}. */ Character getChar(T name); /** - * Returns the char value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code char} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @param defaultValue the default value - * @return the first header value if the header is found and its value is a char. {@code defaultValue} if there's no - * such header or its value is not a char. + * @return the {@code char} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code char}. */ char getChar(T name, char defaultValue); /** - * Returns the short value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code short} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is a short. {@code null} if there's no such - * header or its value is not a short. + * @param name the name of the header to retrieve + * @return the {@code short} value of the first value in insertion order or {@code null} if there is no such + * value or it can't be converted to {@code short}. */ Short getShort(T name); /** - * Returns the short value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code short} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @param defaultValue the default value - * @return the first header value if the header is found and its value is a short. {@code defaultValue} if there's - * no such header or its value is not a short. + * @return the {@code short} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code short}. */ - short getInt(T name, short defaultValue); + short getShort(T name, short defaultValue); /** - * Returns the integer value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the {@code int} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is an integer. {@code null} if there's no - * such header or its value is not an integer. + * @param name the name of the header to retrieve + * @return the {@code int} value of the first value in insertion order or {@code null} if there is no such + * value or it can't be converted to {@code int}. */ Integer getInt(T name); /** - * Returns the integer value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the {@code int} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @param defaultValue the default value - * @return the first header value if the header is found and its value is an integer. {@code defaultValue} if - * there's no such header or its value is not an integer. + * @return the {@code int} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code int}. */ int getInt(T name, int defaultValue); /** - * Returns the long value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code long} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is a long. {@code null} if there's no such - * header or its value is not a long. + * @param name the name of the header to retrieve + * @return the {@code long} value of the first value in insertion order or {@code null} if there is no such + * value or it can't be converted to {@code long}. */ Long getLong(T name); /** - * Returns the long value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code long} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @param defaultValue the default value - * @return the first header value if the header is found and its value is a long. {@code defaultValue} if there's no - * such header or its value is not a long. + * @return the {@code long} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code long}. */ long getLong(T name, long defaultValue); /** - * Returns the float value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code float} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is a float. {@code null} if there's no such - * header or its value is not a float. + * @param name the name of the header to retrieve + * @return the {@code float} value of the first value in insertion order or {@code null} if there is no such + * value or it can't be converted to {@code float}. */ Float getFloat(T name); /** - * Returns the float value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code float} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @param defaultValue the default value - * @return the first header value if the header is found and its value is a float. {@code defaultValue} if there's - * no such header or its value is not a float. + * @return the {@code float} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code float}. */ float getFloat(T name, float defaultValue); /** - * Returns the double value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code double} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is a double. {@code null} if there's no such - * header or its value is not a double. + * @param name the name of the header to retrieve + * @return the {@code double} value of the first value in insertion order or {@code null} if there is no such + * value or it can't be converted to {@code double}. */ Double getDouble(T name); /** - * Returns the double value of a header with the specified name. If there are more than one values for the specified - * name, the first value is returned. + * Returns the {@code double} value of a header with the specified name. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name the name of the header to search + * @param name the name of the header to retrieve * @param defaultValue the default value - * @return the first header value if the header is found and its value is a double. {@code defaultValue} if there's - * no such header or its value is not a double. + * @return the {@code double} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code double}. */ double getDouble(T name, double defaultValue); /** - * Returns the date value of a header with the specified name as milliseconds. If there are more than one values for - * the specified name, the first value is returned. + * Returns the value of a header with the specified name in milliseconds. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name The name of the header to search - * @return the first header value in milliseconds if the header is found and its value is a date. {@code null} if - * there's no such header or its value is not a date. + * @param name the name of the header to retrieve + * @return the milliseconds value of the first value in insertion order or {@code null} if there is no such + * value or it can't be converted to milliseconds. */ Long getTimeMillis(T name); /** - * Returns the date value of a header with the specified name as milliseconds. If there are more than one values for - * the specified name, the first value is returned. + * Returns the value of a header with the specified name in milliseconds. If there is more than one value for the + * specified name, the first value in insertion order is returned. * - * @param name The name of the header to search - * @param defaultValue default value - * @return the first header value in milliseconds if the header is found and its value is a date. - * {@code defaultValue} if there's no such header or its value is not a date. + * @param name the name of the header to retrieve + * @param defaultValue the default value + * @return the milliseconds value of the first value in insertion order or {@code defaultValue} if there is no such + * value or it can't be converted to milliseconds. */ long getTimeMillis(T name, long defaultValue); /** - * Returns and removes the boolean value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. + * Returns the {@code boolean} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is a boolean. {@code null} if there's no such - * header or its value is not a boolean. + * @param name the name of the header to retrieve + * @return the {@code boolean} value of the first value in insertion order or {@code null} if there is no + * such value or it can't be converted to {@code boolean}. */ Boolean getBooleanAndRemove(T name); /** - * Returns and removes the boolean value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. + * Returns the {@code boolean} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search * @param defaultValue the default value - * @return the first header value if the header is found and its value is a boolean. {@code defaultValue} if there - * is no such header or its value of header is not a boolean. + * @return the {@code boolean} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code boolean}. */ boolean getBooleanAndRemove(T name, boolean defaultValue); /** - * Returns and removes the byte value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the {@code byte} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search - * @return the first header value if the header is found and its value is a byte. {@code null} if there's no such - * header or its value is not a byte. + * @return the {@code byte} value of the first value in insertion order or {@code null} if there is no + * such value or it can't be converted to {@code byte}. */ Byte getByteAndRemove(T name); /** - * Returns and removes the byte value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the {@code byte} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search * @param defaultValue the default value - * @return the first header value if the header is found and its value is a byte. {@code defaultValue} if there is - * no such header or its value of header is not a byte. + * @return the {@code byte} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code byte}. */ byte getByteAndRemove(T name, byte defaultValue); /** - * Returns and removes the char value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the {@code char} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search - * @return the first header value if the header is found and its value is a char. {@code null} if there's no such - * header or its value is not a char. + * @return the {@code char} value of the first value in insertion order or {@code null} if there is no + * such value or it can't be converted to {@code char}. */ Character getCharAndRemove(T name); /** - * Returns and removes the char value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the {@code char} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search * @param defaultValue the default value - * @return the first header value if the header is found and its value is a char. {@code defaultValue} if there is - * no such header or its value of header is not a char. + * @return the {@code char} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code char}. */ char getCharAndRemove(T name, char defaultValue); /** - * Returns and removes the short value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. + * Returns the {@code short} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search - * @return the first header value if the header is found and its value is a short. {@code null} if there's no such - * header or its value is not a short. + * @return the {@code short} value of the first value in insertion order or {@code null} if there is no + * such value or it can't be converted to {@code short}. */ Short getShortAndRemove(T name); /** - * Returns and removes the short value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. + * Returns the {@code short} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search * @param defaultValue the default value - * @return the first header value if the header is found and its value is a short. {@code defaultValue} if there is - * no such header or its value of header is not a short. + * @return the {@code short} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code short}. */ short getShortAndRemove(T name, short defaultValue); /** - * Returns and removes the integer value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. + * Returns the {@code int} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search - * @return the first header value if the header is found and its value is an integer. {@code null} if there's no - * such header or its value is not an integer. + * @return the {@code int} value of the first value in insertion order or {@code null} if there is no + * such value or it can't be converted to {@code int}. */ Integer getIntAndRemove(T name); /** - * Returns and removes the integer value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. + * Returns the {@code int} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search * @param defaultValue the default value - * @return the first header value if the header is found and its value is an integer. {@code defaultValue} if there - * is no such header or its value of header is not an integer. + * @return the {@code int} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code int}. */ int getIntAndRemove(T name, int defaultValue); /** - * Returns and removes the long value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the {@code long} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search - * @return the first header value if the header is found and its value is a long. {@code null} if there's no such - * header or its value is not a long. + * @return the {@code long} value of the first value in insertion order or {@code null} if there is no + * such value or it can't be converted to {@code long}. */ Long getLongAndRemove(T name); /** - * Returns and removes the long value of a header with the specified name. If there are more than one values for the - * specified name, the first value is returned. + * Returns the {@code long} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search * @param defaultValue the default value - * @return the first header value if the header is found and its value is a long. {@code defaultValue} if there's no - * such header or its value is not a long. + * @return the {@code long} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code long}. */ long getLongAndRemove(T name, long defaultValue); /** - * Returns and removes the float value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. + * Returns the {@code float} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search - * @return the first header value if the header is found and its value is a float. {@code null} if there's no such - * header or its value is not a float. + * @return the {@code float} value of the first value in insertion order or {@code null} if there is no + * such value or it can't be converted to {@code float}. */ Float getFloatAndRemove(T name); /** - * Returns and removes the float value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. + * Returns the {@code float} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search * @param defaultValue the default value - * @return the first header value if the header is found and its value is a float. {@code defaultValue} if there's - * no such header or its value is not a float. + * @return the {@code float} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code float}. */ float getFloatAndRemove(T name, float defaultValue); /** - * Returns and removes the double value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. + * Returns the {@code double} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search - * @return the first header value if the header is found and its value is a double. {@code null} if there's no such - * header or its value is not a double. + * @return the {@code double} value of the first value in insertion order or {@code null} if there is no + * such value or it can't be converted to {@code double}. */ Double getDoubleAndRemove(T name); /** - * Returns and removes the double value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. + * Returns the {@code double} value of a header with the specified {@code name} and removes the header from this + * object. If there is more than one value for the specified name, the first value in insertion order is returned. + * In any case all values for {@code name} are removed. * * @param name the name of the header to search * @param defaultValue the default value - * @return the first header value if the header is found and its value is a double. {@code defaultValue} if there's - * no such header or its value is not a double. + * @return the {@code double} value of the first value in insertion order or {@code defaultValue} if there is no + * such value or it can't be converted to {@code double}. */ double getDoubleAndRemove(T name, double defaultValue); /** - * Returns and removes the date value of a header with the specified name as milliseconds. If there are more than - * one values for the specified name, the first value is returned. + * Returns the value of a header with the specified {@code name} in milliseconds and removes the header from this + * object. If there is more than one value for the specified {@code name}, the first value in insertion order is + * returned. In any case all values for {@code name} are removed. * - * @param name The name of the header to search - * @return the first header value in milliseconds if the header is found and its value is a date. {@code null} if - * there's no such header or its value is not a date. + * @param name the name of the header to retrieve + * @return the milliseconds value of the first value in insertion order or {@code null} if there is no such + * value or it can't be converted to milliseconds. */ Long getTimeMillisAndRemove(T name); /** - * Returns and removes the date value of a header with the specified name as milliseconds. If there are more than - * one values for the specified name, the first value is returned. + * Returns the value of a header with the specified {@code name} in milliseconds and removes the header from this + * object. If there is more than one value for the specified {@code name}, the first value in insertion order is + * returned. In any case all values for {@code name} are removed. * - * @param name The name of the header to search - * @param defaultValue default value - * @return the first header value in milliseconds if the header is found and its value is a date. - * {@code defaultValue} if there's no such header or its value is not a date. + * @param name the name of the header to retrieve + * @param defaultValue the default value + * @return the milliseconds value of the first value in insertion order or {@code defaultValue} if there is no such + * value or it can't be converted to milliseconds. */ long getTimeMillisAndRemove(T name, long defaultValue); /** - * Returns a new {@link List} that contains all headers in this object. Note that modifying the returned - * {@link List} will not affect the state of this object. If you intend to enumerate over the header entries only, - * use {@link #iterator()} instead, which has much less overhead. - */ - List> entries(); - - /** - * Returns {@code true} if and only if this collection contains the header with the specified name. + * Returns {@code true} if a header with the {@code name} exists, {@code false} otherwise. * - * @param name The name of the header to search for - * @return {@code true} if at least one header is found + * @param name the header name */ boolean contains(T name); /** - * Returns {@code true} if a header with the name and value exists. - * + * Returns {@code true} if a header with the {@code name} and {@code value} exists, {@code false} otherwise. + *

+ * The {@link Object#equals(Object)} method is used to test for equality of {@code value}. + *

* @param name the header name - * @param value the header value - * @return {@code true} if it contains it {@code false} otherwise */ boolean contains(T name, T value); @@ -638,353 +623,281 @@ public interface Headers extends Iterable> { boolean containsTimeMillis(T name, long value); /** - * Returns {@code true} if a header with the name and value exists. + * Returns {@code true} if a header with the {@code name} and {@code value} exists. * * @param name the header name * @param value the header value - * @param comparator The comparator to use when comparing {@code name} and {@code value} to entries in this map + * @param valueComparator The comparator to use when comparing {@code value} to entries in this map * @return {@code true} if it contains it {@code false} otherwise */ - boolean contains(T name, T value, Comparator comparator); + boolean contains(T name, T value, Comparator valueComparator); /** - * Returns {@code true} if a header with the name and value exists. - * - * @param name the header name - * @param value the header value - * @param keyComparator The comparator to use when comparing {@code name} to names in this map - * @param valueComparator The comparator to use when comparing {@code value} to values in this map - * @return {@code true} if it contains it {@code false} otherwise - */ - boolean contains(T name, T value, Comparator keyComparator, Comparator valueComparator); - - /** - * Returns {@code true} if a header with the name and value exists. - * - * @param name the header name - * @param value the header value - * @param comparator The comparator to use when comparing {@code name} and {@code value} to entries in this map - * @return {@code true} if it contains it {@code false} otherwise - */ - boolean containsObject(T name, Object value, Comparator comparator); - - /** - * Returns {@code true} if a header with the name and value exists. - * - * @param name the header name - * @param value the header value - * @param keyComparator The comparator to use when comparing {@code name} to names in this map - * @param valueComparator The comparator to use when comparing {@code value} to values in this map - * @return {@code true} if it contains it {@code false} otherwise - */ - boolean containsObject(T name, Object value, Comparator keyComparator, - Comparator valueComparator); - - /** - * Returns the number of header entries in this collection. + * Returns the number of headers in this object. */ int size(); /** - * Returns {@code true} if and only if this collection contains no header entries. + * Returns {@code true} if {@link #size()} equals {@code 0}. */ boolean isEmpty(); /** - * Returns a new {@link Set} that contains the names of all headers in this object. Note that modifying the returned - * {@link Set} will not affect the state of this object. If you intend to enumerate over the header entries only, - * use {@link #iterator()} instead, which has much less overhead. + * Returns a {@link Set} of all header names in this object. The returned {@link Set} cannot be modified. */ Set names(); /** - * Returns a new {@link List} that contains the names of all headers in this object. Note that modifying the - * returned {@link List} will not affect the state of this object. If you intend to enumerate over the header - * entries only, use {@link #iterator()} instead, which has much less overhead. - */ - List namesList(); - - /** - * Adds a new header with the specified name and value. If the specified value is not a {@link String}, it is - * converted into a {@link String} by {@link Object#toString()}, except in the cases of {@link java.util.Date} and - * {@link java.util.Calendar}, which are formatted to the date format defined in RFC2616. + * Adds a new header with the specified {@code name} and {@code value}. * - * @param name the name of the header being added - * @param value the value of the header being added + * @param name the name of the header + * @param value the value of the header * @return {@code this} */ Headers add(T name, T value); /** - * Adds a new header with the specified name and values. This getMethod can be represented approximately as the - * following code: + * Adds new headers with the specified {@code name} and {@code values}. This method is semantically equivalent to * *
-     * for (Object v : values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
+     * for (T value : values) {
+     *     headers.add(name, value);
      * }
      * 
* - * @param name the name of the headepublic abstract rs being set - * @param values the values of the headers being set + * @param name the header name + * @param values the values of the header * @return {@code this} */ Headers add(T name, Iterable values); /** - * Adds a new header with the specified name and values. This getMethod can be represented approximately as the - * following code: + * Adds new headers with the specified {@code name} and {@code values}. This method is semantically equivalent to * *
-     * for (Object v : values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
+     * for (T value : values) {
+     *     headers.add(name, value);
      * }
      * 
* - * @param name the name of the headepublic abstract rs being set - * @param values the values of the headers being set + * @param name the header name + * @param values the values of the header * @return {@code this} */ Headers add(T name, T... values); /** - * Adds a new header with the specified name and value. If the specified value is not a {@link String}, it is - * converted into a {@link String} by {@link Object#toString()}, except in the cases of {@link java.util.Date} and - * {@link java.util.Calendar}, which are formatted to the date format defined in RFC2616. + * Adds a new header. Before the {@code value} is add, it's converted to type {@code T} by a call to + * {@link ValueConverter#convertObject(java.lang.Object)}. * - * @param name the name of the header being added - * @param value the value of the header being added + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers addObject(T name, Object value); /** - * Adds a new header with the specified name and values. This getMethod can be represented approximately as the - * following code: + * Adds a new header with the specified name and values. This method is equivalent to * *
      * for (Object v : values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
+     *     headers.addObject(name, v);
      * }
      * 
* - * @param name the name of the headepublic abstract rs being set - * @param values the values of the headers being set + * @param name the header name + * @param values the value of the header * @return {@code this} */ Headers addObject(T name, Iterable values); /** - * Adds a new header with the specified name and values. This getMethod can be represented approximately as the - * following code: + * Adds a new header with the specified name and values. This method is equivalent to * *
      * for (Object v : values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
+     *     headers.addObject(name, v);
      * }
      * 
* - * @param name the name of the headepublic abstract rs being set - * @param values the values of the headers being set + * @param name the header name + * @param values the value of the header * @return {@code this} */ Headers addObject(T name, Object... values); /** - * Add the {@code name} to {@code value}. - * @param name The name to modify - * @param value The value + * Adds a new header. + * + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers addBoolean(T name, boolean value); /** - * Add the {@code name} to {@code value}. - * @param name The name to modify - * @param value The value + * Adds a new header. + * + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers addByte(T name, byte value); /** - * Add the {@code name} to {@code value}. - * @param name The name to modify - * @param value The value + * Adds a new header. + * + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers addChar(T name, char value); /** - * Add the {@code name} to {@code value}. - * @param name The name to modify - * @param value The value + * Adds a new header. + * + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers addShort(T name, short value); /** - * Add the {@code name} to {@code value}. - * @param name The name to modify - * @param value The value + * Adds a new header. + * + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers addInt(T name, int value); /** - * Add the {@code name} to {@code value}. - * @param name The name to modify - * @param value The value + * Adds a new header. + * + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers addLong(T name, long value); /** - * Add the {@code name} to {@code value}. - * @param name The name to modify - * @param value The value + * Adds a new header. + * + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers addFloat(T name, float value); /** - * Add the {@code name} to {@code value}. - * @param name The name to modify - * @param value The value + * Adds a new header. + * + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers addDouble(T name, double value); /** - * Add the {@code name} to {@code value}. - * @param name The name to modify - * @param value The value + * Adds a new header. + * + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers addTimeMillis(T name, long value); /** - * Adds all header entries of the specified {@code headers}. + * Adds all header names and values of {@code headers} to this object. * + * @throws IllegalArgumentException if {@code headers == this}. * @return {@code this} */ - Headers add(Headers headers); + Headers add(Headers headers); /** - * Sets a header with the specified name and value. If there is an existing header with the same name, it is - * removed. If the specified value is not a {@link String}, it is converted into a {@link String} by - * {@link Object#toString()}, except for {@link java.util.Date} and {@link java.util.Calendar}, which are formatted - * to the date format defined in RFC2616. + * Sets a header with the specified name and value. Any existing headers with the same name are overwritten. * - * @param name The name of the header being set - * @param value The value of the header being set + * @param name the header name + * @param value the value of the header * @return {@code this} */ Headers set(T name, T value); /** - * Sets a header with the specified name and values. If there is an existing header with the same name, it is - * removed. This getMethod can be represented approximately as the following code: + * Sets a new header with the specified name and values. This method is equivalent to * *
-     * headers.remove(name);
-     * for (Object v : values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
+     * for (T v : values) {
+     *     headers.addObject(name, v);
      * }
      * 
* - * @param name the name of the headers being set - * @param values the values of the headers being set + * @param name the header name + * @param values the value of the header * @return {@code this} */ Headers set(T name, Iterable values); /** - * Sets a header with the specified name and values. If there is an existing header with the same name, it is - * removed. This getMethod can be represented approximately as the following code: + * Sets a header with the specified name and values. Any existing headers with this name are removed. This method + * is equivalent to: * *
      * headers.remove(name);
-     * for (Object v : values) {
-     *     if (v == null) {
-     *         break;
-     *     }
+     * for (T v : values) {
      *     headers.add(name, v);
      * }
      * 
* - * @param name the name of the headers being set - * @param values the values of the headers being set + * @param name the header name + * @param values the value of the header * @return {@code this} */ Headers set(T name, T... values); /** - * Sets a header with the specified name and value. If there is an existing header with the same name, it is - * removed. If the specified value is not a {@link String}, it is converted into a {@link String} by - * {@link Object#toString()}, except for {@link java.util.Date} and {@link java.util.Calendar}, which are formatted - * to the date format defined in RFC2616. + * Sets a new header. Any existing headers with this name are removed. Before the {@code value} is add, it's + * converted to type {@code T} by a call to {@link ValueConverter#convertObject(java.lang.Object)}. * - * @param name The name of the header being set - * @param value The value of the header being set + * @param name the header name + * @param value the value of the header + * @throws NullPointerException if either {@code name} or {@code value} before or after its conversion is + * {@code null}. * @return {@code this} */ Headers setObject(T name, Object value); /** - * Sets a header with the specified name and values. If there is an existing header with the same name, it is - * removed. This getMethod can be represented approximately as the following code: + * Sets a header with the specified name and values. Any existing headers with this name are removed. This method + * is equivalent to: * *
      * headers.remove(name);
      * for (Object v : values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
+     *     headers.addObject(name, v);
      * }
      * 
* - * @param name the name of the headers being set - * @param values the values of the headers being set + * @param name the header name + * @param values the values of the header * @return {@code this} */ Headers setObject(T name, Iterable values); /** - * Sets a header with the specified name and values. If there is an existing header with the same name, it is - * removed. This getMethod can be represented approximately as the following code: + * Sets a header with the specified name and values. Any existing headers with this name are removed. This method + * is equivalent to: * *
      * headers.remove(name);
      * for (Object v : values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
+     *     headers.addObject(name, v);
      * }
      * 
* - * @param name the name of the headers being set - * @param values the values of the headers being set + * @param name the header name + * @param values the values of the header * @return {@code this} */ Headers setObject(T name, Object... values); @@ -1062,30 +975,30 @@ public interface Headers extends Iterable> { Headers setTimeMillis(T name, long value); /** - * Cleans the current header entries and copies all header entries of the specified {@code headers}. + * Clears the current header entries and copies all header entries of the specified {@code headers}. * * @return {@code this} */ - Headers set(Headers headers); + Headers set(Headers headers); /** - * Retains all current headers but calls {@link #set(Object, Object)} for each entry in {@code headers} + * Retains all current headers but calls {@link #set(T, T)} for each entry in {@code headers}. * - * @param headers The headers used to {@link #set(Object, Object)} values in this instance + * @param headers The headers used to {@link #set(T, T)} values in this instance * @return {@code this} */ - Headers setAll(Headers headers); + Headers setAll(Headers headers); /** - * Removes the header with the specified name. + * Removes all headers with the specified {@code name}. * - * @param name The name of the header to remove - * @return {@code true} if and only if at least one entry has been removed + * @param name the header name + * @return {@code true} if at least one entry has been removed. */ boolean remove(T name); /** - * Removes all headers. + * Removes all headers. After a call to this method {@link #size()} equals {@code 0}. * * @return {@code this} */ @@ -1093,18 +1006,4 @@ public interface Headers extends Iterable> { @Override Iterator> iterator(); - - /** - * Provides an abstraction to iterate over elements maintained in the {@link Headers} collection. - * @param visitor The visitor which will visit each element in this map - * @return The last entry before iteration stopped or {@code null} if iteration went past the end - */ - Map.Entry forEachEntry(EntryVisitor visitor) throws Exception; - - /** - * Provides an abstraction to iterate over elements maintained in the {@link Headers} collection. - * @param visitor The visitor which will visit each element in this map - * @return The last key before iteration stopped or {@code null} if iteration went past the end - */ - T forEachName(NameVisitor visitor) throws Exception; } diff --git a/codec/src/main/java/io/netty/handler/codec/TextHeaders.java b/codec/src/main/java/io/netty/handler/codec/TextHeaders.java index 0eb14ff071..394ed9d403 100644 --- a/codec/src/main/java/io/netty/handler/codec/TextHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/TextHeaders.java @@ -25,17 +25,6 @@ package io.netty.handler.codec; * . */ public interface TextHeaders extends ConvertibleHeaders { - /** - * A visitor that helps reduce GC pressure while iterating over a collection of {@link Headers}. - */ - interface EntryVisitor extends Headers.EntryVisitor { - } - - /** - * A visitor that helps reduce GC pressure while iterating over a collection of {@link Headers}. - */ - interface NameVisitor extends Headers.NameVisitor { - } /** * Returns {@code true} if a header with the name and value exists. @@ -45,14 +34,6 @@ public interface TextHeaders extends ConvertibleHeaders { */ boolean contains(CharSequence name, CharSequence value, boolean ignoreCase); - /** - * Returns {@code true} if a header with the name and value exists. - * @param name the header name - * @param value the header value - * @return {@code true} if it contains it {@code false} otherwise - */ - boolean containsObject(CharSequence name, Object value, boolean ignoreCase); - @Override TextHeaders add(CharSequence name, CharSequence value); diff --git a/codec/src/test/java/io/netty/handler/codec/DefaultBinaryHeadersTest.java b/codec/src/test/java/io/netty/handler/codec/DefaultBinaryHeadersTest.java index c4bfb43b95..3da629c4ef 100644 --- a/codec/src/test/java/io/netty/handler/codec/DefaultBinaryHeadersTest.java +++ b/codec/src/test/java/io/netty/handler/codec/DefaultBinaryHeadersTest.java @@ -14,20 +14,25 @@ */ package io.netty.handler.codec; +import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import io.netty.util.AsciiString; +import static org.junit.Assert.fail; + import io.netty.util.ByteString; -import java.util.HashSet; +import java.text.ParsePosition; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.NoSuchElementException; -import java.util.Random; -import java.util.Set; +import io.netty.util.CharsetUtil; import org.junit.Test; /** @@ -36,107 +41,186 @@ import org.junit.Test; public class DefaultBinaryHeadersTest { @Test - public void binaryHeadersWithSameValuesShouldBeEquivalent() { - byte[] key1 = randomBytes(); - byte[] value1 = randomBytes(); - byte[] key2 = randomBytes(); - byte[] value2 = randomBytes(); + public void addShouldIncreaseAndRemoveShouldDecreaseTheSize() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + assertEquals(0, headers.size()); + headers.add(bs("name1"), bs("value1"), bs("value2")); + assertEquals(2, headers.size()); + headers.add(bs("name2"), bs("value3"), bs("value4")); + assertEquals(4, headers.size()); + headers.add(bs("name3"), bs("value5")); + assertEquals(5, headers.size()); - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.set(as(key1), as(value1)); - h1.set(as(key2), as(value2)); - - DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); - h2.set(as(key1), as(value1)); - h2.set(as(key2), as(value2)); - - assertTrue(h1.equals(h2)); - assertTrue(h2.equals(h1)); - assertTrue(h2.equals(h2)); - assertTrue(h1.equals(h1)); + headers.remove(bs("name3")); + assertEquals(4, headers.size()); + headers.remove(bs("name1")); + assertEquals(2, headers.size()); + headers.remove(bs("name2")); + assertEquals(0, headers.size()); + assertTrue(headers.isEmpty()); } @Test - public void binaryHeadersWithSameDuplicateValuesShouldBeEquivalent() { - byte[] k1 = randomBytes(); - byte[] k2 = randomBytes(); - byte[] v1 = randomBytes(); - byte[] v2 = randomBytes(); - byte[] v3 = randomBytes(); - byte[] v4 = randomBytes(); - - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.set(as(k1), as(v1)); - h1.set(as(k2), as(v2)); - h1.add(as(k2), as(v3)); - h1.add(as(k1), as(v4)); - - DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); - h2.set(as(k1), as(v1)); - h2.set(as(k2), as(v2)); - h2.add(as(k1), as(v4)); - h2.add(as(k2), as(v3)); - - assertTrue(h1.equals(h2)); - assertTrue(h2.equals(h1)); - assertTrue(h2.equals(h2)); - assertTrue(h1.equals(h1)); + public void afterClearHeadersShouldBeEmpty() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.add(bs("name1"), bs("value1")); + headers.add(bs("name2"), bs("value2")); + assertEquals(2, headers.size()); + headers.clear(); + assertEquals(0, headers.size()); + assertTrue(headers.isEmpty()); + assertFalse(headers.contains(bs("name1"))); + assertFalse(headers.contains(bs("name2"))); } @Test - public void binaryHeadersWithDifferentValuesShouldNotBeEquivalent() { - byte[] k1 = randomBytes(); - byte[] k2 = randomBytes(); - byte[] v1 = randomBytes(); - byte[] v2 = randomBytes(); - byte[] v3 = randomBytes(); - byte[] v4 = randomBytes(); - - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.set(as(k1), as(v1)); - h1.set(as(k2), as(v2)); - h1.add(as(k2), as(v3)); - h1.add(as(k1), as(v4)); - - DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); - h2.set(as(k1), as(v1)); - h2.set(as(k2), as(v2)); - h2.add(as(k1), as(v4)); - - assertFalse(h1.equals(h2)); - assertFalse(h2.equals(h1)); - assertTrue(h2.equals(h2)); - assertTrue(h1.equals(h1)); + public void removingANameForASecondTimeShouldReturnFalse() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.add(bs("name1"), bs("value1")); + headers.add(bs("name2"), bs("value2")); + assertTrue(headers.remove(bs("name2"))); + assertFalse(headers.remove(bs("name2"))); } @Test - public void binarySetAllShouldMergeHeaders() { - byte[] k1 = randomBytes(); - byte[] k2 = randomBytes(); - byte[] v1 = randomBytes(); - byte[] v2 = randomBytes(); - byte[] v3 = randomBytes(); - byte[] v4 = randomBytes(); + public void multipleValuesPerNameShouldBeAllowed() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.add(bs("name"), bs("value1")); + headers.add(bs("name"), bs("value2")); + headers.add(bs("name"), bs("value3")); + assertEquals(3, headers.size()); + List values = headers.getAll(bs("name")); + assertEquals(3, values.size()); + assertTrue(values.containsAll(asList(bs("value1"), bs("value2"), bs("value3")))); + } + + @Test + public void testContains() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.addBoolean(bs("boolean"), true); + assertTrue(headers.containsBoolean(bs("boolean"), true)); + assertFalse(headers.containsBoolean(bs("boolean"), false)); + + headers.addLong(bs("long"), Long.MAX_VALUE); + assertTrue(headers.containsLong(bs("long"), Long.MAX_VALUE)); + assertFalse(headers.containsLong(bs("long"), Long.MIN_VALUE)); + + headers.addInt(bs("int"), Integer.MIN_VALUE); + assertTrue(headers.containsInt(bs("int"), Integer.MIN_VALUE)); + assertFalse(headers.containsInt(bs("int"), Integer.MAX_VALUE)); + + headers.addShort(bs("short"), Short.MAX_VALUE); + assertTrue(headers.containsShort(bs("short"), Short.MAX_VALUE)); + assertFalse(headers.containsShort(bs("short"), Short.MIN_VALUE)); + + headers.addChar(bs("char"), Character.MAX_VALUE); + assertTrue(headers.containsChar(bs("char"), Character.MAX_VALUE)); + assertFalse(headers.containsChar(bs("char"), Character.MIN_VALUE)); + + headers.addByte(bs("byte"), Byte.MAX_VALUE); + assertTrue(headers.containsByte(bs("byte"), Byte.MAX_VALUE)); + assertFalse(headers.containsLong(bs("byte"), Byte.MIN_VALUE)); + + headers.addDouble(bs("double"), Double.MAX_VALUE); + assertTrue(headers.containsDouble(bs("double"), Double.MAX_VALUE)); + assertFalse(headers.containsDouble(bs("double"), Double.MIN_VALUE)); + + headers.addFloat(bs("float"), Float.MAX_VALUE); + assertTrue(headers.containsFloat(bs("float"), Float.MAX_VALUE)); + assertFalse(headers.containsFloat(bs("float"), Float.MIN_VALUE)); + + long millis = System.currentTimeMillis(); + headers.addTimeMillis(bs("millis"), millis); + assertTrue(headers.containsTimeMillis(bs("millis"), millis)); + // This test doesn't work on midnight, January 1, 1970 UTC + assertFalse(headers.containsTimeMillis(bs("millis"), 0)); + + headers.addObject(bs("object"), "Hello World"); + assertTrue(headers.containsObject(bs("object"), "Hello World")); + assertFalse(headers.containsObject(bs("object"), "")); + + headers.add(bs("name"), bs("value")); + assertTrue(headers.contains(bs("name"), bs("value"))); + assertFalse(headers.contains(bs("name"), bs("value1"))); + } + + @Test + public void canMixConvertedAndNormalValues() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.add(bs("name"), bs("value")); + headers.addInt(bs("name"), 100); + headers.addBoolean(bs("name"), false); + + assertEquals(3, headers.size()); + assertTrue(headers.contains(bs("name"))); + assertTrue(headers.contains(bs("name"), bs("value"))); + assertTrue(headers.containsInt(bs("name"), 100)); + assertTrue(headers.containsBoolean(bs("name"), false)); + } + + @Test + public void testGetAndRemove() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.add(bs("name1"), bs("value1")); + headers.add(bs("name2"), bs("value2"), bs("value3")); + headers.add(bs("name3"), bs("value4"), bs("value5"), bs("value6")); + + assertEquals(bs("value1"), headers.getAndRemove(bs("name1"), bs("defaultvalue"))); + assertEquals(bs("value2"), headers.getAndRemove(bs("name2"))); + assertNull(headers.getAndRemove(bs("name2"))); + assertEquals(asList(bs("value4"), bs("value5"), bs("value6")), headers.getAllAndRemove(bs("name3"))); + assertEquals(0, headers.size()); + assertNull(headers.getAndRemove(bs("noname"))); + assertEquals(bs("defaultvalue"), headers.getAndRemove(bs("noname"), bs("defaultvalue"))); + } + + @Test + public void whenNameContainsMultipleValuesGetShouldReturnTheFirst() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.add(bs("name1"), bs("value1"), bs("value2")); + assertEquals(bs("value1"), headers.get(bs("name1"))); + } + + @Test + public void getWithDefaultValueWorks() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.add(bs("name1"), bs("value1")); + + assertEquals(bs("value1"), headers.get(bs("name1"), bs("defaultvalue"))); + assertEquals(bs("defaultvalue"), headers.get(bs("noname"), bs("defaultvalue"))); + } + + @Test + public void setShouldOverWritePreviousValue() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.set(bs("name"), bs("value1")); + headers.set(bs("name"), bs("value2")); + assertEquals(1, headers.size()); + assertEquals(1, headers.getAll(bs("name")).size()); + assertEquals(bs("value2"), headers.getAll(bs("name")).get(0)); + assertEquals(bs("value2"), headers.get(bs("name"))); + } + + @Test + public void setAllShouldOverwriteSomeAndLeaveOthersUntouched() { DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.set(as(k1), as(v1)); - h1.set(as(k2), as(v2)); - h1.add(as(k2), as(v3)); - h1.add(as(k1), as(v4)); + + h1.add(bs("name1"), bs("value1")); + h1.add(bs("name2"), bs("value2")); + h1.add(bs("name2"), bs("value3")); + h1.add(bs("name3"), bs("value4")); DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); - h2.set(as(k1), as(v1)); - h2.set(as(k2), as(v2)); - h2.add(as(k1), as(v4)); + h2.add(bs("name1"), bs("value5")); + h2.add(bs("name2"), bs("value6")); + h2.add(bs("name1"), bs("value7")); DefaultBinaryHeaders expected = new DefaultBinaryHeaders(); - expected.set(as(k1), as(v1)); - expected.set(as(k2), as(v2)); - expected.add(as(k2), as(v3)); - expected.add(as(k1), as(v4)); - expected.set(as(k1), as(v1)); - expected.set(as(k2), as(v2)); - expected.set(as(k1), as(v4)); + expected.add(bs("name1"), bs("value5")); + expected.add(bs("name2"), bs("value6")); + expected.add(bs("name1"), bs("value7")); + expected.add(bs("name3"), bs("value4")); h1.setAll(h2); @@ -144,118 +228,64 @@ public class DefaultBinaryHeadersTest { } @Test - public void binarySetShouldReplacePreviousValues() { - byte[] k1 = randomBytes(); - byte[] v1 = randomBytes(); - byte[] v2 = randomBytes(); - byte[] v3 = randomBytes(); + public void headersWithSameNamesAndValuesShouldBeEquivalent() { + DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + headers1.add(bs("name1"), bs("value1")); + headers1.add(bs("name2"), bs("value2")); + headers1.add(bs("name2"), bs("value3")); - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.add(as(k1), as(v1)); - h1.add(as(k1), as(v2)); - assertEquals(2, h1.size()); + DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + headers2.add(bs("name1"), bs("value1")); + headers2.add(bs("name2"), bs("value2")); + headers2.add(bs("name2"), bs("value3")); - h1.set(as(k1), as(v3)); - assertEquals(1, h1.size()); - List list = h1.getAll(as(k1)); - assertEquals(1, list.size()); - assertEquals(as(v3), list.get(0)); + assertEquals(headers1, headers2); + assertEquals(headers2, headers1); + assertEquals(headers1, headers1); + assertEquals(headers2, headers2); + assertEquals(headers1.hashCode(), headers2.hashCode()); + assertEquals(headers1.hashCode(), headers1.hashCode()); + assertEquals(headers2.hashCode(), headers2.hashCode()); } @Test - public void headersWithSameValuesShouldBeEquivalent() { - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.set(as("foo"), as("goo")); - h1.set(as("foo2"), as("goo2")); + public void emptyHeadersShouldBeEqual() { + DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + assertNotSame(headers1, headers2); + assertEquals(headers1, headers2); + assertEquals(headers1.hashCode(), headers2.hashCode()); + } + @Test + public void headersWithSameNamesButDifferentValuesShouldNotBeEquivalent() { + DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + headers1.add(bs("name1"), bs("value1")); + DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + headers1.add(bs("name1"), bs("value2")); + assertNotEquals(headers1, headers2); + } + + @Test + public void subsetOfHeadersShouldNotBeEquivalent() { + DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + headers1.add(bs("name1"), bs("value1")); + headers1.add(bs("name2"), bs("value2")); + DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + headers1.add(bs("name1"), bs("value1")); + assertNotEquals(headers1, headers2); + } + + @Test + public void headersWithDifferentNamesAndValuesShouldNotBeEquivalent() { + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); + h1.set(bs("name1"), bs("value1")); DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); - h2.set(as("foo"), as("goo")); - h2.set(as("foo2"), as("goo2")); - - assertTrue(h1.equals(h2)); - assertTrue(h2.equals(h1)); - assertTrue(h2.equals(h2)); - assertTrue(h1.equals(h1)); - } - - @Test - public void headersWithSameDuplicateValuesShouldBeEquivalent() { - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.set(as("foo"), as("goo")); - h1.set(as("foo2"), as("goo2")); - h1.add(as("foo2"), as("goo3")); - h1.add(as("foo"), as("goo4")); - - DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); - h2.set(as("foo"), as("goo")); - h2.set(as("foo2"), as("goo2")); - h2.add(as("foo"), as("goo4")); - h2.add(as("foo2"), as("goo3")); - - assertTrue(h1.equals(h2)); - assertTrue(h2.equals(h1)); - assertTrue(h2.equals(h2)); - assertTrue(h1.equals(h1)); - } - - @Test - public void headersWithDifferentValuesShouldNotBeEquivalent() { - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.set(as("foo"), as("goo")); - h1.set(as("foo2"), as("goo2")); - h1.add(as("foo2"), as("goo3")); - h1.add(as("foo"), as("goo4")); - - DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); - h2.set(as("foo"), as("goo")); - h2.set(as("foo2"), as("goo2")); - h2.add(as("foo"), as("goo4")); - - assertFalse(h1.equals(h2)); - assertFalse(h2.equals(h1)); - assertTrue(h2.equals(h2)); - assertTrue(h1.equals(h1)); - } - - @Test - public void setAllShouldMergeHeaders() { - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.set(as("foo"), as("goo")); - h1.set(as("foo2"), as("goo2")); - h1.add(as("foo2"), as("goo3")); - h1.add(as("foo"), as("goo4")); - - DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); - h2.set(as("foo"), as("goo")); - h2.set(as("foo2"), as("goo2")); - h2.add(as("foo"), as("goo4")); - - DefaultBinaryHeaders expected = new DefaultBinaryHeaders(); - expected.set(as("foo"), as("goo")); - expected.set(as("foo2"), as("goo2")); - expected.add(as("foo2"), as("goo3")); - expected.add(as("foo"), as("goo4")); - expected.set(as("foo"), as("goo")); - expected.set(as("foo2"), as("goo2")); - expected.set(as("foo"), as("goo4")); - - h1.setAll(h2); - - assertEquals(expected, h1); - } - - @Test - public void setShouldReplacePreviousValues() { - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.add(as("foo"), as("goo")); - h1.add(as("foo"), as("goo2")); - assertEquals(2, h1.size()); - - h1.set(as("foo"), as("goo3")); - assertEquals(1, h1.size()); - List list = h1.getAll(as("foo")); - assertEquals(1, list.size()); - assertEquals(as("goo3"), list.get(0)); + h2.set(bs("name2"), bs("value2")); + assertNotEquals(h1, h2); + assertNotEquals(h2, h1); + assertEquals(h1, h1); + assertEquals(h2, h2); } @Test(expected = NoSuchElementException.class) @@ -266,53 +296,138 @@ public class DefaultBinaryHeadersTest { } @Test - public void iterateHeadersShouldReturnAllValues() { - Set headers = new HashSet(); - headers.add("a:1"); - headers.add("a:2"); - headers.add("a:3"); - headers.add("b:1"); - headers.add("b:2"); - headers.add("c:1"); + public void iteratorShouldReturnAllNameValuePairs() { + DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + headers1.add(bs("name1"), bs("value1"), bs("value2")); + headers1.add(bs("name2"), bs("value3")); + headers1.add(bs("name3"), bs("value4"), bs("value5"), bs("value6")); + headers1.add(bs("name1"), bs("value7"), bs("value8")); + assertEquals(8, headers1.size()); - // Build the headers from the input set. - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - for (String header : headers) { - String[] parts = header.split(":"); - h1.add(as(parts[0]), as(parts[1])); + DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + for (Entry entry : headers1) { + Object v = entry.getValue(); + headers2.add(entry.getKey(), entry.getValue()); } - // Now iterate through the headers, removing them from the original set. - for (Map.Entry entry : h1) { - assertTrue(headers.remove(entry.getKey().toString() + ':' + entry.getValue().toString())); - } - - // Make sure we removed them all. - assertTrue(headers.isEmpty()); + assertEquals(headers1, headers2); } @Test - public void getAndRemoveShouldReturnFirstEntry() { - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); - h1.add(as("foo"), as("goo")); - h1.add(as("foo"), as("goo2")); - assertEquals(as("goo"), h1.getAndRemove(as("foo"))); - assertEquals(0, h1.size()); - List values = h1.getAll(as("foo")); - assertEquals(0, values.size()); + public void iteratorSetValueShouldChangeHeaderValue() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.add(bs("name1"), bs("value1"), bs("value2"), bs("value3")); + headers.add(bs("name2"), bs("value4")); + assertEquals(4, headers.size()); + + Iterator> iter = headers.iterator(); + while (iter.hasNext()) { + Entry header = iter.next(); + if (bs("name1").equals(header.getKey()) && bs("value2").equals(header.getValue())) { + header.setValue(bs("updatedvalue2")); + assertEquals(bs("updatedvalue2"), header.getValue()); + } + if (bs("name1").equals(header.getKey()) && bs("value3").equals(header.getValue())) { + header.setValue(bs("updatedvalue3")); + assertEquals(bs("updatedvalue3"), header.getValue()); + } + } + + assertEquals(4, headers.size()); + assertTrue(headers.contains(bs("name1"), bs("updatedvalue2"))); + assertFalse(headers.contains(bs("name1"), bs("value2"))); + assertTrue(headers.contains(bs("name1"), bs("updatedvalue3"))); + assertFalse(headers.contains(bs("name1"), bs("value3"))); } - private static byte[] randomBytes() { - byte[] data = new byte[100]; - new Random().nextBytes(data); - return data; + @Test + public void getAllReturnsEmptyListForUnknownName() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + assertEquals(0, headers.getAll(bs("noname")).size()); } - private AsciiString as(byte[] bytes) { - return new AsciiString(bytes); + @Test + public void canNotModifyTheListReturnedByGetAll() { + DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + headers.add(bs("name1"), bs("value1")); + headers.add(bs("name2"), bs("value2"), bs("value3")); + + // Test for single value names. + try { + headers.getAll(bs("name1")).add(bs("value")); + fail(); + } catch (UnsupportedOperationException e) { + // for checkstyle + } + try { + headers.getAll(bs("name1")).remove(0); + fail(); + } catch (UnsupportedOperationException e) { + // for checkstyle + } + + // Test for multi value names. + try { + headers.getAll(bs("name2")).add(bs("value")); + fail(); + } catch (UnsupportedOperationException e) { + // for checkstyle + } + try { + headers.getAll(bs("name2")).remove(0); + fail(); + } catch (UnsupportedOperationException e) { + // for checkstyle + } + + // Test for names that don't exist. + try { + headers.getAll(bs("name3")).add(bs("value")); + fail(); + } catch (UnsupportedOperationException e) { + // for checkstyle + } + try { + headers.getAll(bs("name3")).remove(0); + fail(); + } catch (UnsupportedOperationException e) { + // for checkstyle + } } - private AsciiString as(String value) { - return new AsciiString(value); + @Test + public void setHeadersShouldClearAndOverwrite() { + DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + headers1.add(bs("name"), bs("value")); + + DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + headers2.add(bs("name"), bs("newvalue")); + headers2.add(bs("name1"), bs("value1")); + + headers1.set(headers2); + assertEquals(headers1, headers2); + } + + @Test + public void setAllHeadersShouldOnlyOverwriteHeaders() { + DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + headers1.add(bs("name"), bs("value")); + headers1.add(bs("name1"), bs("value1")); + + DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + headers2.add(bs("name"), bs("newvalue")); + headers2.add(bs("name2"), bs("value2")); + + DefaultBinaryHeaders expected = new DefaultBinaryHeaders(); + expected.add(bs("name"), bs("newvalue")); + expected.add(bs("name1"), bs("value1")); + expected.add(bs("name2"), bs("value2")); + + headers1.setAll(headers2); + assertEquals(headers1, expected); + } + + private ByteString bs(String value) { + return new ByteString(value, CharsetUtil.US_ASCII); } } diff --git a/codec/src/test/java/io/netty/handler/codec/DefaultTextHeadersTest.java b/codec/src/test/java/io/netty/handler/codec/DefaultTextHeadersTest.java index 8be40ab036..142f0aacc7 100644 --- a/codec/src/test/java/io/netty/handler/codec/DefaultTextHeadersTest.java +++ b/codec/src/test/java/io/netty/handler/codec/DefaultTextHeadersTest.java @@ -16,8 +16,6 @@ package io.netty.handler.codec; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static io.netty.util.internal.StringUtil.COMMA; import static io.netty.util.internal.StringUtil.DOUBLE_QUOTE; import java.util.Arrays; @@ -31,88 +29,6 @@ public class DefaultTextHeadersTest { private static final String HEADER_NAME = "testHeader"; - @Test - public void testEqualsMultipleHeaders() { - DefaultTextHeaders h1 = new DefaultTextHeaders(); - h1.set("Foo", "goo"); - h1.set("foo2", "goo2"); - - DefaultTextHeaders h2 = new DefaultTextHeaders(); - h2.set("FoO", "goo"); - h2.set("fOO2", "goo2"); - - assertTrue(h1.equals(h2)); - assertTrue(h2.equals(h1)); - assertTrue(h2.equals(h2)); - assertTrue(h1.equals(h1)); - } - - @Test - public void testEqualsDuplicateMultipleHeaders() { - DefaultTextHeaders h1 = new DefaultTextHeaders(); - h1.set("FOO", "goo"); - h1.set("Foo2", "goo2"); - h1.add("fOo2", "goo3"); - h1.add("foo", "goo4"); - - DefaultTextHeaders h2 = new DefaultTextHeaders(); - h2.set("foo", "goo"); - h2.set("foo2", "goo2"); - h2.add("foo", "goo4"); - h2.add("foO2", "goo3"); - - assertTrue(h1.equals(h2)); - assertTrue(h2.equals(h1)); - assertTrue(h2.equals(h2)); - assertTrue(h1.equals(h1)); - } - - @Test - public void testNotEqualsDuplicateMultipleHeaders() { - DefaultTextHeaders h1 = new DefaultTextHeaders(); - h1.set("FOO", "goo"); - h1.set("foo2", "goo2"); - h1.add("foo2", "goo3"); - h1.add("foo", "goo4"); - - DefaultTextHeaders h2 = new DefaultTextHeaders(); - h2.set("foo", "goo"); - h2.set("foo2", "goo2"); - h2.add("foo", "goo4"); - - assertFalse(h1.equals(h2)); - assertFalse(h2.equals(h1)); - assertTrue(h2.equals(h2)); - assertTrue(h1.equals(h1)); - } - - @Test - public void testSetAll() { - DefaultTextHeaders h1 = new DefaultTextHeaders(); - h1.set("FOO", "goo"); - h1.set("foo2", "goo2"); - h1.add("foo2", "goo3"); - h1.add("foo", "goo4"); - - DefaultTextHeaders h2 = new DefaultTextHeaders(); - h2.set("foo", "goo"); - h2.set("foo2", "goo2"); - h2.add("foo", "goo4"); - - DefaultTextHeaders expected = new DefaultTextHeaders(); - expected.set("FOO", "goo"); - expected.set("foo2", "goo2"); - expected.add("foo2", "goo3"); - expected.add("foo", "goo4"); - expected.set("foo", "goo"); - expected.set("foo2", "goo2"); - expected.set("foo", "goo4"); - - h1.setAll(h2); - - assertEquals(expected, h1); - } - @Test public void addCharSequences() { final TextHeaders headers = newDefaultTextHeaders(); @@ -325,11 +241,11 @@ public class DefaultTextHeadersTest { } private static TextHeaders newDefaultTextHeaders() { - return new DefaultTextHeaders(); + return new DefaultTextHeaders(false); } private static TextHeaders newCsvTextHeaders() { - return new DefaultTextHeaders(true, true); + return new DefaultTextHeaders(true); } private static void addValues(final TextHeaders headers, HeaderValue... headerValues) { diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index 92b1db52ac..097fe33d6c 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -54,64 +54,53 @@ public final class AsciiString extends ByteString implements CharSequence, Compa }; public static final Comparator CHARSEQUENCE_CASE_INSENSITIVE_ORDER = new Comparator() { + @Override public int compare(CharSequence o1, CharSequence o2) { - if (o1 == o2) { - return 0; + int len1 = o1.length(); + int delta = len1 - o2.length(); + if (delta != 0) { + return delta; } - - AsciiString a1 = o1 instanceof AsciiString ? (AsciiString) o1 : null; - AsciiString a2 = o2 instanceof AsciiString ? (AsciiString) o2 : null; - - int result; - int length1 = o1.length(); - int length2 = o2.length(); - int minLength = Math.min(length1, length2); - if (a1 != null && a2 != null) { - final int a1Len = minLength + a1.arrayOffset(); + if (o1.getClass().equals(AsciiString.class) && o2.getClass().equals(AsciiString.class)) { + AsciiString a1 = (AsciiString) o1; + AsciiString a2 = (AsciiString) o2; + final int a1Len = a1.length() + a1.arrayOffset(); for (int i = a1.arrayOffset(), j = a2.arrayOffset(); i < a1Len; i++, j++) { - byte v1 = a1.value[i]; - byte v2 = a2.value[j]; - if (v1 == v2) { - continue; - } - int c1 = toLowerCase(v1); - int c2 = toLowerCase(v2); - result = c1 - c2; - if (result != 0) { - return result; - } - } - } else if (a1 != null) { - for (int i = a1.arrayOffset(), j = 0; j < minLength; i++, j++) { - int c1 = toLowerCase(a1.value[i]); - int c2 = toLowerCase(o2.charAt(j)); - result = c1 - c2; - if (result != 0) { - return result; - } - } - } else if (a2 != null) { - for (int i = 0, j = a2.arrayOffset(); i < minLength; i++, j++) { - int c1 = toLowerCase(o1.charAt(i)); - int c2 = toLowerCase(a2.value[j]); - result = c1 - c2; - if (result != 0) { - return result; + byte c1 = a1.value[i]; + byte c2 = a2.value[j]; + if (c1 != c2) { + if (c1 >= 'A' && c1 <= 'Z') { + c1 += 32; + } + if (c2 >= 'A' && c2 <= 'Z') { + c2 += 32; + } + delta = c1 - c2; + if (delta != 0) { + return delta; + } } } } else { - for (int i = 0; i < minLength; i++) { - int c1 = toLowerCase(o1.charAt(i)); - int c2 = toLowerCase(o2.charAt(i)); - result = c1 - c2; - if (result != 0) { - return result; + for (int i = len1 - 1; i >= 0; i --) { + char c1 = o1.charAt(i); + char c2 = o2.charAt(i); + if (c1 != c2) { + if (c1 >= 'A' && c1 <= 'Z') { + c1 += 32; + } + if (c2 >= 'A' && c2 <= 'Z') { + c2 += 32; + } + delta = c1 - c2; + if (delta != 0) { + return delta; + } } } } - - return length1 - length2; + return 0; } }; diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java index ebada20a63..41fc23b7c9 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java @@ -45,6 +45,8 @@ import java.io.File; import java.io.FileNotFoundException; import java.net.InetSocketAddress; import java.net.URI; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map.Entry; @@ -186,12 +188,17 @@ public final class HttpUploadClient { ); // send request - List> entries = headers.entriesConverted(); channel.writeAndFlush(request); // Wait for the server to close the connection. channel.closeFuture().sync(); + // convert headers to list + List> entries = new ArrayList>(headers.size()); + Iterator> iterConverted = headers.iteratorConverted(); + while (iterConverted.hasNext()) { + entries.add(iterConverted.next()); + } return entries; } diff --git a/microbench/src/main/java/io/netty/microbench/headers/ExampleHeaders.java b/microbench/src/main/java/io/netty/microbench/headers/ExampleHeaders.java new file mode 100644 index 0000000000..606ead4d70 --- /dev/null +++ b/microbench/src/main/java/io/netty/microbench/headers/ExampleHeaders.java @@ -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> EXAMPLES = + new EnumMap>(HeaderExample.class); + + static { + Map header = new HashMap(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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() { + } +} diff --git a/microbench/src/main/java/io/netty/microbench/headers/HeadersBenchmark.java b/microbench/src/main/java/io/netty/microbench/headers/HeadersBenchmark.java new file mode 100644 index 0000000000..fc57991f9d --- /dev/null +++ b/microbench/src/main/java/io/netty/microbench/headers/HeadersBenchmark.java @@ -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 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 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 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 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 entry : http2Headers) { + final ByteString name = entry.getKey(); + final ByteString value = entry.getValue(); + if (!Http2Headers.PseudoHeaderName.isPseudoHeader(name)) { + bh.consume(value); + } + } + } +} diff --git a/microbench/src/main/java/io/netty/microbench/headers/package-info.java b/microbench/src/main/java/io/netty/microbench/headers/package-info.java new file mode 100644 index 0000000000..c7ef25659c --- /dev/null +++ b/microbench/src/main/java/io/netty/microbench/headers/package-info.java @@ -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;