From ba6ce5449ee852b782dde9a11933f6a09b123e22 Mon Sep 17 00:00:00 2001 From: Scott Mitchell Date: Wed, 12 Aug 2015 19:05:37 -0700 Subject: [PATCH] Headers Performance Boost and Interface Simplification Motivation: A degradation in performance has been observed from the 4.0 branch as documented in https://github.com/netty/netty/issues/3962. Modifications: - Simplify Headers class hierarchy. - Restore the DefaultHeaders to be based upon DefaultHttpHeaders from 4.0. - Make various other modifications that are causing hot spots. Result: Performance is now on par with 4.0. --- .../codec/http/CombinedHttpHeaders.java | 185 ++++ .../codec/http/DefaultFullHttpResponse.java | 16 +- .../codec/http/DefaultHttpHeaders.java | 224 ++--- .../codec/http/DefaultHttpMessage.java | 12 +- .../codec/http/DefaultHttpRequest.java | 12 +- .../codec/http/DefaultHttpResponse.java | 11 +- .../codec/http/DefaultLastHttpContent.java | 27 +- .../handler/codec/http/EmptyHttpHeaders.java | 158 ++++ .../codec/http/HttpClientUpgradeHandler.java | 2 +- .../codec/http/HttpContentCompressor.java | 2 +- .../codec/http/HttpContentDecompressor.java | 17 +- .../handler/codec/http/HttpHeaderUtil.java | 11 +- .../netty/handler/codec/http/HttpHeaders.java | 235 ++--- .../netty/handler/codec/http/HttpMethod.java | 22 +- .../handler/codec/http/HttpObjectDecoder.java | 6 +- .../HttpPostMultipartRequestDecoder.java | 18 +- .../multipart/HttpPostRequestEncoder.java | 2 +- .../WebSocketClientHandshaker00.java | 4 +- .../WebSocketClientHandshaker07.java | 4 +- .../WebSocketClientHandshaker08.java | 4 +- .../WebSocketClientHandshaker13.java | 4 +- .../WebSocketServerHandshaker00.java | 8 +- .../WebSocketServerHandshaker07.java | 1 + .../WebSocketServerHandshaker08.java | 1 + .../WebSocketServerHandshaker13.java | 1 + .../codec/spdy/DefaultSpdyHeaders.java | 67 +- .../netty/handler/codec/spdy/SpdyHeaders.java | 44 +- .../handler/codec/spdy/SpdyHttpDecoder.java | 8 +- .../codec/http/CombinedHttpHeadersTest.java | 201 ++++ .../codec/http/DefaultHttpHeadersTest.java | 118 +++ .../codec/http/HttpHeaderUtilTest.java | 60 ++ .../handler/codec/http/HttpHeadersTest.java | 8 +- .../codec/http/HttpHeadersTestUtils.java | 130 +++ .../codec/spdy/SpdySessionHandlerTest.java | 2 +- .../codec/http2/DefaultHttp2Headers.java | 77 +- .../codec/http2/EmptyHttp2Headers.java | 12 +- .../handler/codec/http2/Http2Headers.java | 16 +- .../netty/handler/codec/http2/HttpUtil.java | 8 +- .../InboundHttp2ToHttpPriorityAdapter.java | 25 +- .../codec/http2/DataCompressionHttp2Test.java | 7 +- .../codec/http2/DefaultHttp2FrameIOTest.java | 49 +- .../DefaultHttp2HeaderTableListSizeTest.java | 5 +- .../http2/DefaultHttp2HeadersDecoderTest.java | 21 +- .../http2/DefaultHttp2HeadersEncoderTest.java | 10 +- .../codec/http2/DefaultHttp2HeadersTest.java | 87 +- .../http2/Http2ConnectionRoundtripTest.java | 61 +- .../codec/http2/Http2FrameRoundtripTest.java | 7 +- .../codec/http2/Http2HeaderBlockIOTest.java | 43 +- .../handler/codec/http2/Http2TestUtil.java | 16 +- .../HttpToHttp2ConnectionHandlerTest.java | 90 +- .../http2/InboundHttp2ToHttpAdapterTest.java | 82 +- .../codec/stomp/DefaultStompHeaders.java | 48 +- .../handler/codec/stomp/StompHeaders.java | 44 +- .../codec/stomp/StompSubframeEncoder.java | 7 +- .../codec/stomp/StompTestConstants.java | 2 +- .../io/netty/handler/codec/BinaryHeaders.java | 135 --- .../codec/ByteStringValueConverter.java | 140 +++ .../codec/CharSequenceValueConverter.java | 133 +++ .../handler/codec/ConvertibleHeaders.java | 105 --- .../handler/codec/DefaultBinaryHeaders.java | 361 -------- .../codec/DefaultConvertibleHeaders.java | 160 ---- .../netty/handler/codec/DefaultHeaders.java | 859 +++++++++--------- .../handler/codec/DefaultTextHeaders.java | 616 ------------- .../handler/codec/EmptyBinaryHeaders.java | 228 ----- .../codec/EmptyConvertibleHeaders.java | 67 -- .../io/netty/handler/codec/EmptyHeaders.java | 6 - .../netty/handler/codec/EmptyTextHeaders.java | 231 ----- .../java/io/netty/handler/codec/Headers.java | 133 ++- .../io/netty/handler/codec/HeadersUtils.java | 282 ++++++ .../io/netty/handler/codec/TextHeaders.java | 144 --- .../netty/handler/codec/ValueConverter.java | 57 ++ ...adersTest.java => DefaultHeadersTest.java} | 132 +-- .../handler/codec/DefaultTextHeadersTest.java | 360 -------- .../main/java/io/netty/util/AsciiString.java | 497 +++++----- .../main/java/io/netty/util/ByteString.java | 67 +- .../java/io/netty/util/HashingStrategy.java | 75 ++ .../io/netty/util/internal/StringUtil.java | 1 - .../java/io/netty/util/AsciiStringTest.java | 92 +- .../java/io/netty/util/ByteStringTest.java | 63 -- .../example/http/upload/HttpUploadClient.java | 2 +- .../netty/example/http2/tiles/HttpServer.java | 1 - .../example/stomp/StompClientHandler.java | 2 +- .../microbench/headers/ExampleHeaders.java | 2 +- .../microbench/headers/HeadersBenchmark.java | 16 + 84 files changed, 3193 insertions(+), 4116 deletions(-) create mode 100644 codec-http/src/main/java/io/netty/handler/codec/http/CombinedHttpHeaders.java create mode 100644 codec-http/src/main/java/io/netty/handler/codec/http/EmptyHttpHeaders.java create mode 100644 codec-http/src/test/java/io/netty/handler/codec/http/CombinedHttpHeadersTest.java create mode 100644 codec-http/src/test/java/io/netty/handler/codec/http/HttpHeaderUtilTest.java create mode 100644 codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTestUtils.java delete mode 100644 codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java create mode 100644 codec/src/main/java/io/netty/handler/codec/ByteStringValueConverter.java create mode 100644 codec/src/main/java/io/netty/handler/codec/CharSequenceValueConverter.java delete mode 100644 codec/src/main/java/io/netty/handler/codec/ConvertibleHeaders.java delete mode 100644 codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java delete mode 100644 codec/src/main/java/io/netty/handler/codec/DefaultConvertibleHeaders.java delete mode 100644 codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java delete mode 100644 codec/src/main/java/io/netty/handler/codec/EmptyBinaryHeaders.java delete mode 100644 codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java delete mode 100644 codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java create mode 100644 codec/src/main/java/io/netty/handler/codec/HeadersUtils.java delete mode 100644 codec/src/main/java/io/netty/handler/codec/TextHeaders.java create mode 100644 codec/src/main/java/io/netty/handler/codec/ValueConverter.java rename codec/src/test/java/io/netty/handler/codec/{DefaultBinaryHeadersTest.java => DefaultHeadersTest.java} (77%) delete mode 100644 codec/src/test/java/io/netty/handler/codec/DefaultTextHeadersTest.java create mode 100644 common/src/main/java/io/netty/util/HashingStrategy.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/CombinedHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/CombinedHttpHeaders.java new file mode 100644 index 0000000000..1145fd6ea0 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/CombinedHttpHeaders.java @@ -0,0 +1,185 @@ +/* + * 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 io.netty.handler.codec.DefaultHeaders; +import io.netty.handler.codec.ValueConverter; +import io.netty.util.HashingStrategy; +import io.netty.util.internal.StringUtil; + +import java.util.Collection; +import java.util.Iterator; + +import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER; +import static io.netty.util.internal.StringUtil.COMMA; + +/** + * Will add multiple values for the same header as single header with a comma separated list of values. + *

+ * Please refer to section RFC 7230, 3.2.2. + */ +public class CombinedHttpHeaders extends DefaultHttpHeaders { + public CombinedHttpHeaders(boolean validate) { + super(new CombinedHttpHeadersImpl(CASE_INSENSITIVE_HASHER, valueConverter(validate), nameValidator(validate))); + } + + private static final class CombinedHttpHeadersImpl extends DefaultHeaders { + /** + * An estimate of the size of a header value. + */ + private static final int VALUE_LENGTH_ESTIMATE = 10; + private CsvValueEscaper objectEscaper; + private CsvValueEscaper charSequenceEscaper; + + private CsvValueEscaper objectEscaper() { + if (objectEscaper == null) { + objectEscaper = new CsvValueEscaper() { + @Override + public CharSequence escape(Object value) { + return StringUtil.escapeCsv(valueConverter().convertObject(value)); + } + }; + } + return objectEscaper; + } + + private CsvValueEscaper charSequenceEscaper() { + if (charSequenceEscaper == null) { + charSequenceEscaper = new CsvValueEscaper() { + @Override + public CharSequence escape(CharSequence value) { + return StringUtil.escapeCsv(value); + } + }; + } + return charSequenceEscaper; + } + + public CombinedHttpHeadersImpl(HashingStrategy nameHashingStrategy, + ValueConverter valueConverter, + io.netty.handler.codec.DefaultHeaders.NameValidator nameValidator) { + super(nameHashingStrategy, valueConverter, nameValidator); + } + + @Override + public CombinedHttpHeadersImpl add(CharSequence name, CharSequence value) { + return addEscapedValue(name, StringUtil.escapeCsv(value)); + } + + @Override + public CombinedHttpHeadersImpl add(CharSequence name, CharSequence... values) { + return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values)); + } + + @Override + public CombinedHttpHeadersImpl add(CharSequence name, Iterable values) { + return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values)); + } + + @Override + public CombinedHttpHeadersImpl addObject(CharSequence name, Iterable values) { + return addEscapedValue(name, commaSeparate(objectEscaper(), values)); + } + + @Override + public CombinedHttpHeadersImpl addObject(CharSequence name, Object... values) { + return addEscapedValue(name, commaSeparate(objectEscaper(), values)); + } + + @Override + public CombinedHttpHeadersImpl set(CharSequence name, CharSequence... values) { + super.set(name, commaSeparate(charSequenceEscaper(), values)); + return this; + } + + @Override + public CombinedHttpHeadersImpl set(CharSequence name, Iterable values) { + super.set(name, commaSeparate(charSequenceEscaper(), values)); + return this; + } + + @Override + public CombinedHttpHeadersImpl setObject(CharSequence name, Object... values) { + super.set(name, commaSeparate(objectEscaper(), values)); + return this; + } + + @Override + public CombinedHttpHeadersImpl setObject(CharSequence name, Iterable values) { + super.set(name, commaSeparate(objectEscaper(), values)); + return this; + } + + private CombinedHttpHeadersImpl addEscapedValue(CharSequence name, CharSequence escapedValue) { + CharSequence currentValue = super.get(name); + if (currentValue == null) { + super.add(name, escapedValue); + } else { + super.set(name, commaSeparateEscapedValues(currentValue, escapedValue)); + } + return this; + } + + private static CharSequence commaSeparate(CsvValueEscaper escaper, T... values) { + StringBuilder sb = new StringBuilder(values.length * VALUE_LENGTH_ESTIMATE); + if (values.length > 0) { + int end = values.length - 1; + for (int i = 0; i < end; i++) { + sb.append(escaper.escape(values[i])).append(COMMA); + } + sb.append(escaper.escape(values[end])); + } + return sb; + } + + private static CharSequence commaSeparate(CsvValueEscaper escaper, Iterable values) { + @SuppressWarnings("rawtypes") + final StringBuilder sb = values instanceof Collection + ? new StringBuilder(((Collection) values).size() * VALUE_LENGTH_ESTIMATE) : new StringBuilder(); + Iterator iterator = values.iterator(); + if (iterator.hasNext()) { + T next = iterator.next(); + while (iterator.hasNext()) { + sb.append(escaper.escape(next)).append(COMMA); + next = iterator.next(); + } + sb.append(escaper.escape(next)); + } + return sb; + } + + private CharSequence commaSeparateEscapedValues(CharSequence currentValue, CharSequence value) { + return new StringBuilder(currentValue.length() + 1 + value.length()) + .append(currentValue) + .append(COMMA) + .append(value); + } + + /** + * Escapes comma separated values (CSV). + * + * @param The type that a concrete implementation handles + */ + private interface CsvValueEscaper { + /** + * Appends the value to the specified {@link StringBuilder}, escaping if necessary. + * + * @param value the value to be appended, escaped if necessary + */ + CharSequence escape(T value); + } + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpResponse.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpResponse.java index 6028f40267..36ac176d64 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpResponse.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultFullHttpResponse.java @@ -15,10 +15,10 @@ */ package io.netty.handler.codec.http; +import static io.netty.util.internal.ObjectUtil.checkNotNull; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; - /** * Default implementation of a {@link FullHttpResponse}. */ @@ -33,7 +33,7 @@ public class DefaultFullHttpResponse extends DefaultHttpResponse implements Full } public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, ByteBuf content) { - this(version, status, content, false); + this(version, status, content, true); } public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, boolean validateHeaders) { @@ -46,18 +46,16 @@ public class DefaultFullHttpResponse extends DefaultHttpResponse implements Full } public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, - ByteBuf content, boolean singleFieldHeaders) { - this(version, status, content, true, singleFieldHeaders); + ByteBuf content, boolean validateHeaders) { + this(version, status, content, validateHeaders, false); } public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, ByteBuf content, boolean validateHeaders, boolean singleFieldHeaders) { super(version, status, validateHeaders, singleFieldHeaders); - if (content == null) { - throw new NullPointerException("content"); - } - this.content = content; - trailingHeaders = new DefaultHttpHeaders(validateHeaders, singleFieldHeaders); + this.content = checkNotNull(content, "content"); + this.trailingHeaders = singleFieldHeaders ? new CombinedHttpHeaders(validateHeaders) + : new DefaultHttpHeaders(validateHeaders); this.validateHeaders = validateHeaders; } 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 5f83fe4075..68f4878c4a 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,11 +15,14 @@ */ package io.netty.handler.codec.http; +import io.netty.handler.codec.CharSequenceValueConverter; import io.netty.handler.codec.DefaultHeaders; -import io.netty.handler.codec.DefaultTextHeaders; -import io.netty.handler.codec.DefaultTextHeaders.CharSequenceConverter; -import io.netty.handler.codec.TextHeaders; +import io.netty.handler.codec.DefaultHeaders.NameValidator; +import io.netty.handler.codec.HeadersUtils; +import io.netty.handler.codec.ValueConverter; import io.netty.util.AsciiString; +import io.netty.util.ByteProcessor; +import io.netty.util.internal.PlatformDependent; import java.util.ArrayList; import java.util.Calendar; @@ -30,56 +33,58 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.TreeMap; +import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER; +import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER; import static io.netty.util.internal.ObjectUtil.checkNotNull; /** * Default implementation of {@link HttpHeaders}. */ public class DefaultHttpHeaders extends HttpHeaders { - private static final int HIGHEST_INVALID_NAME_CHAR_MASK = ~63; private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15; + private static final ByteProcessor HEADER_NAME_VALIDATOR = new ByteProcessor() { + @Override + public boolean process(byte value) throws Exception { + validateChar((char) (value & 0xFF)); + return true; + } + }; + static final NameValidator HttpNameValidator = new NameValidator() { + @Override + public void validateName(CharSequence name) { + if (name instanceof AsciiString) { + try { + ((AsciiString) name).forEachByte(HEADER_NAME_VALIDATOR); + } catch (Exception e) { + PlatformDependent.throwException(e); + } + } else { + checkNotNull(name, "name"); + // Go through each character in the name + for (int index = 0; index < name.length(); ++index) { + validateChar(name.charAt(index)); + } + } + } + }; - /** - * A look-up table used for checking if a character in a header name is prohibited. - */ - private static final byte[] LOOKUP_TABLE = new byte[~HIGHEST_INVALID_NAME_CHAR_MASK + 1]; - - static { - LOOKUP_TABLE['\t'] = -1; - LOOKUP_TABLE['\n'] = -1; - LOOKUP_TABLE[0x0b] = -1; - LOOKUP_TABLE['\f'] = -1; - LOOKUP_TABLE[' '] = -1; - LOOKUP_TABLE[','] = -1; - LOOKUP_TABLE[':'] = -1; - LOOKUP_TABLE[';'] = -1; - LOOKUP_TABLE['='] = -1; - } - - private final TextHeaders headers; + private final DefaultHeaders headers; public DefaultHttpHeaders() { this(true); } public DefaultHttpHeaders(boolean validate) { - this(validate, false); + this(validate, nameValidator(validate)); } - protected DefaultHttpHeaders(boolean validate, boolean singleHeaderFields) { - this(true, validate ? HeaderNameValidator.INSTANCE : DefaultTextHeaders.NO_NAME_VALIDATOR, singleHeaderFields); + protected DefaultHttpHeaders(boolean validate, NameValidator nameValidator) { + this(new DefaultHeaders(CASE_INSENSITIVE_HASHER, valueConverter(validate), nameValidator)); } - protected DefaultHttpHeaders(boolean validate, - DefaultHeaders.NameValidator nameValidator, - boolean singleHeaderFields) { - headers = new DefaultTextHeaders( - new TreeMap(AsciiString.CHARSEQUENCE_CASE_INSENSITIVE_ORDER), - nameValidator, - validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE, - singleHeaderFields); + protected DefaultHttpHeaders(DefaultHeaders headers) { + this.headers = headers; } @Override @@ -102,24 +107,28 @@ public class DefaultHttpHeaders extends HttpHeaders { } } + @Deprecated @Override public HttpHeaders add(String name, Object value) { headers.addObject(name, value); return this; } + @Deprecated @Override public HttpHeaders add(CharSequence name, Object value) { headers.addObject(name, value); return this; } + @Deprecated @Override public HttpHeaders add(String name, Iterable values) { headers.addObject(name, values); return this; } + @Deprecated @Override public HttpHeaders add(CharSequence name, Iterable values) { headers.addObject(name, values); @@ -138,36 +147,42 @@ public class DefaultHttpHeaders extends HttpHeaders { return this; } + @Deprecated @Override public HttpHeaders remove(String name) { headers.remove(name); return this; } + @Deprecated @Override public HttpHeaders remove(CharSequence name) { headers.remove(name); return this; } + @Deprecated @Override public HttpHeaders set(String name, Object value) { headers.setObject(name, value); return this; } + @Deprecated @Override public HttpHeaders set(CharSequence name, Object value) { headers.setObject(name, value); return this; } + @Deprecated @Override public HttpHeaders set(String name, Iterable values) { headers.setObject(name, values); return this; } + @Deprecated @Override public HttpHeaders set(CharSequence name, Iterable values) { headers.setObject(name, values); @@ -192,14 +207,16 @@ public class DefaultHttpHeaders extends HttpHeaders { return this; } + @Deprecated @Override public String get(String name) { - return headers.getAndConvert(name); + return get((CharSequence) name); } + @Deprecated @Override public String get(CharSequence name) { - return headers.getAndConvert(name); + return HeadersUtils.getAsString(headers, name); } @Override @@ -232,16 +249,19 @@ public class DefaultHttpHeaders extends HttpHeaders { return headers.getTimeMillis(name, defaultValue); } + @Deprecated @Override public List getAll(String name) { - return headers.getAllAndConvert(name); + return getAll((CharSequence) name); } + @Deprecated @Override public List getAll(CharSequence name) { - return headers.getAllAndConvert(name); + return HeadersUtils.getAllAsString(headers, name); } + @Deprecated @Override public List> entries() { if (isEmpty()) { @@ -255,30 +275,22 @@ public class DefaultHttpHeaders extends HttpHeaders { return entriesConverted; } - /** - * @deprecated Use {@link #iteratorCharSequence()}. - */ - @Override @Deprecated + @Override public Iterator> iterator() { - return headers.iteratorConverted(); + return HeadersUtils.iteratorAsString(headers); } - /** - * @deprecated Future major releases will have this be the default iterator. - * Get an iterator to traverse over the underlying name/value pairs. - *

- * This iterator should be preferred over {@link #iterator()} if {@link AsciiString} objects are used. - */ - @Override @Deprecated + @Override public Iterator> iteratorCharSequence() { return headers.iterator(); } + @Deprecated @Override public boolean contains(String name) { - return headers.contains(name); + return contains((CharSequence) name); } @Override @@ -298,88 +310,90 @@ public class DefaultHttpHeaders extends HttpHeaders { @Override public boolean contains(String name, String value, boolean ignoreCase) { - return headers.contains(name, value, ignoreCase); + return contains((CharSequence) name, (CharSequence) value, ignoreCase); } @Override public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { - return headers.contains(name, value, ignoreCase); + return headers.contains(name, value, ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER); + } + + @Deprecated + @Override + public Set names() { + return HeadersUtils.namesAsString(headers); } @Override - public Set names() { - return headers.namesAndConvert(String.CASE_INSENSITIVE_ORDER); + public boolean equals(Object o) { + if (!(o instanceof DefaultHttpHeaders)) { + return false; + } + return headers.equals(((DefaultHttpHeaders) o).headers, CASE_SENSITIVE_HASHER); } @Override public int hashCode() { - return headers.size(); + return headers.hashCode(CASE_SENSITIVE_HASHER); } - @Override - public boolean equals(Object other) { - if (!(other instanceof DefaultHttpHeaders)) { - return false; - } - DefaultHttpHeaders headers = (DefaultHttpHeaders) other; - return DefaultHeaders.comparatorEquals(this.headers, headers.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 void validateChar(char character) { + switch (character) { + case '\t': + case '\n': + case 0x0b: + case '\f': + case '\r': + case ' ': + case ',': + case ':': + case ';': + case '=': + throw new IllegalArgumentException( + "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " + + character); + default: + // Check to see if the character is not an ASCII character, or invalid + if (character > 127) { + throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " + + character); } } } - private static class HeaderValueConverter extends CharSequenceConverter { + static ValueConverter valueConverter(boolean validate) { + return validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE; + } - public static final HeaderValueConverter INSTANCE = new HeaderValueConverter(); + @SuppressWarnings("unchecked") + static NameValidator nameValidator(boolean validate) { + return validate ? HttpNameValidator : NameValidator.NOT_NULL; + } + + private static class HeaderValueConverter extends CharSequenceValueConverter { + 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 (CharSequence) value; } - return seq; + if (value instanceof Number) { + return value.toString(); + } + if (value instanceof Date) { + return HttpHeaderDateFormat.get().format((Date) value); + } + if (value instanceof Calendar) { + return HttpHeaderDateFormat.get().format(((Calendar) value).getTime()); + } + return value.toString(); } } private static final class HeaderValueConverterAndValidator extends HeaderValueConverter { - - public static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator(); + static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator(); @Override public CharSequence convertObject(Object value) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpMessage.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpMessage.java index 958e3693a9..82fd0cb4fb 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpMessage.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpMessage.java @@ -15,6 +15,8 @@ */ package io.netty.handler.codec.http; +import static io.netty.util.internal.ObjectUtil.checkNotNull; + /** * The default {@link HttpMessage} implementation. */ @@ -33,12 +35,10 @@ public abstract class DefaultHttpMessage extends DefaultHttpObject implements Ht /** * Creates a new instance. */ - protected DefaultHttpMessage(final HttpVersion version, boolean validateHeaders, boolean singleHeaderFields) { - if (version == null) { - throw new NullPointerException("version"); - } - this.version = version; - headers = new DefaultHttpHeaders(validateHeaders, singleHeaderFields); + protected DefaultHttpMessage(final HttpVersion version, boolean validateHeaders, boolean singleFieldHeaders) { + this.version = checkNotNull(version, "version"); + headers = singleFieldHeaders ? new CombinedHttpHeaders(validateHeaders) + : new DefaultHttpHeaders(validateHeaders); } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java index 8b543ba660..7844688d35 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpRequest.java @@ -15,6 +15,8 @@ */ package io.netty.handler.codec.http; +import static io.netty.util.internal.ObjectUtil.checkNotNull; + /** * The default {@link HttpRequest} implementation. */ @@ -44,14 +46,8 @@ public class DefaultHttpRequest extends DefaultHttpMessage implements HttpReques */ public DefaultHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, boolean validateHeaders) { super(httpVersion, validateHeaders, false); - if (method == null) { - throw new NullPointerException("method"); - } - if (uri == null) { - throw new NullPointerException("uri"); - } - this.method = method; - this.uri = uri; + this.method = checkNotNull(method, "method"); + this.uri = checkNotNull(uri, "uri"); } @Override diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpResponse.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpResponse.java index 85117c77e4..4ee54a3f3f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpResponse.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpResponse.java @@ -50,12 +50,15 @@ public class DefaultHttpResponse extends DefaultHttpMessage implements HttpRespo * @param version the HTTP version of this response * @param status the getStatus of this response * @param validateHeaders validate the header names and values when adding them to the {@link HttpHeaders} - * @param singleHeaderFields determines if HTTP headers with multiple values should be added as a single - * field or as multiple header fields. + * @param singleFieldHeaders {@code true} to check and enforce that headers with the same name are appended + * to the same entry and comma separated. + * See RFC 7230, 3.2.2. + * {@code false} to allow multiple header entries with the same name to + * coexist. */ public DefaultHttpResponse(HttpVersion version, HttpResponseStatus status, boolean validateHeaders, - boolean singleHeaderFields) { - super(version, validateHeaders, singleHeaderFields); + boolean singleFieldHeaders) { + super(version, validateHeaders, singleFieldHeaders); if (status == null) { throw new NullPointerException("status"); } 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 b7f6afacb7..cc8ef0c7d6 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,8 +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.handler.codec.DefaultTextHeaders; +import io.netty.handler.codec.DefaultHeaders.NameValidator; import io.netty.util.internal.StringUtil; import java.util.Map.Entry; @@ -27,7 +26,6 @@ import java.util.Map.Entry; * The default {@link LastHttpContent} implementation. */ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHttpContent { - private final HttpHeaders trailingHeaders; private final boolean validateHeaders; @@ -109,26 +107,21 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt } private static final class TrailingHttpHeaders extends DefaultHttpHeaders { - private static final class TrailingHttpHeadersNameValidator implements - DefaultHeaders.NameValidator { - - private static final TrailingHttpHeadersNameValidator INSTANCE = new TrailingHttpHeadersNameValidator(); - + private static final NameValidator TrailerNameValidator = new NameValidator() { @Override - public void validate(CharSequence name) { - HeaderNameValidator.INSTANCE.validate(name); - if (HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(name) - || HttpHeaderNames.TRANSFER_ENCODING.equalsIgnoreCase(name) - || HttpHeaderNames.TRAILER.equalsIgnoreCase(name)) { + public void validateName(CharSequence name) { + DefaultHttpHeaders.HttpNameValidator.validateName(name); + if (HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(name) + || HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(name) + || HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(name)) { throw new IllegalArgumentException("prohibited trailing header: " + name); } } - } + }; + @SuppressWarnings({ "unchecked" }) TrailingHttpHeaders(boolean validate) { - super(validate, - validate ? TrailingHttpHeadersNameValidator.INSTANCE : DefaultTextHeaders.NO_NAME_VALIDATOR, - false); + super(validate, validate ? TrailerNameValidator : NameValidator.NOT_NULL); } } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/EmptyHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/EmptyHttpHeaders.java new file mode 100644 index 0000000000..f4c7392aac --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/EmptyHttpHeaders.java @@ -0,0 +1,158 @@ +/* + * 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 java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +public class EmptyHttpHeaders extends HttpHeaders { + static final Iterator> EMPTY_CHARS_ITERATOR = + Collections.>emptyList().iterator(); + + public static final EmptyHttpHeaders INSTANCE = new EmptyHttpHeaders(); + + protected EmptyHttpHeaders() { + } + + @Override + public String get(String name) { + return null; + } + + @Override + public Integer getInt(CharSequence name) { + return null; + } + + @Override + public int getInt(CharSequence name, int defaultValue) { + return defaultValue; + } + + @Override + public Short getShort(CharSequence name) { + return null; + } + + @Override + public short getShort(CharSequence name, short defaultValue) { + return defaultValue; + } + + @Override + public Long getTimeMillis(CharSequence name) { + return null; + } + + @Override + public long getTimeMillis(CharSequence name, long defaultValue) { + return defaultValue; + } + + @Override + public List getAll(String name) { + return Collections.emptyList(); + } + + @Override + public List> entries() { + return Collections.emptyList(); + } + + @Override + public boolean contains(String name) { + return false; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public int size() { + return 0; + } + + @Override + public Set names() { + return Collections.emptySet(); + } + + @Override + public HttpHeaders add(String name, Object value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public HttpHeaders add(String name, Iterable values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public HttpHeaders addInt(CharSequence name, int value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public HttpHeaders addShort(CharSequence name, short value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public HttpHeaders set(String name, Object value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public HttpHeaders set(String name, Iterable values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public HttpHeaders setInt(CharSequence name, int value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public HttpHeaders setShort(CharSequence name, short value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public HttpHeaders remove(String name) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public HttpHeaders clear() { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Iterator> iterator() { + return entries().iterator(); + } + + @Override + public Iterator> iteratorCharSequence() { + return EMPTY_CHARS_ITERATOR; + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientUpgradeHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientUpgradeHandler.java index 75c9d5d056..6b4d453314 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientUpgradeHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpClientUpgradeHandler.java @@ -220,7 +220,7 @@ public class HttpClientUpgradeHandler extends HttpObjectAggregator implements Ch throw new IllegalStateException( "Switching Protocols response missing UPGRADE header"); } - if (!AsciiString.equalsIgnoreCase(upgradeCodec.protocol(), upgradeHeader)) { + if (!AsciiString.contentEqualsIgnoreCase(upgradeCodec.protocol(), upgradeHeader)) { throw new IllegalStateException( "Switching Protocols response with unexpected UPGRADE protocol: " + upgradeHeader); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java index 42dd05b05b..6c90b0cad6 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java @@ -96,7 +96,7 @@ public class HttpContentCompressor extends HttpContentEncoder { protected Result beginEncode(HttpResponse headers, String acceptEncoding) throws Exception { String contentEncoding = headers.headers().get(HttpHeaderNames.CONTENT_ENCODING); if (contentEncoding != null && - !HttpHeaderValues.IDENTITY.equalsIgnoreCase(contentEncoding)) { + !HttpHeaderValues.IDENTITY.contentEqualsIgnoreCase(contentEncoding)) { return null; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java index baddaa7538..05fbcb6d72 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecompressor.java @@ -15,6 +15,10 @@ */ package io.netty.handler.codec.http; +import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE; +import static io.netty.handler.codec.http.HttpHeaderValues.GZIP; +import static io.netty.handler.codec.http.HttpHeaderValues.X_DEFLATE; +import static io.netty.handler.codec.http.HttpHeaderValues.X_GZIP; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.codec.compression.ZlibCodecFactory; import io.netty.handler.codec.compression.ZlibWrapper; @@ -47,16 +51,13 @@ public class HttpContentDecompressor extends HttpContentDecoder { @Override protected EmbeddedChannel newContentDecoder(String contentEncoding) throws Exception { - if ("gzip".equalsIgnoreCase(contentEncoding) || "x-gzip".equalsIgnoreCase(contentEncoding)) { + if (GZIP.contentEqualsIgnoreCase(contentEncoding) || + X_GZIP.contentEqualsIgnoreCase(contentEncoding)) { return new EmbeddedChannel(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP)); } - if ("deflate".equalsIgnoreCase(contentEncoding) || "x-deflate".equalsIgnoreCase(contentEncoding)) { - ZlibWrapper wrapper; - if (strict) { - wrapper = ZlibWrapper.ZLIB; - } else { - wrapper = ZlibWrapper.ZLIB_OR_NONE; - } + if (DEFLATE.contentEqualsIgnoreCase(contentEncoding) || + X_DEFLATE.contentEqualsIgnoreCase(contentEncoding)) { + final ZlibWrapper wrapper = strict ? ZlibWrapper.ZLIB : ZlibWrapper.ZLIB_OR_NONE; // To be strict, 'deflate' means ZLIB, but some servers were not implemented correctly. return new EmbeddedChannel(ZlibCodecFactory.newZlibDecoder(wrapper)); } 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 c54f5af9af..f4efc56932 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 @@ -32,14 +32,14 @@ public final class HttpHeaderUtil { */ public static boolean isKeepAlive(HttpMessage message) { CharSequence connection = message.headers().get(HttpHeaderNames.CONNECTION); - if (connection != null && HttpHeaderValues.CLOSE.equalsIgnoreCase(connection)) { + if (connection != null && HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(connection)) { return false; } if (message.protocolVersion().isKeepAliveDefault()) { - return !HttpHeaderValues.CLOSE.equalsIgnoreCase(connection); + return !HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(connection); } else { - return HttpHeaderValues.KEEP_ALIVE.equalsIgnoreCase(connection); + return HttpHeaderValues.KEEP_ALIVE.contentEqualsIgnoreCase(connection); } } @@ -193,7 +193,7 @@ public final class HttpHeaderUtil { if (value == null) { return false; } - if (HttpHeaderValues.CONTINUE.equalsIgnoreCase(value)) { + if (HttpHeaderValues.CONTINUE.contentEqualsIgnoreCase(value)) { return true; } @@ -231,7 +231,6 @@ public final class HttpHeaderUtil { m.headers().add(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); m.headers().remove(HttpHeaderNames.CONTENT_LENGTH); } else { - // Make a copy to be able to modify values while iterating List encodings = m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING); if (encodings.isEmpty()) { return; @@ -240,7 +239,7 @@ public final class HttpHeaderUtil { Iterator valuesIt = values.iterator(); while (valuesIt.hasNext()) { CharSequence value = valuesIt.next(); - if (HttpHeaderValues.CHUNKED.equalsIgnoreCase(value)) { + if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(value)) { valuesIt.remove(); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java index ca4129f851..6a7057bd26 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java @@ -17,11 +17,11 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; +import io.netty.handler.codec.Headers; import io.netty.util.AsciiString; import java.text.ParseException; import java.util.Calendar; -import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -36,135 +36,11 @@ import static io.netty.util.internal.ObjectUtil.checkNotNull; * commonly used utility methods that accesses an {@link HttpMessage}. */ public abstract class HttpHeaders implements Iterable> { - static final Iterator> EMPTY_CHARS_ITERATOR = - Collections.>emptyList().iterator(); - - public static final HttpHeaders EMPTY_HEADERS = new HttpHeaders() { - @Override - public String get(String name) { - return null; - } - - @Override - public Integer getInt(CharSequence name) { - return null; - } - - @Override - public int getInt(CharSequence name, int defaultValue) { - return defaultValue; - } - - @Override - public Short getShort(CharSequence name) { - return null; - } - - @Override - public short getShort(CharSequence name, short defaultValue) { - return defaultValue; - } - - @Override - public Long getTimeMillis(CharSequence name) { - return null; - } - - @Override - public long getTimeMillis(CharSequence name, long defaultValue) { - return defaultValue; - } - - @Override - public List getAll(String name) { - return Collections.emptyList(); - } - - @Override - public List> entries() { - return Collections.emptyList(); - } - - @Override - public boolean contains(String name) { - return false; - } - - @Override - public boolean isEmpty() { - return true; - } - - @Override - public int size() { - return 0; - } - - @Override - public Set names() { - return Collections.emptySet(); - } - - @Override - public HttpHeaders add(String name, Object value) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public HttpHeaders add(String name, Iterable values) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public HttpHeaders addInt(CharSequence name, int value) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public HttpHeaders addShort(CharSequence name, short value) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public HttpHeaders set(String name, Object value) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public HttpHeaders set(String name, Iterable values) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public HttpHeaders setInt(CharSequence name, int value) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public HttpHeaders setShort(CharSequence name, short value) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public HttpHeaders remove(String name) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public HttpHeaders clear() { - throw new UnsupportedOperationException("read only"); - } - - @Override - public Iterator> iterator() { - return entries().iterator(); - } - - @Override - public Iterator> iteratorCharSequence() { - return EMPTY_CHARS_ITERATOR; - } - }; + /** + * @deprecated Use {@link EmptyHttpHeaders#INSTANCE}. + */ + @Deprecated + public static final HttpHeaders EMPTY_HEADERS = EmptyHttpHeaders.INSTANCE; /** * @deprecated Use {@link HttpHeaderNames} instead. @@ -1261,7 +1137,7 @@ public abstract class HttpHeaders implements Iterable> */ @Deprecated public static boolean equalsIgnoreCase(CharSequence name1, CharSequence name2) { - return AsciiString.equalsIgnoreCase(name1, name2); + return AsciiString.contentEqualsIgnoreCase(name1, name2); } static void encode(HttpHeaders headers, ByteBuf buf) throws Exception { @@ -1305,28 +1181,36 @@ public abstract class HttpHeaders implements Iterable> protected HttpHeaders() { } /** + * @deprecated Use {@link #get(CharSequence)} * @see {@link #get(CharSequence)} */ + @Deprecated public abstract String get(String name); /** + * @deprecated Use {@link #getAsString(CharSequence)} + *

* 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. * * @param name The name of the header to search * @return The first header value or {@code null} if there is no such header */ + @Deprecated public String get(CharSequence name) { return get(name.toString()); } /** + * @deprecated Future releases will use {@link CharSequence} instead of {@link String}. + *

* 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. * * @param name The name of the header to search * @return The first header value or {@code defaultValue} if there is no such header */ + @Deprecated public String get(CharSequence name, String defaultValue) { String value = get(name); if (value == null) { @@ -1401,39 +1285,55 @@ public abstract class HttpHeaders implements Iterable> /** * @see {@link #getAll(CharSequence)} */ + @Deprecated public abstract List getAll(String name); /** + * @deprecated Use {@link #getAllAsString(CharSequence)} + *

* Returns the values of headers with the specified name * * @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 */ + @Deprecated public List getAll(CharSequence name) { return getAll(name.toString()); } /** + * @deprecated Use {@link #iteratorCharSequence()} + *

* 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. */ + @Deprecated public abstract List> entries(); /** + * @deprecated Use {@link #contains(CharSequence)} + *

* @see {@link #contains(CharSequence)} */ + @Deprecated public abstract boolean contains(String name); /** - * @deprecated Use {@link #iteratorCharSequence()}. + * @deprecated It is preferred to use {@link #iteratorCharSequence()} unless you need {@link String}. + * If {@link String} is required then use {@link #iteratorAsString()}. + *

* {@inheritDoc} */ @Override @Deprecated public abstract Iterator> iterator(); + /** + * @deprecated In future releases this method will be renamed to {@code iterator()}. + * @return Iterator over the name/value header pairs. + */ @Deprecated public abstract Iterator> iteratorCharSequence(); @@ -1458,18 +1358,24 @@ public abstract class HttpHeaders implements Iterable> public abstract int size(); /** + * @deprecated Future releases will return a {@link Set} of type {@link CharSequence}. + *

* 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. */ + @Deprecated public abstract Set names(); /** - * @see {@link #add(CharSequence, Object)} + * @deprecated Use {@link #add(CharSequence, Object)} */ + @Deprecated public abstract HttpHeaders add(String name, Object value); /** + * @deprecated Future releases will have a {@code addObject} method. + *

* Adds a new header with the specified name and value. * * If the specified value is not a {@link String}, it is converted @@ -1482,16 +1388,20 @@ public abstract class HttpHeaders implements Iterable> * * @return {@code this} */ + @Deprecated public HttpHeaders add(CharSequence name, Object value) { return add(name.toString(), value); } /** - * @see {@link #add(CharSequence, Iterable)} + * @deprecated Use {@link #add(CharSequence, Iterable)} */ + @Deprecated public abstract HttpHeaders add(String name, Iterable values); /** + * @deprecated Future release will have an {@code addObject(...)}. + *

* Adds a new header with the specified name and values. * * This getMethod can be represented approximately as the following code: @@ -1508,6 +1418,7 @@ public abstract class HttpHeaders implements Iterable> * @param values The values of the headers being set * @return {@code this} */ + @Deprecated public HttpHeaders add(CharSequence name, Iterable values) { return add(name.toString(), values); } @@ -1544,11 +1455,14 @@ public abstract class HttpHeaders implements Iterable> public abstract HttpHeaders addShort(CharSequence name, short value); /** - * @see {@link #set(CharSequence, Object)} + * @deprecated Use {@link #set(CharSequence, Object)} */ + @Deprecated public abstract HttpHeaders set(String name, Object value); /** + * @deprecated Future release will have a {@code setObject(...)}. + *

* Sets a header with the specified name and value. * * If there is an existing header with the same name, it is removed. @@ -1561,16 +1475,20 @@ public abstract class HttpHeaders implements Iterable> * @param value The value of the header being set * @return {@code this} */ + @Deprecated public HttpHeaders set(CharSequence name, Object value) { return set(name.toString(), value); } /** - * @see {@link #set(CharSequence, Iterable)} + * @deprecated {@link #set(CharSequence, Iterable)} */ + @Deprecated public abstract HttpHeaders set(String name, Iterable values); /** + * @deprecated Future release will have a {@code setObject(...)}. + *

* Sets a header with the specified name and values. * * If there is an existing header with the same name, it is removed. @@ -1589,6 +1507,7 @@ public abstract class HttpHeaders implements Iterable> * @param values The values of the headers being set * @return {@code this} */ + @Deprecated public HttpHeaders set(CharSequence name, Iterable values) { return set(name.toString(), values); } @@ -1649,16 +1568,20 @@ public abstract class HttpHeaders implements Iterable> public abstract HttpHeaders setShort(CharSequence name, short value); /** - * @see {@link #remove(CharSequence)} + * @deprecated {@link #remove(CharSequence)} */ + @Deprecated public abstract HttpHeaders remove(String name); /** + * @deprecated Future releases the signature will change. + *

* Removes the header with the specified name. * * @param name The name of the header to remove * @return {@code this} */ + @Deprecated public HttpHeaders remove(CharSequence name) { return remove(name.toString()); } @@ -1694,12 +1617,38 @@ public abstract class HttpHeaders implements Iterable> } /** - * Returns {@code true} if a header with the name and value exists. - * - * @param name the headername - * @param value the value - * @param ignoreCase {@code true} if case should be ignored - * @return contains {@code true} if it contains it {@code false} otherwise + * {@link Headers#get(Object)} and convert the result to a {@link String}. + * @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. + */ + public final String getAsString(CharSequence name) { + return get(name); + } + + /** + * {@link Headers#getAll(Object)} and convert each element of {@link List} to a {@link String}. + * @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. + */ + public final List getAllAsString(CharSequence name) { + return getAll(name); + } + + /** + * {@link Iterator} that converts each {@link Entry}'s key and value to a {@link String}. + */ + public final Iterator> iteratorAsString() { + return iterator(); + } + + /** + * Returns {@code true} if a header with the {@code name} and {@code value} exists, {@code false} otherwise. + *

+ * If {@code ignoreCase} is {@code true} then a case insensitive compare is done on the value. + * @param name the name of the header to find + * @param value the value of the header to find + * @param ignoreCase {@code true} then a case insensitive compare is run to compare values. + * otherwise a case sensitive compare is run to compare values. */ public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { return contains(name.toString(), value.toString(), ignoreCase); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java index 7af32564ad..2aef9409e2 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java @@ -21,6 +21,8 @@ import io.netty.util.CharsetUtil; import java.util.HashMap; import java.util.Map; +import static io.netty.util.internal.ObjectUtil.checkNotNull; + /** * The request getMethod of HTTP or its derived protocols, such as * RTSP and @@ -86,8 +88,7 @@ public class HttpMethod implements Comparable { */ public static final HttpMethod CONNECT = new HttpMethod("CONNECT", true); - private static final Map methodMap = - new HashMap(); + private static final Map methodMap = new HashMap(); static { methodMap.put(OPTIONS.toString(), OPTIONS); @@ -107,21 +108,8 @@ public class HttpMethod implements Comparable { * will be returned. Otherwise, a new instance will be returned. */ public static HttpMethod valueOf(String name) { - if (name == null) { - throw new NullPointerException("name"); - } - - name = name.trim(); - if (name.isEmpty()) { - throw new IllegalArgumentException("empty name"); - } - HttpMethod result = methodMap.get(name); - if (result != null) { - return result; - } else { - return new HttpMethod(name); - } + return result != null ? result : new HttpMethod(name); } private final String name; @@ -143,7 +131,7 @@ public class HttpMethod implements Comparable { throw new NullPointerException("name"); } - name = name.trim(); + name = checkNotNull(name, "name").trim(); if (name.isEmpty()) { throw new IllegalArgumentException("empty name"); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java index 3db9f843c1..6ce2128b47 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java @@ -625,9 +625,9 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder { } else { splitHeader(line); CharSequence headerName = name; - if (!HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(headerName) && - !HttpHeaderNames.TRANSFER_ENCODING.equalsIgnoreCase(headerName) && - !HttpHeaderNames.TRAILER.equalsIgnoreCase(headerName)) { + if (!HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(headerName) && + !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(headerName) && + !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(headerName)) { trailer.trailingHeaders().add(headerName, value); } lastHeader = name; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java index 182943140e..21f36a1cde 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java @@ -689,13 +689,13 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest return null; } String[] contents = splitMultipartHeader(newline); - if (HttpHeaderNames.CONTENT_DISPOSITION.equalsIgnoreCase(contents[0])) { + if (HttpHeaderNames.CONTENT_DISPOSITION.contentEqualsIgnoreCase(contents[0])) { boolean checkSecondArg; if (currentStatus == MultiPartStatus.DISPOSITION) { - checkSecondArg = HttpHeaderValues.FORM_DATA.equalsIgnoreCase(contents[1]); + checkSecondArg = HttpHeaderValues.FORM_DATA.contentEqualsIgnoreCase(contents[1]); } else { - checkSecondArg = HttpHeaderValues.ATTACHMENT.equalsIgnoreCase(contents[1]) - || HttpHeaderValues.FILE.equalsIgnoreCase(contents[1]); + checkSecondArg = HttpHeaderValues.ATTACHMENT.contentEqualsIgnoreCase(contents[1]) + || HttpHeaderValues.FILE.contentEqualsIgnoreCase(contents[1]); } if (checkSecondArg) { // read next values and store them in the map as Attribute @@ -723,7 +723,7 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest currentFieldAttributes.put(attribute.getName(), attribute); } } - } else if (HttpHeaderNames.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(contents[0])) { + } else if (HttpHeaderNames.CONTENT_TRANSFER_ENCODING.contentEqualsIgnoreCase(contents[0])) { Attribute attribute; try { attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_TRANSFER_ENCODING.toString(), @@ -733,8 +733,9 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest } catch (IllegalArgumentException e) { throw new ErrorDataDecoderException(e); } + currentFieldAttributes.put(HttpHeaderNames.CONTENT_TRANSFER_ENCODING, attribute); - } else if (HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(contents[0])) { + } else if (HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(contents[0])) { Attribute attribute; try { attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_LENGTH.toString(), @@ -744,10 +745,11 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest } catch (IllegalArgumentException e) { throw new ErrorDataDecoderException(e); } + currentFieldAttributes.put(HttpHeaderNames.CONTENT_LENGTH, attribute); - } else if (HttpHeaderNames.CONTENT_TYPE.equalsIgnoreCase(contents[0])) { + } else if (HttpHeaderNames.CONTENT_TYPE.contentEqualsIgnoreCase(contents[0])) { // Take care of possible "multipart/mixed" - if (HttpHeaderValues.MULTIPART_MIXED.equalsIgnoreCase(contents[1])) { + if (HttpHeaderValues.MULTIPART_MIXED.contentEqualsIgnoreCase(contents[1])) { if (currentStatus == MultiPartStatus.DISPOSITION) { String values = StringUtil.substringAfter(contents[2], '='); multipartMixedBoundary = "--" + values; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java index b7f87d16e8..591c7320cc 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java @@ -753,7 +753,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { if (transferEncoding != null) { headers.remove(HttpHeaderNames.TRANSFER_ENCODING); for (CharSequence v : transferEncoding) { - if (HttpHeaderValues.CHUNKED.equalsIgnoreCase(v)) { + if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(v)) { // ignore } else { headers.add(HttpHeaderNames.TRANSFER_ENCODING, v); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index 126d590ab8..b1de3e3bbd 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -201,13 +201,13 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { HttpHeaders headers = response.headers(); CharSequence upgrade = headers.get(HttpHeaderNames.UPGRADE); - if (!WEBSOCKET.equalsIgnoreCase(upgrade)) { + if (!WEBSOCKET.contentEqualsIgnoreCase(upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade); } CharSequence connection = headers.get(HttpHeaderNames.CONNECTION); - if (!HttpHeaderValues.UPGRADE.equalsIgnoreCase(connection)) { + if (!HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java index 7d35aaecdd..820a080876 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java @@ -206,12 +206,12 @@ public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker { } CharSequence upgrade = headers.get(HttpHeaderNames.UPGRADE); - if (!HttpHeaderValues.WEBSOCKET.equalsIgnoreCase(upgrade)) { + if (!HttpHeaderValues.WEBSOCKET.contentEqualsIgnoreCase(upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade); } CharSequence connection = headers.get(HttpHeaderNames.CONNECTION); - if (!HttpHeaderValues.UPGRADE.equalsIgnoreCase(connection)) { + if (!HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index 666a36b27a..a8ff02ede0 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -207,12 +207,12 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { } CharSequence upgrade = headers.get(HttpHeaderNames.UPGRADE); - if (!HttpHeaderValues.WEBSOCKET.equalsIgnoreCase(upgrade)) { + if (!HttpHeaderValues.WEBSOCKET.contentEqualsIgnoreCase(upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade); } CharSequence connection = headers.get(HttpHeaderNames.CONNECTION); - if (!HttpHeaderValues.UPGRADE.equalsIgnoreCase(connection)) { + if (!HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index 0be0886401..52fa9a5df8 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -217,12 +217,12 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { } CharSequence upgrade = headers.get(HttpHeaderNames.UPGRADE); - if (!HttpHeaderValues.WEBSOCKET.equalsIgnoreCase(upgrade)) { + if (!HttpHeaderValues.WEBSOCKET.contentEqualsIgnoreCase(upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade); } CharSequence connection = headers.get(HttpHeaderNames.CONNECTION); - if (!HttpHeaderValues.UPGRADE.equalsIgnoreCase(connection)) { + if (!HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java index b3a135b528..d6a17c16ab 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java @@ -30,7 +30,7 @@ import io.netty.handler.codec.http.HttpResponseStatus; import java.util.regex.Pattern; -import static io.netty.handler.codec.http.HttpVersion.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; /** *

@@ -107,8 +107,8 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker { protected FullHttpResponse newHandshakeResponse(FullHttpRequest req, HttpHeaders headers) { // Serve the WebSocket handshake request. - if (!HttpHeaderValues.UPGRADE.equalsIgnoreCase(req.headers().get(HttpHeaderNames.CONNECTION)) - || !HttpHeaderValues.WEBSOCKET.equalsIgnoreCase(req.headers().get(HttpHeaderNames.UPGRADE))) { + if (!HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(req.headers().get(HttpHeaderNames.CONNECTION)) + || !HttpHeaderValues.WEBSOCKET.contentEqualsIgnoreCase(req.headers().get(HttpHeaderNames.UPGRADE))) { throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade"); } @@ -131,6 +131,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker { // New handshake getMethod with a challenge: res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN, req.headers().get(HttpHeaderNames.ORIGIN)); res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_LOCATION, uri()); + String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); if (subprotocols != null) { String selectedSubprotocol = selectSubprotocol(subprotocols); @@ -160,6 +161,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker { // Old Hixie 75 handshake getMethod with no challenge: res.headers().add(HttpHeaderNames.WEBSOCKET_ORIGIN, req.headers().get(HttpHeaderNames.ORIGIN)); res.headers().add(HttpHeaderNames.WEBSOCKET_LOCATION, uri()); + String protocol = req.headers().get(HttpHeaderNames.WEBSOCKET_PROTOCOL); if (protocol != null) { res.headers().add(HttpHeaderNames.WEBSOCKET_PROTOCOL, selectSubprotocol(protocol)); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java index c575fe8cf6..f753714e0d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java @@ -142,6 +142,7 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker { res.headers().add(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET); res.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE); res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept); + String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); if (subprotocols != null) { String selectedSubprotocol = selectSubprotocol(subprotocols); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java index a4900a43a6..b27323db01 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java @@ -141,6 +141,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { res.headers().add(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET); res.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE); res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept); + String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); if (subprotocols != null) { String selectedSubprotocol = selectSubprotocol(subprotocols); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java index b010dddb47..25cb85bc1e 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java @@ -139,6 +139,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { res.headers().add(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET); res.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE); res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept); + String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); if (subprotocols != null) { String selectedSubprotocol = selectSubprotocol(subprotocols); 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 270812c713..4ebc75f975 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 @@ -15,18 +15,28 @@ */ package io.netty.handler.codec.spdy; -import io.netty.handler.codec.DefaultTextHeaders; -import io.netty.handler.codec.TextHeaders; +import io.netty.handler.codec.CharSequenceValueConverter; +import io.netty.handler.codec.DefaultHeaders; +import io.netty.handler.codec.Headers; +import io.netty.handler.codec.HeadersUtils; -import java.util.LinkedHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; -public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeaders { +import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER; +import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER; + +public class DefaultSpdyHeaders extends DefaultHeaders implements SpdyHeaders { + private static final NameValidator SpydNameValidator = new NameValidator() { + @Override + public void validateName(CharSequence name) { + SpdyCodecUtil.validateHeaderName(name); + } + }; public DefaultSpdyHeaders() { - super(new LinkedHashMap(), - HeaderNameValidator.INSTANCE, - HeaderValueConverterAndValidator.INSTANCE, - false); + super(CASE_INSENSITIVE_HASHER, HeaderValueConverterAndValidator.INSTANCE, SpydNameValidator); } @Override @@ -120,7 +130,7 @@ public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeader } @Override - public SpdyHeaders add(TextHeaders headers) { + public SpdyHeaders add(Headers headers) { super.add(headers); return this; } @@ -216,13 +226,13 @@ public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeader } @Override - public SpdyHeaders set(TextHeaders headers) { + public SpdyHeaders set(Headers headers) { super.set(headers); return this; } @Override - public SpdyHeaders setAll(TextHeaders headers) { + public SpdyHeaders setAll(Headers headers) { super.setAll(headers); return this; } @@ -233,23 +243,38 @@ public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeader return this; } - private static class HeaderNameValidator implements NameValidator { - - public static final HeaderNameValidator INSTANCE = new HeaderNameValidator(); - - @Override - public void validate(CharSequence name) { - SpdyCodecUtil.validateHeaderName(name); - } + @Override + public String getAsString(CharSequence name) { + return HeadersUtils.getAsString(this, name); } - private static class HeaderValueConverterAndValidator extends CharSequenceConverter { + @Override + public List getAllAsString(CharSequence name) { + return HeadersUtils.getAllAsString(this, name); + } + @Override + public Iterator> iteratorAsString() { + return HeadersUtils.iteratorAsString(this); + } + + @Override + public boolean contains(CharSequence name, CharSequence value) { + return contains(name, value, false); + } + + @Override + public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { + return contains(name, value, + ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER); + } + + private static final class HeaderValueConverterAndValidator extends CharSequenceValueConverter { public static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator(); @Override public CharSequence convertObject(Object value) { - CharSequence seq; + final CharSequence seq; if (value instanceof CharSequence) { seq = (CharSequence) value; } else { diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java index e82fccef2d..ebbcd52340 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java @@ -15,14 +15,18 @@ */ package io.netty.handler.codec.spdy; -import io.netty.handler.codec.TextHeaders; +import io.netty.handler.codec.Headers; import io.netty.util.AsciiString; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; + /** * Provides the constants for the standard SPDY HTTP header names and commonly * used utility methods that access a {@link SpdyHeadersFrame}. */ -public interface SpdyHeaders extends TextHeaders { +public interface SpdyHeaders extends Headers { /** * SPDY HTTP header names @@ -102,7 +106,7 @@ public interface SpdyHeaders extends TextHeaders { SpdyHeaders addTimeMillis(CharSequence name, long value); @Override - SpdyHeaders add(TextHeaders headers); + SpdyHeaders add(Headers headers); @Override SpdyHeaders set(CharSequence name, CharSequence value); @@ -150,11 +154,41 @@ public interface SpdyHeaders extends TextHeaders { SpdyHeaders setObject(CharSequence name, Object... values); @Override - SpdyHeaders set(TextHeaders headers); + SpdyHeaders set(Headers headers); @Override - SpdyHeaders setAll(TextHeaders headers); + SpdyHeaders setAll(Headers headers); @Override SpdyHeaders clear(); + + /** + * {@link Headers#get(Object)} and convert the result to a {@link String}. + * @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. + */ + String getAsString(CharSequence name); + + /** + * {@link Headers#getAll(Object)} and convert each element of {@link List} to a {@link String}. + * @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 getAllAsString(CharSequence name); + + /** + * {@link #iterator()} that converts each {@link Entry}'s key and value to a {@link String}. + */ + Iterator> iteratorAsString(); + + /** + * Returns {@code true} if a header with the {@code name} and {@code value} exists, {@code false} otherwise. + *

+ * If {@code ignoreCase} is {@code true} then a case insensitive compare is done on the value. + * @param name the name of the header to find + * @param value the value of the header to find + * @param ignoreCase {@code true} then a case insensitive compare is run to compare values. + * otherwise a case sensitive compare is run to compare values. + */ + boolean contains(CharSequence name, CharSequence value, boolean ignoreCase); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java index de02449849..efd5c895dd 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java @@ -350,9 +350,9 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { throws Exception { // Create the first line of the request from the name/value pairs SpdyHeaders headers = requestFrame.headers(); - HttpMethod method = HttpMethod.valueOf(headers.getAndConvert(METHOD)); - String url = headers.getAndConvert(PATH); - HttpVersion httpVersion = HttpVersion.valueOf(headers.getAndConvert(VERSION)); + HttpMethod method = HttpMethod.valueOf(headers.getAsString(METHOD)); + String url = headers.getAsString(PATH); + HttpVersion httpVersion = HttpVersion.valueOf(headers.getAsString(VERSION)); headers.remove(METHOD); headers.remove(PATH); headers.remove(VERSION); @@ -386,7 +386,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { // Create the first line of the response from the name/value pairs SpdyHeaders headers = responseFrame.headers(); HttpResponseStatus status = HttpResponseStatus.parseLine(headers.get(STATUS)); - HttpVersion version = HttpVersion.valueOf(headers.getAndConvert(VERSION)); + HttpVersion version = HttpVersion.valueOf(headers.getAsString(VERSION)); headers.remove(STATUS); headers.remove(VERSION); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/CombinedHttpHeadersTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/CombinedHttpHeadersTest.java new file mode 100644 index 0000000000..3473c70ef5 --- /dev/null +++ b/codec-http/src/test/java/io/netty/handler/codec/http/CombinedHttpHeadersTest.java @@ -0,0 +1,201 @@ +/* + * 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 io.netty.handler.codec.http.HttpHeadersTestUtils.HeaderValue; +import org.junit.Test; + +import java.util.Collections; + +import static io.netty.util.AsciiString.contentEquals; +import static org.junit.Assert.assertTrue; + +public class CombinedHttpHeadersTest { + private static final String HEADER_NAME = "testHeader"; + + @Test + public void addCharSequencesCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asList()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void addCharSequencesCsvWithExistingHeader() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asList()); + headers.add(HEADER_NAME, HeaderValue.FIVE.subset(4)); + assertCsvValues(headers, HeaderValue.FIVE); + } + + @Test + public void addCharSequencesCsvWithValueContainingComma() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.SIX_QUOTED.subset(4)); + assertTrue(contentEquals(HeaderValue.SIX_QUOTED.subsetAsCsvString(4), headers.get(HEADER_NAME))); + assertTrue(contentEquals(HeaderValue.SIX_QUOTED.subsetAsCsvString(4), headers.getAll(HEADER_NAME).get(0))); + } + + @Test + public void addCharSequencesCsvWithValueContainingCommas() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.EIGHT.subset(6)); + assertTrue(contentEquals(HeaderValue.EIGHT.subsetAsCsvString(6), headers.get(HEADER_NAME))); + assertTrue(contentEquals(HeaderValue.EIGHT.subsetAsCsvString(6), headers.getAll(HEADER_NAME).get(0))); + } + + @Test (expected = NullPointerException.class) + public void addCharSequencesCsvNullValue() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + final String value = null; + headers.add(HEADER_NAME, value); + } + + @Test + public void addCharSequencesCsvMultipleTimes() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + for (int i = 0; i < 5; ++i) { + headers.add(HEADER_NAME, "value"); + } + assertTrue(contentEquals("value,value,value,value,value", headers.get(HEADER_NAME))); + } + + @Test + public void addCharSequenceCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + addValues(headers, HeaderValue.ONE, HeaderValue.TWO, HeaderValue.THREE); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void addCharSequenceCsvSingleValue() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + addValues(headers, HeaderValue.ONE); + assertCsvValue(headers, HeaderValue.ONE); + } + + @Test + public void addIterableCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asList()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void addIterableCsvWithExistingHeader() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asList()); + headers.add(HEADER_NAME, HeaderValue.FIVE.subset(4)); + assertCsvValues(headers, HeaderValue.FIVE); + } + + @Test + public void addIterableCsvSingleValue() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.ONE.asList()); + assertCsvValue(headers, HeaderValue.ONE); + } + + @Test + public void addIterableCsvEmtpy() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, Collections.emptyList()); + assertTrue(contentEquals("", headers.getAll(HEADER_NAME).get(0))); + } + + @Test + public void addObjectCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + addObjectValues(headers, HeaderValue.ONE, HeaderValue.TWO, HeaderValue.THREE); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void addObjectsCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asList()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void addObjectsIterableCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asList()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void addObjectsCsvWithExistingHeader() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asList()); + headers.add(HEADER_NAME, HeaderValue.FIVE.subset(4)); + assertCsvValues(headers, HeaderValue.FIVE); + } + + @Test + public void setCharSequenceCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.set(HEADER_NAME, HeaderValue.THREE.asList()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void setIterableCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.set(HEADER_NAME, HeaderValue.THREE.asList()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void setObjectObjectsCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.set(HEADER_NAME, HeaderValue.THREE.asList()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void setObjectIterableCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.set(HEADER_NAME, HeaderValue.THREE.asList()); + assertCsvValues(headers, HeaderValue.THREE); + } + + private static CombinedHttpHeaders newCombinedHttpHeaders() { + return new CombinedHttpHeaders(true); + } + + private static void assertCsvValues(final CombinedHttpHeaders headers, final HeaderValue headerValue) { + assertTrue(contentEquals(headerValue.asCsv(), headers.get(HEADER_NAME))); + assertTrue(contentEquals(headerValue.asCsv(), headers.getAll(HEADER_NAME).get(0))); + } + + private static void assertCsvValue(final CombinedHttpHeaders headers, final HeaderValue headerValue) { + assertTrue(contentEquals(headerValue.toString(), headers.get(HEADER_NAME))); + assertTrue(contentEquals(headerValue.toString(), headers.getAll(HEADER_NAME).get(0))); + } + + private static void addValues(final CombinedHttpHeaders headers, HeaderValue... headerValues) { + for (HeaderValue v: headerValues) { + headers.add(HEADER_NAME, v.toString()); + } + } + + private static void addObjectValues(final CombinedHttpHeaders headers, HeaderValue... headerValues) { + for (HeaderValue v: headerValues) { + headers.add(HEADER_NAME, v.toString()); + } + } +} 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 index cfb57b26e9..6caa88bc98 100644 --- 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 @@ -15,15 +15,24 @@ */ package io.netty.handler.codec.http; +import io.netty.handler.codec.http.HttpHeadersTestUtils.HeaderValue; +import io.netty.util.AsciiString; import org.junit.Test; import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import static io.netty.util.AsciiString.contentEquals; import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class DefaultHttpHeadersTest { + private static final String HEADER_NAME = "testHeader"; @Test public void keysShouldBeCaseInsensitive() { @@ -57,4 +66,113 @@ public class DefaultHttpHeadersTest { assertEquals(headers2, headers1); assertEquals(headers1.hashCode(), headers2.hashCode()); } + + @Test + public void testRemoveTransferEncodingIgnoreCase() { + HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + message.headers().set(HttpHeaderNames.TRANSFER_ENCODING, "Chunked"); + assertFalse(message.headers().isEmpty()); + HttpHeaderUtil.setTransferEncodingChunked(message, false); + assertTrue(message.headers().isEmpty()); + } + + // Test for https://github.com/netty/netty/issues/1690 + @Test + public void testGetOperations() { + HttpHeaders headers = new DefaultHttpHeaders(); + headers.add("Foo", "1"); + headers.add("Foo", "2"); + + assertEquals("1", headers.get("Foo")); + + List values = headers.getAll("Foo"); + assertEquals(2, values.size()); + assertEquals("1", values.get(0)); + assertEquals("2", values.get(1)); + } + + @Test + public void testEquansIgnoreCase() { + assertThat(AsciiString.contentEqualsIgnoreCase(null, null), is(true)); + assertThat(AsciiString.contentEqualsIgnoreCase(null, "foo"), is(false)); + assertThat(AsciiString.contentEqualsIgnoreCase("bar", null), is(false)); + assertThat(AsciiString.contentEqualsIgnoreCase("FoO", "fOo"), is(true)); + } + + @Test(expected = NullPointerException.class) + public void testSetNullHeaderValueValidate() { + HttpHeaders headers = new DefaultHttpHeaders(true); + headers.set("test", (CharSequence) null); + } + + @Test(expected = NullPointerException.class) + public void testSetNullHeaderValueNotValidate() { + HttpHeaders headers = new DefaultHttpHeaders(false); + headers.set("test", (CharSequence) null); + } + + @Test + public void addCharSequences() { + final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asList()); + assertDefaultValues(headers, HeaderValue.THREE); + } + + @Test + public void addIterable() { + final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asList()); + assertDefaultValues(headers, HeaderValue.THREE); + } + + @Test + public void addObjects() { + final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asList()); + assertDefaultValues(headers, HeaderValue.THREE); + } + + @Test + public void setCharSequences() { + final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); + headers.set(HEADER_NAME, HeaderValue.THREE.asList()); + assertDefaultValues(headers, HeaderValue.THREE); + } + + @Test + public void setIterable() { + final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); + headers.set(HEADER_NAME, HeaderValue.THREE.asList()); + assertDefaultValues(headers, HeaderValue.THREE); + } + + @Test + public void setObjectObjects() { + final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); + headers.set(HEADER_NAME, HeaderValue.THREE.asList()); + assertDefaultValues(headers, HeaderValue.THREE); + } + + @Test + public void setObjectIterable() { + final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); + headers.set(HEADER_NAME, HeaderValue.THREE.asList()); + assertDefaultValues(headers, HeaderValue.THREE); + } + + private static void assertDefaultValues(final DefaultHttpHeaders headers, final HeaderValue headerValue) { + assertTrue(contentEquals(headerValue.asList().get(0), headers.get(HEADER_NAME))); + List expected = headerValue.asList(); + List actual = headers.getAll(HEADER_NAME); + assertEquals(expected.size(), actual.size()); + Iterator eItr = expected.iterator(); + Iterator aItr = actual.iterator(); + while (eItr.hasNext()) { + assertTrue(contentEquals(eItr.next(), aItr.next())); + } + } + + private static DefaultHttpHeaders newDefaultDefaultHttpHeaders() { + return new DefaultHttpHeaders(true); + } } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeaderUtilTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeaderUtilTest.java new file mode 100644 index 0000000000..631c4fbff1 --- /dev/null +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeaderUtilTest.java @@ -0,0 +1,60 @@ +/* + * 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 io.netty.util.AsciiString; + +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +public class HttpHeaderUtilTest { + + @Test + public void testRemoveTransferEncodingIgnoreCase() { + HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + message.headers().set(HttpHeaderNames.TRANSFER_ENCODING, "Chunked"); + assertFalse(message.headers().isEmpty()); + HttpHeaderUtil.setTransferEncodingChunked(message, false); + assertTrue(message.headers().isEmpty()); + } + + // Test for https://github.com/netty/netty/issues/1690 + @Test + public void testGetOperations() { + HttpHeaders headers = new DefaultHttpHeaders(); + headers.add("Foo", "1"); + headers.add("Foo", "2"); + + assertEquals("1", headers.get("Foo")); + + List values = headers.getAll("Foo"); + assertEquals(2, values.size()); + assertEquals("1", values.get(0)); + assertEquals("2", values.get(1)); + } + + @Test + public void testEquansIgnoreCase() { + assertThat(AsciiString.contentEqualsIgnoreCase(null, null), is(true)); + assertThat(AsciiString.contentEqualsIgnoreCase(null, "foo"), is(false)); + assertThat(AsciiString.contentEqualsIgnoreCase("bar", null), is(false)); + assertThat(AsciiString.contentEqualsIgnoreCase("FoO", "fOo"), is(true)); + } +} diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java index 68e8c29bf2..ac4acec79e 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java @@ -52,10 +52,10 @@ public class HttpHeadersTest { @Test public void testEquansIgnoreCase() { - assertThat(AsciiString.equalsIgnoreCase(null, null), is(true)); - assertThat(AsciiString.equalsIgnoreCase(null, "foo"), is(false)); - assertThat(AsciiString.equalsIgnoreCase("bar", null), is(false)); - assertThat(AsciiString.equalsIgnoreCase("FoO", "fOo"), is(true)); + assertThat(AsciiString.contentEqualsIgnoreCase(null, null), is(true)); + assertThat(AsciiString.contentEqualsIgnoreCase(null, "foo"), is(false)); + assertThat(AsciiString.contentEqualsIgnoreCase("bar", null), is(false)); + assertThat(AsciiString.contentEqualsIgnoreCase("FoO", "fOo"), is(true)); } @Test(expected = NullPointerException.class) diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTestUtils.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTestUtils.java new file mode 100644 index 0000000000..184e1a9bc2 --- /dev/null +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTestUtils.java @@ -0,0 +1,130 @@ +/* + * 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.netty.util.internal.StringUtil.COMMA; +import static io.netty.util.internal.StringUtil.DOUBLE_QUOTE; + +class HttpHeadersTestUtils { + public enum HeaderValue { + UNKNOWN("Unknown", 0), + ONE("One", 1), + TWO("Two", 2), + THREE("Three", 3), + FOUR("Four", 4), + FIVE("Five", 5), + SIX_QUOTED("Six,", 6), + SEVEN_QUOTED("Seven; , GMT", 7), + EIGHT("Eight", 8); + + private final int nr; + private final String value; + private List array; + + HeaderValue(final String value, final int nr) { + this.nr = nr; + this.value = value; + } + + @Override + public String toString() { + return value; + } + + public List asList() { + if (array == null) { + List list = new ArrayList(nr); + for (int i = 1; i <= nr; i++) { + list.add(of(i).toString()); + } + array = list; + } + return array; + } + + public List subset(int from) { + assert from > 0; + --from; + final int size = nr - from; + final int end = from + size; + List list = new ArrayList(size); + List fullList = asList(); + for (int i = from; i < end; ++i) { + list.add(fullList.get(i)); + } + return list; + } + + public String subsetAsCsvString(final int from) { + final List subset = subset(from); + return asCsv(subset); + } + + public String asCsv(final List arr) { + if (arr == null || arr.isEmpty()) { + return ""; + } + final StringBuilder sb = new StringBuilder(arr.size() * 10); + final int end = arr.size() - 1; + for (int i = 0; i < end; ++i) { + quoted(sb, arr.get(i)).append(COMMA); + } + quoted(sb, arr.get(end)); + return sb.toString(); + } + + public CharSequence asCsv() { + return asCsv(asList()); + } + + private static StringBuilder quoted(final StringBuilder sb, final CharSequence value) { + if (contains(value, COMMA) && !contains(value, DOUBLE_QUOTE)) { + return sb.append(DOUBLE_QUOTE).append(value).append(DOUBLE_QUOTE); + } + return sb.append(value); + } + + private static boolean contains(CharSequence value, char c) { + for (int i = 0; i < value.length(); ++i) { + if (value.charAt(i) == c) { + return true; + } + } + return false; + } + + private static final Map MAP; + + static { + final Map map = new HashMap(); + for (HeaderValue v : values()) { + final int nr = v.nr; + map.put(Integer.valueOf(nr), v); + } + MAP = map; + } + + public static HeaderValue of(final int nr) { + final HeaderValue v = MAP.get(Integer.valueOf(nr)); + return v == null ? UNKNOWN : v; + } + } +} 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 5c8ece78c4..d12e1451ae 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 @@ -84,7 +84,7 @@ public class SpdySessionHandlerTest { assertEquals(last, spdyHeadersFrame.isLast()); for (CharSequence name: headers.names()) { List expectedValues = headers.getAll(name); - List receivedValues = new ArrayList(spdyHeadersFrame.headers().getAll(name)); + List receivedValues = spdyHeadersFrame.headers().getAll(name); assertTrue(receivedValues.containsAll(expectedValues)); receivedValues.removeAll(expectedValues); assertTrue(receivedValues.isEmpty()); 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 a62d32ad2b..6aef682649 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,19 +14,16 @@ */ package io.netty.handler.codec.http2; -import io.netty.handler.codec.BinaryHeaders; -import io.netty.handler.codec.DefaultBinaryHeaders; +import io.netty.handler.codec.ByteStringValueConverter; import io.netty.handler.codec.DefaultHeaders; +import io.netty.handler.codec.Headers; import io.netty.util.ByteString; -import java.io.Serializable; -import java.util.Comparator; -import java.util.List; -import java.util.TreeMap; -public class DefaultHttp2Headers extends DefaultBinaryHeaders implements Http2Headers { +public class DefaultHttp2Headers extends DefaultHeaders implements Http2Headers { + private HeaderEntry firstNonPseudo = head; public DefaultHttp2Headers() { - super(new TreeMap(Http2HeaderNameComparator.INSTANCE)); + super(ByteStringValueConverter.INSTANCE); } @Override @@ -120,7 +117,7 @@ public class DefaultHttp2Headers extends DefaultBinaryHeaders implements Http2He } @Override - public Http2Headers add(BinaryHeaders headers) { + public Http2Headers add(Headers headers) { super.add(headers); return this; } @@ -216,13 +213,13 @@ public class DefaultHttp2Headers extends DefaultBinaryHeaders implements Http2He } @Override - public Http2Headers set(BinaryHeaders headers) { + public Http2Headers set(Headers headers) { super.set(headers); return this; } @Override - public Http2Headers setAll(BinaryHeaders headers) { + public Http2Headers setAll(Headers headers) { super.setAll(headers); return this; } @@ -289,43 +286,37 @@ public class DefaultHttp2Headers extends DefaultBinaryHeaders implements Http2He } @Override - public int hashCode() { - return size(); + protected final HeaderEntry newHeaderEntry(int h, ByteString name, ByteString value, + HeaderEntry next) { + return new Http2HeaderEntry(h, name, value, next); } - @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 final class Http2HeaderEntry extends HeaderEntry { + protected Http2HeaderEntry(int hash, ByteString key, ByteString value, HeaderEntry next) { + super(hash, key); + this.value = value; + this.next = next; - 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); + // Make sure the pseudo headers fields are first in iteration order + if (!key.isEmpty() && key.byteAt(0) == ':') { + after = firstNonPseudo; + before = firstNonPseudo.before(); + } else { + after = head; + before = head.before(); + if (firstNonPseudo == head) { + firstNonPseudo = this; } } - return delta; + pointNeighborsToThis(); + } + + @Override + protected void remove() { + if (this == firstNonPseudo) { + firstNonPseudo = firstNonPseudo.after(); + } + super.remove(); } } } diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/EmptyHttp2Headers.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/EmptyHttp2Headers.java index e08941ba9f..e55f7554f7 100644 --- a/codec-http2/src/main/java/io/netty/handler/codec/http2/EmptyHttp2Headers.java +++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/EmptyHttp2Headers.java @@ -15,11 +15,11 @@ package io.netty.handler.codec.http2; -import io.netty.handler.codec.BinaryHeaders; -import io.netty.handler.codec.EmptyBinaryHeaders; +import io.netty.handler.codec.EmptyHeaders; +import io.netty.handler.codec.Headers; import io.netty.util.ByteString; -public final class EmptyHttp2Headers extends EmptyBinaryHeaders implements Http2Headers { +public final class EmptyHttp2Headers extends EmptyHeaders implements Http2Headers { public static final EmptyHttp2Headers INSTANCE = new EmptyHttp2Headers(); private EmptyHttp2Headers() { @@ -116,7 +116,7 @@ public final class EmptyHttp2Headers extends EmptyBinaryHeaders implements Http2 } @Override - public Http2Headers add(BinaryHeaders headers) { + public Http2Headers add(Headers headers) { super.add(headers); return this; } @@ -212,13 +212,13 @@ public final class EmptyHttp2Headers extends EmptyBinaryHeaders implements Http2 } @Override - public Http2Headers set(BinaryHeaders headers) { + public Http2Headers set(Headers headers) { super.set(headers); return this; } @Override - public Http2Headers setAll(BinaryHeaders headers) { + public Http2Headers setAll(Headers headers) { super.setAll(headers); return this; } 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 61a8bed800..92381a066b 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 @@ -15,19 +15,19 @@ package io.netty.handler.codec.http2; -import io.netty.handler.codec.BinaryHeaders; -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; +import io.netty.handler.codec.Headers; +import io.netty.util.ByteString; +import io.netty.util.CharsetUtil; + /** * A collection of headers sent or received via HTTP/2. */ -public interface Http2Headers extends BinaryHeaders { +public interface Http2Headers extends Headers { /** * HTTP/2 pseudo-headers names. @@ -129,7 +129,7 @@ public interface Http2Headers extends BinaryHeaders { Http2Headers addTimeMillis(ByteString name, long value); @Override - Http2Headers add(BinaryHeaders headers); + Http2Headers add(Headers headers); @Override Http2Headers set(ByteString name, ByteString value); @@ -177,10 +177,10 @@ public interface Http2Headers extends BinaryHeaders { Http2Headers setTimeMillis(ByteString name, long value); @Override - Http2Headers set(BinaryHeaders headers); + Http2Headers set(Headers headers); @Override - Http2Headers setAll(BinaryHeaders headers); + Http2Headers setAll(Headers headers); @Override Http2Headers clear(); 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 42dbcb116f..a8d60465d8 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 @@ -275,7 +275,7 @@ public final class HttpUtil { out.path(new AsciiString(request.uri())); out.method(new AsciiString(request.method().toString())); - String value = inHeaders.get(HttpHeaderNames.HOST); + String value = inHeaders.getAsString(HttpHeaderNames.HOST); if (value != null) { URI hostUri = URI.create(value); // The authority MUST NOT include the deprecated "userinfo" subcomponent @@ -324,8 +324,8 @@ public final class HttpUtil { 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)) { + if (!aName.contentEqualsIgnoreCase(HttpHeaderNames.TE) || + aValue.contentEqualsIgnoreCase(HttpHeaderValues.TRAILERS)) { out.add(aName, aValue); } } @@ -334,7 +334,7 @@ public final class HttpUtil { } /** - * A visitor which translates HTTP/2 headers to HTTP/1 headers + * Utility which translates HTTP/2 headers to HTTP/1 headers. */ private static final class Http2ToHttpHeaderTranslator { /** 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 a719291537..53d527dd34 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 @@ -14,12 +14,6 @@ */ package io.netty.handler.codec.http2; -import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; -import static io.netty.handler.codec.http2.Http2Exception.connectionError; - -import java.util.Iterator; -import java.util.Map.Entry; - import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.FullHttpMessage; @@ -27,7 +21,12 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.util.AsciiString; import io.netty.util.collection.IntObjectHashMap; import io.netty.util.collection.IntObjectMap; -import io.netty.util.internal.PlatformDependent; + +import java.util.Iterator; +import java.util.Map.Entry; + +import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; +import static io.netty.handler.codec.http2.Http2Exception.connectionError; /** * Translate header/data/priority HTTP/2 frame events into HTTP events. Just as {@link InboundHttp2ToHttpAdapter} @@ -136,14 +135,10 @@ public final class InboundHttp2ToHttpPriorityAdapter extends InboundHttp2ToHttpA * @param http2Headers The target HTTP/2 headers */ private static void addHttpHeadersToHttp2Headers(HttpHeaders httpHeaders, final Http2Headers http2Headers) { - try { - Iterator> iter = httpHeaders.iteratorCharSequence(); - while (iter.hasNext()) { - Entry entry = iter.next(); - http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue())); - } - } catch (Exception ex) { - PlatformDependent.throwException(ex); + Iterator> iter = httpHeaders.iteratorCharSequence(); + while (iter.hasNext()) { + Entry entry = iter.next(); + http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue())); } } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java index 38cdb71a07..fcf0ed1b13 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DataCompressionHttp2Test.java @@ -49,7 +49,6 @@ import java.util.Random; import java.util.concurrent.CountDownLatch; import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT; -import static io.netty.handler.codec.http2.Http2TestUtil.as; import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -67,9 +66,9 @@ import static org.mockito.Mockito.verify; * Test for data decompression in the HTTP/2 codec. */ public class DataCompressionHttp2Test { - private static final AsciiString GET = as("GET"); - private static final AsciiString POST = as("POST"); - private static final AsciiString PATH = as("/some/path"); + private static final AsciiString GET = new AsciiString("GET"); + private static final AsciiString POST = new AsciiString("POST"); + private static final AsciiString PATH = new AsciiString("/some/path"); @Mock private Http2FrameListener serverListener; diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java index d0c464d3c5..b975281602 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java @@ -15,9 +15,29 @@ package io.netty.handler.codec.http2; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import io.netty.buffer.UnpooledByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.util.AsciiString; +import io.netty.util.ByteString; +import io.netty.util.CharsetUtil; +import io.netty.util.concurrent.EventExecutor; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_HEADER_SIZE; import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT; -import static io.netty.handler.codec.http2.Http2TestUtil.as; import static io.netty.handler.codec.http2.Http2TestUtil.randomString; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; @@ -29,26 +49,6 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.Unpooled; -import io.netty.buffer.UnpooledByteBufAllocator; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.util.ByteString; -import io.netty.util.CharsetUtil; -import io.netty.util.concurrent.EventExecutor; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; /** * Integration tests for {@link DefaultHttp2FrameReader} and {@link DefaultHttp2FrameWriter}. @@ -365,8 +365,9 @@ public class DefaultHttp2FrameIOTest { } private static Http2Headers dummyHeaders() { - return new DefaultHttp2Headers().method(as("GET")).scheme(as("https")).authority(as("example.org")) - .path(as("/some/path")).add(as("accept"), as("*/*")); + return new DefaultHttp2Headers().method(new AsciiString("GET")).scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")).path(new AsciiString("/some/path")) + .add(new AsciiString("accept"), new AsciiString("*/*")); } private static Http2Headers largeHeaders() { @@ -374,7 +375,7 @@ public class DefaultHttp2FrameIOTest { for (int i = 0; i < 100; ++i) { String key = "this-is-a-test-header-key-" + i; String value = "this-is-a-test-header-value-" + i; - headers.add(as(key), as(value)); + headers.add(new AsciiString(key), new AsciiString(value)); } return headers; } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeaderTableListSizeTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeaderTableListSizeTest.java index 0a715ef7b3..c5cd5c4de5 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeaderTableListSizeTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeaderTableListSizeTest.java @@ -15,12 +15,11 @@ package io.netty.handler.codec.http2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.assertEquals; + /** * Tests for {@link DefaultHttp2HeaderTableListSize}. */ diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoderTest.java index 2067d9fd11..b825373fb2 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoderTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersDecoderTest.java @@ -15,22 +15,21 @@ package io.netty.handler.codec.http2; +import com.twitter.hpack.Encoder; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.util.AsciiString; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; + import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_HEADER_SIZE; import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE; -import static io.netty.handler.codec.http2.Http2TestUtil.as; import static io.netty.handler.codec.http2.Http2TestUtil.randomBytes; import static io.netty.util.CharsetUtil.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; - -import java.io.ByteArrayOutputStream; - -import org.junit.Before; -import org.junit.Test; - -import com.twitter.hpack.Encoder; /** * Tests for {@link DefaultHttp2HeadersDecoder}. @@ -51,7 +50,7 @@ public class DefaultHttp2HeadersDecoderTest { Http2Headers headers = decoder.decodeHeaders(buf); assertEquals(3, headers.size()); assertEquals("GET", headers.method().toString()); - assertEquals("avalue", headers.get(as("akey")).toString()); + assertEquals("avalue", headers.get(new AsciiString("akey")).toString()); } finally { buf.release(); } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoderTest.java index 3dc9c9d86a..b01010381b 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoderTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2HeadersEncoderTest.java @@ -15,14 +15,14 @@ package io.netty.handler.codec.http2; -import static io.netty.handler.codec.http2.Http2TestUtil.as; -import static org.junit.Assert.assertTrue; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; - +import io.netty.util.AsciiString; import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.assertTrue; + /** * Tests for {@link DefaultHttp2HeadersEncoder}. */ @@ -55,7 +55,7 @@ public class DefaultHttp2HeadersEncoderTest { } private static Http2Headers headers() { - return new DefaultHttp2Headers().method(as("GET")).add(as("a"), as("1")) - .add(as("a"), as("2")); + return new DefaultHttp2Headers().method(new AsciiString("GET")).add(new AsciiString("a"), new AsciiString("1")) + .add(new AsciiString("a"), new AsciiString("2")); } } 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 4c4abc0a5b..7839a8c0f9 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 @@ -16,42 +16,83 @@ package io.netty.handler.codec.http2; +import io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName; 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.HashSet; import java.util.Map.Entry; +import java.util.Set; -import static java.util.Arrays.asList; +import static io.netty.util.ByteString.fromAscii; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class DefaultHttp2HeadersTest { @Test 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")); + Http2Headers headers = newHeaders(); - 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")))); + verifyPseudoHeadersFirst(headers); + verifyAllPseudoHeadersPresent(headers); } - private static ByteString bs(String str) { - return new ByteString(str, CharsetUtil.US_ASCII); + @Test + public void pseudoHeadersWithRemovePreservesPseudoIterationOrder() { + Http2Headers headers = newHeaders(); + + Set nonPseudoHeaders = new HashSet(headers.size()); + for (Entry entry : headers) { + if (entry.getKey().isEmpty() || entry.getKey().byteAt(0) != ':') { + nonPseudoHeaders.add(entry.getKey()); + } + } + + // Remove all the non-pseudo headers and verify + for (ByteString nonPseudoHeader : nonPseudoHeaders) { + assertTrue(headers.remove(nonPseudoHeader)); + verifyPseudoHeadersFirst(headers); + verifyAllPseudoHeadersPresent(headers); + } + + // Add back all non-pseudo headers + for (ByteString nonPseudoHeader : nonPseudoHeaders) { + headers.add(nonPseudoHeader, fromAscii("goo")); + verifyPseudoHeadersFirst(headers); + verifyAllPseudoHeadersPresent(headers); + } + } + + private static void verifyAllPseudoHeadersPresent(Http2Headers headers) { + for (PseudoHeaderName pseudoName : PseudoHeaderName.values()) { + assertNotNull(headers.get(pseudoName.value())); + } + } + + private static void verifyPseudoHeadersFirst(Http2Headers headers) { + ByteString lastNonPseudoName = null; + for (Entry entry: headers) { + if (entry.getKey().isEmpty() || entry.getKey().byteAt(0) != ':') { + lastNonPseudoName = entry.getKey(); + } else if (lastNonPseudoName != null) { + fail("All pseudo headers must be fist in iteration. Pseudo header " + entry.getKey() + + " is after a non pseudo header " + lastNonPseudoName); + } + } + } + + private static Http2Headers newHeaders() { + Http2Headers headers = new DefaultHttp2Headers(); + headers.add(fromAscii("name1"), fromAscii("value1"), fromAscii("value2")); + headers.method(fromAscii("POST")); + headers.add(fromAscii("2name"), fromAscii("value3")); + headers.path(fromAscii("/index.html")); + headers.status(fromAscii("200")); + headers.authority(fromAscii("netty.io")); + headers.add(fromAscii("name3"), fromAscii("value4")); + headers.scheme(fromAscii("https")); + return headers; } } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionRoundtripTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionRoundtripTest.java index 583b1462ce..0b9f4b6a44 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionRoundtripTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2ConnectionRoundtripTest.java @@ -15,28 +15,6 @@ package io.netty.handler.codec.http2; -import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; -import static io.netty.handler.codec.http2.Http2TestUtil.as; -import static io.netty.handler.codec.http2.Http2TestUtil.randomString; -import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel; -import static io.netty.handler.codec.http2.Http2TestUtil.FrameCountDown; -import static io.netty.util.CharsetUtil.UTF_8; -import static java.util.concurrent.TimeUnit.SECONDS; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; @@ -53,15 +31,11 @@ import io.netty.channel.ChannelPromise; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http2.Http2TestUtil.FrameCountDown; import io.netty.handler.codec.http2.Http2TestUtil.Http2Runnable; +import io.netty.util.AsciiString; import io.netty.util.NetUtil; import io.netty.util.concurrent.Future; - -import java.io.ByteArrayOutputStream; -import java.net.InetSocketAddress; -import java.util.Random; -import java.util.concurrent.CountDownLatch; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -70,6 +44,32 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.io.ByteArrayOutputStream; +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.CountDownLatch; + +import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR; +import static io.netty.handler.codec.http2.Http2TestUtil.randomString; +import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel; +import static io.netty.util.CharsetUtil.UTF_8; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + /** * Tests the full HTTP/2 framing stack including the connection and preface handlers. */ @@ -495,8 +495,9 @@ public class Http2ConnectionRoundtripTest { } private static Http2Headers dummyHeaders() { - return new DefaultHttp2Headers().method(as("GET")).scheme(as("https")) - .authority(as("example.org")).path(as("/some/path/resource2")).add(randomString(), randomString()); + return new DefaultHttp2Headers().method(new AsciiString("GET")).scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")).path(new AsciiString("/some/path/resource2")) + .add(randomString(), randomString()); } private void mockFlowControl(Http2FrameListener listener) throws Http2Exception { diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java index 077d48ae4d..527f6819d7 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java @@ -29,6 +29,7 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http2.Http2TestUtil.Http2Runnable; +import io.netty.util.AsciiString; import io.netty.util.NetUtil; import io.netty.util.concurrent.Future; import org.junit.After; @@ -46,7 +47,6 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static io.netty.handler.codec.http2.Http2TestUtil.as; import static io.netty.handler.codec.http2.Http2TestUtil.randomString; import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel; import static io.netty.util.CharsetUtil.UTF_8; @@ -393,7 +393,8 @@ public class Http2FrameRoundtripTest { } private static Http2Headers headers() { - return new DefaultHttp2Headers().method(as("GET")).scheme(as("https")) - .authority(as("example.org")).path(as("/some/path/resource2")).add(randomString(), randomString()); + return new DefaultHttp2Headers().method(new AsciiString("GET")).scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")).path(new AsciiString("/some/path/resource2")) + .add(randomString(), randomString()); } } diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2HeaderBlockIOTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2HeaderBlockIOTest.java index 265d8c0ca5..951d96af17 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2HeaderBlockIOTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2HeaderBlockIOTest.java @@ -15,16 +15,16 @@ package io.netty.handler.codec.http2; -import static io.netty.handler.codec.http2.Http2TestUtil.as; -import static io.netty.handler.codec.http2.Http2TestUtil.randomString; -import static org.junit.Assert.assertEquals; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; - +import io.netty.util.AsciiString; import org.junit.After; import org.junit.Before; import org.junit.Test; +import static io.netty.handler.codec.http2.Http2TestUtil.randomString; +import static org.junit.Assert.assertEquals; + /** * Tests for encoding/decoding HTTP2 header blocks. */ @@ -55,23 +55,23 @@ public class Http2HeaderBlockIOTest { @Test public void successiveCallsShouldSucceed() throws Http2Exception { Http2Headers in = - new DefaultHttp2Headers().method(as("GET")).scheme(as("https")) - .authority(as("example.org")).path(as("/some/path")) - .add(as("accept"), as("*/*")); + new DefaultHttp2Headers().method(new AsciiString("GET")).scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")).path(new AsciiString("/some/path")) + .add(new AsciiString("accept"), new AsciiString("*/*")); assertRoundtripSuccessful(in); in = - new DefaultHttp2Headers().method(as("GET")).scheme(as("https")) - .authority(as("example.org")).path(as("/some/path/resource1")) - .add(as("accept"), as("image/jpeg")) - .add(as("cache-control"), as("no-cache")); + new DefaultHttp2Headers().method(new AsciiString("GET")).scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")).path(new AsciiString("/some/path/resource1")) + .add(new AsciiString("accept"), new AsciiString("image/jpeg")) + .add(new AsciiString("cache-control"), new AsciiString("no-cache")); assertRoundtripSuccessful(in); in = - new DefaultHttp2Headers().method(as("GET")).scheme(as("https")) - .authority(as("example.org")).path(as("/some/path/resource2")) - .add(as("accept"), as("image/png")) - .add(as("cache-control"), as("no-cache")); + new DefaultHttp2Headers().method(new AsciiString("GET")).scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")).path(new AsciiString("/some/path/resource2")) + .add(new AsciiString("accept"), new AsciiString("image/png")) + .add(new AsciiString("cache-control"), new AsciiString("no-cache")); assertRoundtripSuccessful(in); } @@ -91,11 +91,14 @@ public class Http2HeaderBlockIOTest { } private static Http2Headers headers() { - return new DefaultHttp2Headers().method(as("GET")).scheme(as("https")) - .authority(as("example.org")).path(as("/some/path/resource2")) - .add(as("accept"), as("image/png")).add(as("cache-control"), as("no-cache")) - .add(as("custom"), as("value1")).add(as("custom"), as("value2")) - .add(as("custom"), as("value3")).add(as("custom"), as("custom4")) + return new DefaultHttp2Headers().method(new AsciiString("GET")).scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")).path(new AsciiString("/some/path/resource2")) + .add(new AsciiString("accept"), new AsciiString("image/png")) + .add(new AsciiString("cache-control"), new AsciiString("no-cache")) + .add(new AsciiString("custom"), new AsciiString("value1")) + .add(new AsciiString("custom"), new AsciiString("value2")) + .add(new AsciiString("custom"), new AsciiString("value3")) + .add(new AsciiString("custom"), new AsciiString("custom4")) .add(randomString(), randomString()); } } 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 3bfa13432e..68ed6a241a 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 @@ -52,20 +52,6 @@ final class Http2TestUtil { }); } - /** - * Converts a {@link String} into an {@link AsciiString}. - */ - public static AsciiString as(String value) { - return new AsciiString(value); - } - - /** - * Converts a byte array into a {@link ByteString}. - */ - public static ByteString bs(byte[] value) { - return new ByteString(value); - } - /** * Returns a byte array filled with random data. */ @@ -86,7 +72,7 @@ final class Http2TestUtil { * Returns an {@link AsciiString} that wraps a randomly-filled byte array. */ public static ByteString randomString() { - return bs(randomBytes()); + return new ByteString(randomBytes()); } private Http2TestUtil() { diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandlerTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandlerTest.java index 9960de21e7..65e1c42907 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandlerTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/HttpToHttp2ConnectionHandlerTest.java @@ -14,23 +14,6 @@ */ package io.netty.handler.codec.http2; -import static io.netty.handler.codec.http.HttpMethod.GET; -import static io.netty.handler.codec.http.HttpMethod.POST; -import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; -import static io.netty.handler.codec.http2.Http2TestUtil.as; -import static io.netty.util.CharsetUtil.UTF_8; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyShort; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; @@ -54,15 +37,9 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http2.Http2TestUtil.FrameCountDown; +import io.netty.util.AsciiString; import io.netty.util.NetUtil; import io.netty.util.concurrent.Future; - -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CountDownLatch; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -71,6 +48,29 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static io.netty.util.CharsetUtil.UTF_8; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyShort; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + /** * Testing the {@link HttpToHttp2ConnectionHandler} for {@link FullHttpRequest} objects into HTTP/2 frames */ @@ -122,10 +122,11 @@ public class HttpToHttp2ConnectionHandlerTest { httpHeaders.add("foo", "goo2"); httpHeaders.add("foo2", "goo2"); final Http2Headers http2Headers = - new DefaultHttp2Headers().method(as("GET")).path(as("/example")) - .authority(as("www.example.org:5555")).scheme(as("http")) - .add(as("foo"), as("goo")).add(as("foo"), as("goo2")) - .add(as("foo2"), as("goo2")); + new DefaultHttp2Headers().method(new AsciiString("GET")).path(new AsciiString("/example")) + .authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http")) + .add(new AsciiString("foo"), new AsciiString("goo")) + .add(new AsciiString("foo"), new AsciiString("goo2")) + .add(new AsciiString("foo2"), new AsciiString("goo2")); ChannelPromise writePromise = newPromise(); ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise); @@ -161,10 +162,11 @@ public class HttpToHttp2ConnectionHandlerTest { httpHeaders.add("foo", "goo2"); httpHeaders.add("foo2", "goo2"); final Http2Headers http2Headers = - new DefaultHttp2Headers().method(as("POST")).path(as("/example")) - .authority(as("www.example.org:5555")).scheme(as("http")) - .add(as("foo"), as("goo")).add(as("foo"), as("goo2")) - .add(as("foo2"), as("goo2")); + new DefaultHttp2Headers().method(new AsciiString("POST")).path(new AsciiString("/example")) + .authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http")) + .add(new AsciiString("foo"), new AsciiString("goo")) + .add(new AsciiString("foo"), new AsciiString("goo2")) + .add(new AsciiString("foo2"), new AsciiString("goo2")); ChannelPromise writePromise = newPromise(); ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise); @@ -202,14 +204,16 @@ public class HttpToHttp2ConnectionHandlerTest { httpHeaders.add("foo", "goo2"); httpHeaders.add("foo2", "goo2"); final Http2Headers http2Headers = - new DefaultHttp2Headers().method(as("POST")).path(as("/example")) - .authority(as("www.example.org:5555")).scheme(as("http")) - .add(as("foo"), as("goo")).add(as("foo"), as("goo2")) - .add(as("foo2"), as("goo2")); + new DefaultHttp2Headers().method(new AsciiString("POST")).path(new AsciiString("/example")) + .authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http")) + .add(new AsciiString("foo"), new AsciiString("goo")) + .add(new AsciiString("foo"), new AsciiString("goo2")) + .add(new AsciiString("foo2"), new AsciiString("goo2")); request.trailingHeaders().add("trailing", "bar"); - final Http2Headers http2TrailingHeaders = new DefaultHttp2Headers().add(as("trailing"), as("bar")); + final Http2Headers http2TrailingHeaders = new DefaultHttp2Headers() + .add(new AsciiString("trailing"), new AsciiString("bar")); ChannelPromise writePromise = newPromise(); ChannelFuture writeFuture = clientChannel.writeAndFlush(request, writePromise); @@ -251,17 +255,19 @@ public class HttpToHttp2ConnectionHandlerTest { httpHeaders.add("foo", "goo2"); httpHeaders.add("foo2", "goo2"); final Http2Headers http2Headers = - new DefaultHttp2Headers().method(as("POST")).path(as("/example")) - .authority(as("www.example.org:5555")).scheme(as("http")) - .add(as("foo"), as("goo")).add(as("foo"), as("goo2")) - .add(as("foo2"), as("goo2")); + new DefaultHttp2Headers().method(new AsciiString("POST")).path(new AsciiString("/example")) + .authority(new AsciiString("www.example.org:5555")).scheme(new AsciiString("http")) + .add(new AsciiString("foo"), new AsciiString("goo")) + .add(new AsciiString("foo"), new AsciiString("goo2")) + .add(new AsciiString("foo2"), new AsciiString("goo2")); final DefaultHttpContent httpContent = new DefaultHttpContent(Unpooled.copiedBuffer(text, UTF_8)); final LastHttpContent lastHttpContent = new DefaultLastHttpContent(Unpooled.copiedBuffer(text2, UTF_8)); lastHttpContent.trailingHeaders().add("trailing", "bar"); - final Http2Headers http2TrailingHeaders = new DefaultHttp2Headers().add(as("trailing"), as("bar")); + final Http2Headers http2TrailingHeaders = new DefaultHttp2Headers() + .add(new AsciiString("trailing"), new AsciiString("bar")); ChannelPromise writePromise = newPromise(); ChannelFuture writeFuture = clientChannel.write(request, writePromise); diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java index 10deac6d0c..db5cfb9fee 100644 --- a/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java +++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/InboundHttp2ToHttpAdapterTest.java @@ -59,7 +59,6 @@ import java.util.concurrent.CountDownLatch; import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception; import static io.netty.handler.codec.http2.Http2Exception.isStreamError; -import static io.netty.handler.codec.http2.Http2TestUtil.as; import static io.netty.handler.codec.http2.Http2TestUtil.runInChannel; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -210,8 +209,9 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders.set(HttpUtil.ExtensionHeaderNames.AUTHORITY.text(), "example.org"); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).scheme(as("https")) - .authority(as("example.org")).path(as("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")). + scheme(new AsciiString("https")).authority(new AsciiString("example.org")) + .path(new AsciiString("/some/path/resource2")); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { @@ -232,10 +232,10 @@ public class InboundHttp2ToHttpAdapterTest { @Test public void clientRequestSingleHeaderNonAsciiShouldThrow() throws Exception { final Http2Headers http2Headers = new DefaultHttp2Headers() - .method(as("GET")) - .scheme(as("https")) - .authority(as("example.org")) - .path(as("/some/path/resource2")) + .method(new AsciiString("GET")) + .scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")) + .path(new AsciiString("/some/path/resource2")) .add(new AsciiString("çã".getBytes(CharsetUtil.UTF_8)), new AsciiString("Ãã".getBytes(CharsetUtil.UTF_8))); runInChannel(clientChannel, new Http2Runnable() { @@ -259,8 +259,8 @@ public class InboundHttp2ToHttpAdapterTest { HttpHeaders httpHeaders = request.headers(); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).path( - as("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { @@ -289,8 +289,8 @@ public class InboundHttp2ToHttpAdapterTest { HttpHeaders httpHeaders = request.headers(); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).path( - as("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); final int midPoint = text.length() / 2; runInChannel(clientChannel, new Http2Runnable() { @Override @@ -323,8 +323,8 @@ public class InboundHttp2ToHttpAdapterTest { HttpHeaders httpHeaders = request.headers(); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length()); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).path( - as("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { @@ -361,10 +361,12 @@ public class InboundHttp2ToHttpAdapterTest { trailingHeaders.set("FoO", "goo"); trailingHeaders.set("foO2", "goo2"); trailingHeaders.add("fOo2", "goo3"); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).path( - as("/some/path/resource2")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers().set(as("foo"), as("goo")) - .set(as("foo2"), as("goo2")).add(as("foo2"), as("goo3")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers() + .set(new AsciiString("foo"), new AsciiString("goo")) + .set(new AsciiString("foo2"), new AsciiString("goo2")) + .add(new AsciiString("foo2"), new AsciiString("goo3")); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { @@ -398,10 +400,12 @@ public class InboundHttp2ToHttpAdapterTest { trailingHeaders.set("Foo", "goo"); trailingHeaders.set("fOo2", "goo2"); trailingHeaders.add("foO2", "goo3"); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("GET")).path( - as("/some/path/resource2")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers().set(as("foo"), as("goo")) - .set(as("foo2"), as("goo2")).add(as("foo2"), as("goo3")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path( + new AsciiString("/some/path/resource2")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers() + .set(new AsciiString("foo"), new AsciiString("goo")) + .set(new AsciiString("foo2"), new AsciiString("goo2")) + .add(new AsciiString("foo2"), new AsciiString("goo3")); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { @@ -441,10 +445,10 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders2.setInt(HttpUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3); httpHeaders2.setInt(HttpUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), 123); httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length()); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("PUT")).path( - as("/some/path/resource")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(as("PUT")).path( - as("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( + new AsciiString("/some/path/resource")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( + new AsciiString("/some/path/resource2")); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { @@ -488,10 +492,10 @@ public class InboundHttp2ToHttpAdapterTest { HttpHeaders httpHeaders2 = request2.headers(); httpHeaders2.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length()); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("PUT")).path( - as("/some/path/resource")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(as("PUT")).path( - as("/some/path/resource2")); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( + new AsciiString("/some/path/resource")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(new AsciiString("PUT")).path( + new AsciiString("/some/path/resource2")); HttpHeaders httpHeaders3 = request3.headers(); httpHeaders3.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 5); httpHeaders3.setInt(HttpUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3); @@ -549,7 +553,8 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders = request.headers(); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - final Http2Headers http2Headers3 = new DefaultHttp2Headers().method(as("GET")).path(as("/push/test")); + final Http2Headers http2Headers3 = new DefaultHttp2Headers().method(new AsciiString("GET")) + .path(new AsciiString("/push/test")); runInChannel(clientChannel, new Http2Runnable() { @Override public void run() { @@ -563,9 +568,10 @@ public class InboundHttp2ToHttpAdapterTest { capturedRequests = requestCaptor.getAllValues(); assertEquals(request, capturedRequests.get(0)); - final Http2Headers http2Headers = new DefaultHttp2Headers().status(as("200")); - final Http2Headers http2Headers2 = new DefaultHttp2Headers().status(as("201")).scheme(as("https")) - .authority(as("example.org")); + final Http2Headers http2Headers = new DefaultHttp2Headers().status(new AsciiString("200")); + final Http2Headers http2Headers2 = new DefaultHttp2Headers().status(new AsciiString("201")) + .scheme(new AsciiString("https")) + .authority(new AsciiString("example.org")); runInChannel(serverConnectedChannel, new Http2Runnable() { @Override public void run() { @@ -597,8 +603,10 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.set(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - final Http2Headers http2Headers = new DefaultHttp2Headers().method(as("PUT")).path(as("/info/test")) - .set(as(HttpHeaderNames.EXPECT.toString()), as(HttpHeaderValues.CONTINUE.toString())); + final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")) + .path(new AsciiString("/info/test")) + .set(new AsciiString(HttpHeaderNames.EXPECT.toString()), + new AsciiString(HttpHeaderValues.CONTINUE.toString())); final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE); final String text = "a big payload"; final ByteBuf payload = Unpooled.copiedBuffer(text.getBytes()); @@ -624,7 +632,7 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders = response.headers(); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - final Http2Headers http2HeadersResponse = new DefaultHttp2Headers().status(as("100")); + final Http2Headers http2HeadersResponse = new DefaultHttp2Headers().status(new AsciiString("100")); runInChannel(serverConnectedChannel, new Http2Runnable() { @Override public void run() { @@ -661,7 +669,7 @@ public class InboundHttp2ToHttpAdapterTest { httpHeaders = response2.headers(); httpHeaders.setInt(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), 3); httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0); - final Http2Headers http2HeadersResponse2 = new DefaultHttp2Headers().status(as("200")); + final Http2Headers http2HeadersResponse2 = new DefaultHttp2Headers().status(new AsciiString("200")); runInChannel(serverConnectedChannel, new Http2Runnable() { @Override public void run() { 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 a3bb2312ff..c940be648f 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 @@ -16,15 +16,21 @@ package io.netty.handler.codec.stomp; -import io.netty.handler.codec.DefaultTextHeaders; -import io.netty.handler.codec.TextHeaders; +import io.netty.handler.codec.CharSequenceValueConverter; +import io.netty.handler.codec.DefaultHeaders; +import io.netty.handler.codec.Headers; +import io.netty.handler.codec.HeadersUtils; -import java.util.TreeMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; -public class DefaultStompHeaders extends DefaultTextHeaders implements StompHeaders { +import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER; +import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER; +public class DefaultStompHeaders extends DefaultHeaders implements StompHeaders { public DefaultStompHeaders() { - super(new TreeMap(), NO_NAME_VALIDATOR, CharSequenceConverter.INSTANCE, false); + super(CharSequenceValueConverter.INSTANCE); } @Override @@ -118,7 +124,7 @@ public class DefaultStompHeaders extends DefaultTextHeaders implements StompHead } @Override - public StompHeaders add(TextHeaders headers) { + public StompHeaders add(Headers headers) { super.add(headers); return this; } @@ -214,13 +220,13 @@ public class DefaultStompHeaders extends DefaultTextHeaders implements StompHead } @Override - public StompHeaders set(TextHeaders headers) { + public StompHeaders set(Headers headers) { super.set(headers); return this; } @Override - public StompHeaders setAll(TextHeaders headers) { + public StompHeaders setAll(Headers headers) { super.setAll(headers); return this; } @@ -230,4 +236,30 @@ public class DefaultStompHeaders extends DefaultTextHeaders implements StompHead super.clear(); return this; } + + @Override + public String getAsString(CharSequence name) { + return HeadersUtils.getAsString(this, name); + } + + @Override + public List getAllAsString(CharSequence name) { + return HeadersUtils.getAllAsString(this, name); + } + + @Override + public Iterator> iteratorAsString() { + return HeadersUtils.iteratorAsString(this); + } + + @Override + public boolean contains(CharSequence name, CharSequence value) { + return contains(name, value, false); + } + + @Override + public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { + return contains(name, value, + ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER); + } } diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java index 89c93db414..55cfa3d0ea 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java @@ -15,14 +15,18 @@ */ package io.netty.handler.codec.stomp; -import io.netty.handler.codec.TextHeaders; +import io.netty.handler.codec.Headers; import io.netty.util.AsciiString; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; + /** * The multimap data structure for the STOMP header names and values. It also provides the constants for the standard * STOMP header names and values. */ -public interface StompHeaders extends TextHeaders { +public interface StompHeaders extends Headers { AsciiString ACCEPT_VERSION = new AsciiString("accept-version"); AsciiString HOST = new AsciiString("host"); @@ -90,7 +94,7 @@ public interface StompHeaders extends TextHeaders { StompHeaders addTimeMillis(CharSequence name, long value); @Override - StompHeaders add(TextHeaders headers); + StompHeaders add(Headers headers); @Override StompHeaders set(CharSequence name, CharSequence value); @@ -138,11 +142,41 @@ public interface StompHeaders extends TextHeaders { StompHeaders setTimeMillis(CharSequence name, long value); @Override - StompHeaders set(TextHeaders headers); + StompHeaders set(Headers headers); @Override - StompHeaders setAll(TextHeaders headers); + StompHeaders setAll(Headers headers); @Override StompHeaders clear(); + + /** + * {@link Headers#get(Object)} and convert the result to a {@link String}. + * @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. + */ + String getAsString(CharSequence name); + + /** + * {@link Headers#getAll(Object)} and convert each element of {@link List} to a {@link String}. + * @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 getAllAsString(CharSequence name); + + /** + * {@link #iterator()} that converts each {@link Entry}'s key and value to a {@link String}. + */ + Iterator> iteratorAsString(); + + /** + * Returns {@code true} if a header with the {@code name} and {@code value} exists, {@code false} otherwise. + *

+ * If {@code ignoreCase} is {@code true} then a case insensitive compare is done on the value. + * @param name the name of the header to find + * @param value the value of the header to find + * @param ignoreCase {@code true} then a case insensitive compare is run to compare values. + * otherwise a case sensitive compare is run to compare values. + */ + boolean contains(CharSequence name, CharSequence value, boolean ignoreCase); } 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 e57aa5e607..dd32c7e90a 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 @@ -15,6 +15,9 @@ */ package io.netty.handler.codec.stomp; +import java.util.List; +import java.util.Map.Entry; + import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.AsciiHeadersEncoder; @@ -22,10 +25,6 @@ import io.netty.handler.codec.AsciiHeadersEncoder.NewlineType; import io.netty.handler.codec.AsciiHeadersEncoder.SeparatorType; import io.netty.handler.codec.MessageToMessageEncoder; 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}. 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 e3e49a7daf..a427b944cc 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" + - "accept-version:1.1,1.2\n" + "host:stomp.github.org\n" + + "accept-version:1.1,1.2\n" + '\n' + '\0'; public static final String CONNECTED_FRAME = diff --git a/codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java b/codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java deleted file mode 100644 index f2a4000836..0000000000 --- a/codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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: - * - * 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; - -import io.netty.util.ByteString; - -/** - * A typical {@code ByteString} multimap used by protocols that use binary headers (such as HTTP/2) for the - * representation of arbitrary key-value data. {@link ByteString} is just a wrapper around a byte array but provides - * some additional utility when handling text data. - */ -public interface BinaryHeaders extends Headers { - - @Override - BinaryHeaders add(ByteString name, ByteString value); - - @Override - BinaryHeaders add(ByteString name, Iterable values); - - @Override - BinaryHeaders add(ByteString name, ByteString... values); - - @Override - BinaryHeaders addObject(ByteString name, Object value); - - @Override - BinaryHeaders addObject(ByteString name, Iterable values); - - @Override - BinaryHeaders addObject(ByteString name, Object... values); - - @Override - BinaryHeaders addBoolean(ByteString name, boolean value); - - @Override - BinaryHeaders addByte(ByteString name, byte value); - - @Override - BinaryHeaders addChar(ByteString name, char value); - - @Override - BinaryHeaders addShort(ByteString name, short value); - - @Override - BinaryHeaders addInt(ByteString name, int value); - - @Override - BinaryHeaders addLong(ByteString name, long value); - - @Override - BinaryHeaders addFloat(ByteString name, float value); - - @Override - BinaryHeaders addDouble(ByteString name, double value); - - @Override - BinaryHeaders addTimeMillis(ByteString name, long value); - - /** - * See {@link Headers#add(Headers)} - */ - BinaryHeaders add(BinaryHeaders headers); - - @Override - BinaryHeaders set(ByteString name, ByteString value); - - @Override - BinaryHeaders set(ByteString name, Iterable values); - - @Override - BinaryHeaders set(ByteString name, ByteString... values); - - @Override - BinaryHeaders setObject(ByteString name, Object value); - - @Override - BinaryHeaders setObject(ByteString name, Iterable values); - - @Override - BinaryHeaders setObject(ByteString name, Object... values); - - @Override - BinaryHeaders setBoolean(ByteString name, boolean value); - - @Override - BinaryHeaders setByte(ByteString name, byte value); - - @Override - BinaryHeaders setChar(ByteString name, char value); - - @Override - BinaryHeaders setShort(ByteString name, short value); - - @Override - BinaryHeaders setInt(ByteString name, int value); - - @Override - BinaryHeaders setLong(ByteString name, long value); - - @Override - BinaryHeaders setFloat(ByteString name, float value); - - @Override - BinaryHeaders setDouble(ByteString name, double value); - - @Override - BinaryHeaders setTimeMillis(ByteString name, long value); - - /** - * See {@link Headers#set(Headers)} - */ - BinaryHeaders set(BinaryHeaders headers); - - /** - * See {@link Headers#setAll(Headers)} - */ - BinaryHeaders setAll(BinaryHeaders headers); - - @Override - BinaryHeaders clear(); -} diff --git a/codec/src/main/java/io/netty/handler/codec/ByteStringValueConverter.java b/codec/src/main/java/io/netty/handler/codec/ByteStringValueConverter.java new file mode 100644 index 0000000000..76df1a4e1a --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/ByteStringValueConverter.java @@ -0,0 +1,140 @@ +/* + * 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; + +import java.nio.charset.Charset; +import java.text.ParseException; + +import io.netty.handler.codec.DefaultHeaders.HeaderDateFormat; +import io.netty.util.ByteString; +import io.netty.util.CharsetUtil; +import io.netty.util.internal.PlatformDependent; + +/** + * Converts to/from native types, general {@link Object}, and {@link ByteString}s. + */ +public final class ByteStringValueConverter implements ValueConverter { + public static final ByteStringValueConverter INSTANCE = new ByteStringValueConverter(); + private static final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; + + private ByteStringValueConverter() { + } + + @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/CharSequenceValueConverter.java b/codec/src/main/java/io/netty/handler/codec/CharSequenceValueConverter.java new file mode 100644 index 0000000000..c59450ee45 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/CharSequenceValueConverter.java @@ -0,0 +1,133 @@ +/* + * 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; + +import io.netty.handler.codec.DefaultHeaders.HeaderDateFormat; +import io.netty.util.internal.PlatformDependent; + +import java.text.ParseException; + +/** + * Converts to/from native types, general {@link Object}, and {@link CharSequence}s. + */ +public class CharSequenceValueConverter implements ValueConverter { + public static final CharSequenceValueConverter INSTANCE = new CharSequenceValueConverter(); + + @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/ConvertibleHeaders.java b/codec/src/main/java/io/netty/handler/codec/ConvertibleHeaders.java deleted file mode 100644 index ca3eae694a..0000000000 --- a/codec/src/main/java/io/netty/handler/codec/ConvertibleHeaders.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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: - * - * 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; - -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; - -/** - * Extension to the {@link Headers} interface to provide methods which convert the - * native {@code UnconvertedType} to the not-native {@code ConvertedType} - */ -public interface ConvertibleHeaders extends Headers { - - /** - * Interface to do conversions to and from the two generic type parameters - */ - interface TypeConverter { - /** - * Convert a native value - * @param value The value to be converted - * @return The conversion results - */ - ConvertedType toConvertedType(UnconvertedType value); - - /** - * Undo a conversion and restore the original native type - * @param value The converted value - * @return The original native type - */ - UnconvertedType toUnconvertedType(ConvertedType value); - } - - /** - * Invokes {@link Headers#get(Object)} and does a conversion on the results if not {@code null} - * @param name The name of entry to get - * @return The value corresponding to {@code name} and then converted - */ - ConvertedType getAndConvert(UnconvertedType name); - - /** - * Invokes {@link Headers#get(Object, Object)} and does a conversion on the results if not {@code null} - * @param name The name of entry to get - * @return The value corresponding to {@code name} and then converted - */ - ConvertedType getAndConvert(UnconvertedType name, ConvertedType defaultValue); - - /** - * Invokes {@link Headers#getAndRemove(Object)} and does a conversion on the results if not {@code null} - * @param name The name of entry to get - * @return The value corresponding to {@code name} and then converted - */ - ConvertedType getAndRemoveAndConvert(UnconvertedType name); - - /** - * Invokes {@link Headers#getAndRemove(Object, Object)} and does - * a conversion on the results if not {@code null} - * @param name The name of entry to get - * @return The value corresponding to {@code name} and then converted - */ - ConvertedType getAndRemoveAndConvert(UnconvertedType name, ConvertedType defaultValue); - - /** - * Invokes {@link Headers#getAll(Object)} and does a conversion on the results if not {@code null} - * @param name The name of entry to get - * @return The values corresponding to {@code name} and then converted - */ - List getAllAndConvert(UnconvertedType name); - - /** - * Invokes {@link Headers#getAllAndRemove(Object)} and does a conversion on the results if not {@code null} - * @param name The name of entry to get - * @return The values corresponding to {@code name} and then converted - */ - List getAllAndRemoveAndConvert(UnconvertedType name); - - /** - * Invokes {@link Headers#iterator()} and lazily does a conversion on the results as they are accessed - * - * @return Iterator which will provide converted values corresponding to {@code name} - */ - Iterator> iteratorConverted(); - - /** - * Invokes {@link Headers#names()} and does a conversion on the results - * - * @return The values corresponding to {@code name} and then converted - */ - Set namesAndConvert(Comparator comparator); -} diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java deleted file mode 100644 index 2183e5d71d..0000000000 --- a/codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - * 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: - * - * 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; - -import io.netty.util.ByteString; -import io.netty.util.CharsetUtil; -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 NameValidator NO_NAME_VALIDATOR = DefaultHeaders.NoNameValidator.instance(); - - public DefaultBinaryHeaders() { - this(new TreeMap(ByteString.DEFAULT_COMPARATOR)); - } - - public DefaultBinaryHeaders(Map map) { - this(map, NO_NAME_VALIDATOR); - } - - public DefaultBinaryHeaders(Map map, NameValidator nameValidator) { - super(map, nameValidator, ByteStringConverter.INSTANCE); - } - - @Override - public BinaryHeaders add(ByteString name, ByteString value) { - super.add(name, value); - return this; - } - - @Override - public BinaryHeaders add(ByteString name, Iterable values) { - super.add(name, values); - return this; - } - - @Override - public BinaryHeaders add(ByteString name, ByteString... values) { - super.add(name, values); - return this; - } - - @Override - public BinaryHeaders addObject(ByteString name, Object value) { - super.addObject(name, value); - return this; - } - - @Override - public BinaryHeaders addObject(ByteString name, Iterable values) { - super.addObject(name, values); - return this; - } - - @Override - public BinaryHeaders addObject(ByteString name, Object... values) { - super.addObject(name, values); - return this; - } - - @Override - public BinaryHeaders addBoolean(ByteString name, boolean value) { - super.addBoolean(name, value); - return this; - } - - @Override - public BinaryHeaders addChar(ByteString name, char value) { - super.addChar(name, value); - return this; - } - - @Override - public BinaryHeaders addByte(ByteString name, byte value) { - super.addByte(name, value); - return this; - } - - @Override - public BinaryHeaders addShort(ByteString name, short value) { - super.addShort(name, value); - return this; - } - - @Override - public BinaryHeaders addInt(ByteString name, int value) { - super.addInt(name, value); - return this; - } - - @Override - public BinaryHeaders addLong(ByteString name, long value) { - super.addLong(name, value); - return this; - } - - @Override - public BinaryHeaders addFloat(ByteString name, float value) { - super.addFloat(name, value); - return this; - } - - @Override - public BinaryHeaders addDouble(ByteString name, double value) { - super.addDouble(name, value); - return this; - } - - @Override - public BinaryHeaders addTimeMillis(ByteString name, long value) { - super.addTimeMillis(name, value); - return this; - } - - @Override - public BinaryHeaders add(BinaryHeaders headers) { - super.add(headers); - return this; - } - - @Override - public BinaryHeaders set(ByteString name, ByteString value) { - super.set(name, value); - return this; - } - - @Override - public BinaryHeaders set(ByteString name, Iterable values) { - super.set(name, values); - return this; - } - - @Override - public BinaryHeaders set(ByteString name, ByteString... values) { - super.set(name, values); - return this; - } - - @Override - public BinaryHeaders setObject(ByteString name, Object value) { - super.setObject(name, value); - return this; - } - - @Override - public BinaryHeaders setObject(ByteString name, Iterable values) { - super.setObject(name, values); - return this; - } - - @Override - public BinaryHeaders setObject(ByteString name, Object... values) { - super.setObject(name, values); - return this; - } - - @Override - public BinaryHeaders setBoolean(ByteString name, boolean value) { - super.setBoolean(name, value); - return this; - } - - @Override - public BinaryHeaders setChar(ByteString name, char value) { - super.setChar(name, value); - return this; - } - - @Override - public BinaryHeaders setByte(ByteString name, byte value) { - super.setByte(name, value); - return this; - } - - @Override - public BinaryHeaders setShort(ByteString name, short value) { - super.setShort(name, value); - return this; - } - - @Override - public BinaryHeaders setInt(ByteString name, int value) { - super.setInt(name, value); - return this; - } - - @Override - public BinaryHeaders setLong(ByteString name, long value) { - super.setLong(name, value); - return this; - } - - @Override - public BinaryHeaders setFloat(ByteString name, float value) { - super.setFloat(name, value); - return this; - } - - @Override - public BinaryHeaders setDouble(ByteString name, double value) { - super.setDouble(name, value); - return this; - } - - @Override - public BinaryHeaders setTimeMillis(ByteString name, long value) { - super.setTimeMillis(name, value); - return this; - } - - @Override - public BinaryHeaders set(BinaryHeaders headers) { - super.set(headers); - return this; - } - - @Override - public BinaryHeaders setAll(BinaryHeaders headers) { - super.setAll(headers); - return this; - } - - @Override - public BinaryHeaders clear() { - 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 deleted file mode 100644 index 3c7863799d..0000000000 --- a/codec/src/main/java/io/netty/handler/codec/DefaultConvertibleHeaders.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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: - * - * 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; - -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; - -public class DefaultConvertibleHeaders extends DefaultHeaders - implements ConvertibleHeaders { - - private final TypeConverter typeConverter; - - public DefaultConvertibleHeaders(Map map, - NameValidator nameValidator, - ValueConverter valueConverter, - TypeConverter typeConverter) { - super(map, nameValidator, valueConverter); - this.typeConverter = typeConverter; - } - - @Override - public ConvertedType getAndConvert(UnconvertedType name) { - return getAndConvert(name, null); - } - - @Override - public ConvertedType getAndConvert(UnconvertedType name, ConvertedType defaultValue) { - UnconvertedType v = get(name); - if (v == null) { - return defaultValue; - } - return typeConverter.toConvertedType(v); - } - - @Override - public ConvertedType getAndRemoveAndConvert(UnconvertedType name) { - return getAndRemoveAndConvert(name, null); - } - - @Override - public ConvertedType getAndRemoveAndConvert(UnconvertedType name, ConvertedType defaultValue) { - UnconvertedType v = getAndRemove(name); - if (v == null) { - return defaultValue; - } - return typeConverter.toConvertedType(v); - } - - @Override - public List getAllAndConvert(UnconvertedType name) { - List all = getAll(name); - List allConverted = new ArrayList(all.size()); - for (int i = 0; i < all.size(); ++i) { - allConverted.add(typeConverter.toConvertedType(all.get(i))); - } - return allConverted; - } - - @Override - public List getAllAndRemoveAndConvert(UnconvertedType name) { - List all = getAllAndRemove(name); - List allConverted = new ArrayList(all.size()); - for (int i = 0; i < all.size(); ++i) { - allConverted.add(typeConverter.toConvertedType(all.get(i))); - } - return allConverted; - } - - @Override - public Iterator> iteratorConverted() { - return new ConvertedIterator(); - } - - @Override - public Set namesAndConvert(Comparator comparator) { - Set names = names(); - Set namesConverted = new TreeSet(comparator); - for (UnconvertedType unconverted : names) { - namesConverted.add(typeConverter.toConvertedType(unconverted)); - } - return namesConverted; - } - - private final class ConvertedIterator implements Iterator> { - private final Iterator> iter = iterator(); - - @Override - public boolean hasNext() { - return iter.hasNext(); - } - - @Override - public Entry next() { - Entry next = iter.next(); - - return new ConvertedEntry(next); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - private final class ConvertedEntry implements Entry { - private final Entry entry; - private ConvertedType name; - private ConvertedType value; - - ConvertedEntry(Entry entry) { - this.entry = entry; - } - - @Override - public ConvertedType getKey() { - if (name == null) { - name = typeConverter.toConvertedType(entry.getKey()); - } - return name; - } - - @Override - public ConvertedType getValue() { - if (value == null) { - value = typeConverter.toConvertedType(entry.getValue()); - } - return value; - } - - @Override - public ConvertedType setValue(ConvertedType value) { - ConvertedType old = getValue(); - entry.setValue(typeConverter.toUnconvertedType(value)); - return old; - } - - @Override - public String toString() { - return entry.toString(); - } - } -} 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 f1e6cb77f2..c0c44dcf82 100644 --- a/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java @@ -14,30 +14,28 @@ */ package io.netty.handler.codec; +import io.netty.util.HashingStrategy; import io.netty.util.concurrent.FastThreadLocal; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; -import java.util.Comparator; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; 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.Date; -import java.util.TreeMap; +import static io.netty.util.HashingStrategy.JAVA_HASHER; 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}; @@ -45,28 +43,71 @@ import static java.util.Collections.unmodifiableSet; * @param the type of the header name and value. */ public class DefaultHeaders implements Headers { + /** + * How big the underlying array is for the hash data structure. + *

+ * This should be a power of 2 so the {@link #index(int)} method can full address the memory. + */ + private static final int ARRAY_SIZE = 1 << 4; + private static final int HASH_MASK = ARRAY_SIZE - 1; + + private static int index(int hash) { + // Fold the upper 16 bits onto the 16 lower bits so more of the hash code is represented + // when translating to an index. + return ((hash >>> 16) ^ hash) & HASH_MASK; + } + + @SuppressWarnings("unchecked") + private final HeaderEntry[] entries = new DefaultHeaders.HeaderEntry[ARRAY_SIZE]; + protected final HeaderEntry head = new HeaderEntry(); - private final Map map; - private final NameValidator nameValidator; private final ValueConverter valueConverter; + private final NameValidator nameValidator; + private final HashingStrategy hashingStrategy; int size; - boolean hasMultipleValues; + public interface NameValidator { + void validateName(T name); - public DefaultHeaders(Map map, NameValidator nameValidator, ValueConverter valueConverter) { - this.map = checkNotNull(map, "map"); - this.nameValidator = checkNotNull(nameValidator, "nameValidator"); + @SuppressWarnings("rawtypes") + NameValidator NOT_NULL = new NameValidator() { + @Override + public void validateName(Object name) { + checkNotNull(name, "name"); + } + }; + } + + @SuppressWarnings("unchecked") + public DefaultHeaders(ValueConverter valueConverter) { + this(JAVA_HASHER, valueConverter, NameValidator.NOT_NULL); + } + + public DefaultHeaders(HashingStrategy nameHashingStrategy, + ValueConverter valueConverter, NameValidator nameValidator) { this.valueConverter = checkNotNull(valueConverter, "valueConverter"); + this.nameValidator = checkNotNull(nameValidator, "nameValidator"); + this.hashingStrategy = checkNotNull(nameHashingStrategy, "nameHashingStrategy"); + head.before = head.after = head; } @Override - @SuppressWarnings("unchecked") public T get(T name) { - Object value = map.get(name); - if (isList(value)) { - return ((List) value).get(0); + checkNotNull(name, "name"); + + int h = hashingStrategy.hashCode(name); + int i = index(h); + HeaderEntry e = entries[i]; + T value = null; + // loop until the first header was found + while (e != null) { + if (e.hash == h && hashingStrategy.equals(name, e.key)) { + value = e.value; + } + + e = e.next; } - return (T) value; + return value; } @Override @@ -79,19 +120,9 @@ public class DefaultHeaders implements Headers { } @Override - @SuppressWarnings("unchecked") public T getAndRemove(T name) { - Object value = map.remove(name); - if (value == null) { - return null; - } - if (isList(value)) { - List value0 = (List) value; - size -= value0.size(); - return value0.get(0); - } - size--; - return (T) value; + int h = hashingStrategy.hashCode(name); + return remove0(h, index(h), checkNotNull(name, "name")); } @Override @@ -104,16 +135,21 @@ public class DefaultHeaders implements Headers { } @Override - @SuppressWarnings("unchecked") public List getAll(T name) { - Object value = map.get(name); - if (isList(value)) { - return unmodifiableList((List) value); + checkNotNull(name, "name"); + + LinkedList values = new LinkedList(); + + int h = hashingStrategy.hashCode(name); + int i = index(h); + HeaderEntry e = entries[i]; + while (e != null) { + if (e.hash == h && hashingStrategy.equals(name, e.key)) { + values.addFirst(e.getValue()); + } + e = e.next; } - if (value == null) { - return emptyList(); - } - return singletonList((T) value); + return values; } @Override @@ -128,16 +164,6 @@ public class DefaultHeaders implements Headers { return get(name) != null; } - @Override - @SuppressWarnings("unchecked") - public boolean contains(T name, T value) { - Object values = map.get(name); - if (isList(values)) { - return ((List) values).contains(value); - } - return values != null && values.equals(value); - } - @Override public boolean containsObject(T name, Object value) { return contains(name, valueConverter.convertObject(checkNotNull(value, "value"))); @@ -145,63 +171,68 @@ public class DefaultHeaders implements Headers { @Override public boolean containsBoolean(T name, boolean value) { - return contains(name, valueConverter.convertBoolean(checkNotNull(value, "value"))); + return contains(name, valueConverter.convertBoolean(value)); } @Override public boolean containsByte(T name, byte value) { - return contains(name, valueConverter.convertByte(checkNotNull(value, "value"))); + return contains(name, valueConverter.convertByte(value)); } @Override public boolean containsChar(T name, char value) { - return contains(name, valueConverter.convertChar(checkNotNull(value, "value"))); + return contains(name, valueConverter.convertChar(value)); } @Override public boolean containsShort(T name, short value) { - return contains(name, valueConverter.convertShort(checkNotNull(value, "value"))); + return contains(name, valueConverter.convertShort(value)); } @Override public boolean containsInt(T name, int value) { - return contains(name, valueConverter.convertInt(checkNotNull(value, "value"))); + return contains(name, valueConverter.convertInt(value)); } @Override public boolean containsLong(T name, long value) { - return contains(name, valueConverter.convertLong(checkNotNull(value, "value"))); + return contains(name, valueConverter.convertLong(value)); } @Override public boolean containsFloat(T name, float value) { - return contains(name, valueConverter.convertFloat(checkNotNull(value, "value"))); + return contains(name, valueConverter.convertFloat(value)); } @Override public boolean containsDouble(T name, double value) { - return contains(name, valueConverter.convertDouble(checkNotNull(value, "value"))); + return contains(name, valueConverter.convertDouble(value)); } @Override public boolean containsTimeMillis(T name, long value) { - return contains(name, valueConverter.convertTimeMillis(checkNotNull(value, "value"))); + return contains(name, valueConverter.convertTimeMillis(value)); } - @Override @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; - } + @Override + public boolean contains(T name, T value) { + return contains(name, value, JAVA_HASHER); + } + + public final boolean contains(T name, T value, HashingStrategy valueHashingStrategy) { + checkNotNull(name, "name"); + + int h = hashingStrategy.hashCode(name); + int i = index(h); + HeaderEntry e = entries[i]; + while (e != null) { + if (e.hash == h && hashingStrategy.equals(name, e.key) && valueHashingStrategy.equals(value, e.value)) { + return true; } - return false; + e = e.next; } - return values != null && valueComparator.compare((T) values, value) == 0; + return false; } @Override @@ -211,54 +242,51 @@ public class DefaultHeaders implements Headers { @Override public boolean isEmpty() { - return map.isEmpty(); + return head == head.after; } @Override public Set names() { - return unmodifiableSet(map.keySet()); + if (isEmpty()) { + return Collections.emptySet(); + } + Set names = new LinkedHashSet(size()); + HeaderEntry e = head.after; + while (e != head) { + names.add(e.getKey()); + e = e.after; + } + return names; } @Override public Headers add(T name, T value) { - validateName(name); + nameValidator.validateName(name); checkNotNull(value, "value"); - Object prevValue = map.put(name, value); - size++; - if (prevValue != null) { - appendValue(name, value, prevValue); - } + int h = hashingStrategy.hashCode(name); + int i = index(h); + add0(h, i, name, value); 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) { - checkNotNull(values, "values"); - for (T value : values) { - add(name, value); + nameValidator.validateName(name); + int h = hashingStrategy.hashCode(name); + int i = index(h); + for (T v: values) { + add0(h, i, name, v); } return this; } @Override public Headers add(T name, T... values) { - checkNotNull(values, "values"); - for (int i = 0; i < values.length; i++) { - add(name, values[i]); + nameValidator.validateName(name); + int h = hashingStrategy.hashCode(name); + int i = index(h); + for (T v: values) { + add0(h, i, name, v); } return this; } @@ -337,46 +365,69 @@ public class DefaultHeaders implements Headers { if (headers == this) { throw new IllegalArgumentException("can't add to itself."); } - for (Entry header : headers) { - add(header.getKey(), header.getValue()); + if (headers instanceof DefaultHeaders) { + @SuppressWarnings("unchecked") + DefaultHeaders defaultHeaders = (DefaultHeaders) headers; + HeaderEntry e = defaultHeaders.head.after; + while (e != defaultHeaders.head) { + add(e.key, e.value); + e = e.after; + } + return this; + } else { + for (Entry header : headers) { + add(header.getKey(), header.getValue()); + } } return this; } @Override public Headers set(T name, T value) { - validateName(name); + nameValidator.validateName(name); checkNotNull(value, "value"); - Object oldValue = map.put(name, value); - updateSizeAfterSet(oldValue, 1); + int h = hashingStrategy.hashCode(name); + int i = index(h); + remove0(h, i, name); + add0(h, i, name, value); return this; } @Override public Headers set(T name, Iterable values) { - validateName(name); + nameValidator.validateName(name); checkNotNull(values, "values"); - List list = newList(); - for (T value : values) { - list.add(checkNotNull(value, "value")); + + int h = hashingStrategy.hashCode(name); + int i = index(h); + + remove0(h, i, name); + for (T v: values) { + if (v == null) { + break; + } + add0(h, i, name, v); } - Object oldValue = map.put(name, list); - updateSizeAfterSet(oldValue, list.size()); - hasMultipleValues = true; + return this; } @Override public Headers set(T name, T... values) { - validateName(name); + nameValidator.validateName(name); checkNotNull(values, "values"); - List list = newList(values.length); - for (int i = 0; i < values.length; i++) { - list.add(checkNotNull(values[i], "value")); + + int h = hashingStrategy.hashCode(name); + int i = index(h); + + remove0(h, i, name); + for (T v: values) { + if (v == null) { + break; + } + add0(h, i, name, v); } - Object oldValue = map.put(name, list); - updateSizeAfterSet(oldValue, values.length); - hasMultipleValues = true; + return this; } @@ -389,33 +440,39 @@ public class DefaultHeaders implements Headers { @Override public Headers setObject(T name, Iterable values) { - validateName(name); + nameValidator.validateName(name); checkNotNull(values, "values"); - List list = newList(); - for (Object value : values) { - value = checkNotNull(value, "value"); - T convertedValue = checkNotNull(valueConverter.convertObject(value), "convertedValue"); - list.add(convertedValue); + + int h = hashingStrategy.hashCode(name); + int i = index(h); + + remove0(h, i, name); + for (Object v: values) { + if (v == null) { + break; + } + add0(h, i, name, valueConverter.convertObject(v)); } - Object oldValue = map.put(name, list); - updateSizeAfterSet(oldValue, list.size()); - hasMultipleValues = true; + return this; } @Override public Headers setObject(T name, Object... values) { - validateName(name); + nameValidator.validateName(name); checkNotNull(values, "values"); - 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); + + int h = hashingStrategy.hashCode(name); + int i = index(h); + + remove0(h, i, name); + for (Object v: values) { + if (v == null) { + break; + } + add0(h, i, name, valueConverter.convertObject(v)); } - Object oldValue = map.put(name, list); - updateSizeAfterSet(oldValue, list.size()); - hasMultipleValues = true; + return this; } @@ -471,7 +528,17 @@ public class DefaultHeaders implements Headers { return this; } clear(); - add(headers); + if (headers instanceof DefaultHeaders) { + @SuppressWarnings("unchecked") + DefaultHeaders defaultHeaders = (DefaultHeaders) headers; + HeaderEntry e = defaultHeaders.head.after; + while (e != defaultHeaders.head) { + add(e.key, e.value); + e = e.after; + } + } else { + add(headers); + } return this; } @@ -497,227 +564,147 @@ public class DefaultHeaders implements Headers { @Override public Headers clear() { - map.clear(); - hasMultipleValues = false; + Arrays.fill(entries, null); + head.before = head.after = head; size = 0; return this; } @Override - @SuppressWarnings("unchecked") public Iterator> iterator() { - Object iter = map.entrySet().iterator(); - return !hasMultipleValues ? (Iterator>) iter - : new NameValueIterator((Iterator>) iter); + return new HeaderIterator(); } @Override public Boolean getBoolean(T name) { T v = get(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToBoolean(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToBoolean(v) : null; } @Override public boolean getBoolean(T name, boolean defaultValue) { Boolean v = getBoolean(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Byte getByte(T name) { T v = get(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToByte(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToByte(v) : null; } @Override public byte getByte(T name, byte defaultValue) { Byte v = getByte(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Character getChar(T name) { T v = get(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToChar(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToChar(v) : null; } @Override public char getChar(T name, char defaultValue) { Character v = getChar(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Short getShort(T name) { T v = get(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToShort(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToShort(v) : null; } @Override public short getShort(T name, short defaultValue) { Short v = getShort(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Integer getInt(T name) { T v = get(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToInt(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToInt(v) : null; } @Override public int getInt(T name, int defaultValue) { Integer v = getInt(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Long getLong(T name) { T v = get(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToLong(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToLong(v) : null; } @Override public long getLong(T name, long defaultValue) { Long v = getLong(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Float getFloat(T name) { T v = get(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToFloat(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToFloat(v) : null; } @Override public float getFloat(T name, float defaultValue) { Float v = getFloat(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Double getDouble(T name) { T v = get(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToDouble(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToDouble(v) : null; } @Override public double getDouble(T name, double defaultValue) { Double v = getDouble(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Long getTimeMillis(T name) { T v = get(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToTimeMillis(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToTimeMillis(v) : null; } @Override public long getTimeMillis(T name, long defaultValue) { Long v = getTimeMillis(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Boolean getBooleanAndRemove(T name) { T v = getAndRemove(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToBoolean(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToBoolean(v) : null; } @Override public boolean getBooleanAndRemove(T name, boolean defaultValue) { Boolean v = getBooleanAndRemove(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Byte getByteAndRemove(T name) { T v = getAndRemove(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToByte(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToByte(v) : null; } @Override public byte getByteAndRemove(T name, byte defaultValue) { Byte v = getByteAndRemove(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override @@ -736,148 +723,143 @@ public class DefaultHeaders implements Headers { @Override public char getCharAndRemove(T name, char defaultValue) { Character v = getCharAndRemove(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Short getShortAndRemove(T name) { T v = getAndRemove(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToShort(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToShort(v) : null; } @Override public short getShortAndRemove(T name, short defaultValue) { Short v = getShortAndRemove(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Integer getIntAndRemove(T name) { T v = getAndRemove(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToInt(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToInt(v) : null; } @Override public int getIntAndRemove(T name, int defaultValue) { Integer v = getIntAndRemove(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Long getLongAndRemove(T name) { T v = getAndRemove(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToLong(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToLong(v) : null; } @Override public long getLongAndRemove(T name, long defaultValue) { Long v = getLongAndRemove(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Float getFloatAndRemove(T name) { T v = getAndRemove(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToFloat(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToFloat(v) : null; } @Override public float getFloatAndRemove(T name, float defaultValue) { Float v = getFloatAndRemove(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Double getDoubleAndRemove(T name) { T v = getAndRemove(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToDouble(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToDouble(v) : null; } @Override public double getDoubleAndRemove(T name, double defaultValue) { Double v = getDoubleAndRemove(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } @Override public Long getTimeMillisAndRemove(T name) { T v = getAndRemove(name); - if (v == null) { - return null; - } - try { - return valueConverter.convertToTimeMillis(v); - } catch (Throwable ignored) { - return null; - } + return v != null ? valueConverter.convertToTimeMillis(v) : null; } @Override public long getTimeMillisAndRemove(T name, long defaultValue) { Long v = getTimeMillisAndRemove(name); - return v == null ? defaultValue : v; + return v != null ? v : defaultValue; } + @SuppressWarnings("unchecked") @Override public boolean equals(Object o) { - if (!(o instanceof DefaultHeaders)) { + if (!(o instanceof Headers)) { return false; } - @SuppressWarnings("unchecked") - DefaultHeaders other = (DefaultHeaders) o; - return size() == other.size() && map.equals(other.map); + + return equals((Headers) o, JAVA_HASHER); + } + + @SuppressWarnings("unchecked") + @Override + public int hashCode() { + return hashCode(JAVA_HASHER); } /** - * 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. + * Test this object for equality against {@code h2}. + * @param h2 The object to check equality for. + * @param valueHashingStrategy Defines how values will be compared for equality. + * @return {@code true} if this object equals {@code h2} given {@code valueHashingStrategy}. + * {@code false} otherwise. */ - @Override - public int hashCode() { - return size(); + public final boolean equals(Headers h2, HashingStrategy valueHashingStrategy) { + if (h2.size() != size()) { + return false; + } + + if (this == h2) { + return true; + } + + for (T name : names()) { + List otherValues = h2.getAll(name); + List values = getAll(name); + if (otherValues.size() != values.size()) { + return false; + } + for (int i = 0; i < otherValues.size(); i++) { + if (!valueHashingStrategy.equals(otherValues.get(i), values.get(i))) { + return false; + } + } + } + return true; + } + + /** + * Generate a hash code for this object given a {@link HashingStrategy} to generate hash codes for + * individual values. + * @param valueHashingStrategy Defines how values will be hashed. + */ + public final int hashCode(HashingStrategy valueHashingStrategy) { + int result = 1; + for (T name : names()) { + result = 31 * result + hashingStrategy.hashCode(name); + List values = getAll(name); + for (int i = 0; i < values.size(); ++i) { + result = 31 * result + valueHashingStrategy.hashCode(values.get(i)); + } + } + return result; } @Override @@ -895,112 +877,55 @@ public class DefaultHeaders implements Headers { return builder.append(']').toString(); } + protected HeaderEntry newHeaderEntry(int h, T name, T value, HeaderEntry next) { + return new HeaderEntry(h, name, value, next, head); + } + protected ValueConverter valueConverter() { return valueConverter; } - @SuppressWarnings("unchecked") - private void updateSizeAfterSet(Object oldValue, int newSize) { - if (isList(oldValue)) { - size -= ((List) oldValue).size(); - } else if (oldValue != null) { - size--; - } - size += newSize; + private void add0(int h, int i, T name, T value) { + // Update the hash table. + entries[i] = newHeaderEntry(h, name, value, entries[i]); + ++size; } - private void validateName(T name) { - checkNotNull(name, "name"); - nameValidator.validate(name); - } - - private static boolean isList(Object value) { - return value != null && value.getClass() == ValuesList.class; - } - - private List newList() { - return newList(4); - } - - private List newList(int initialSize) { - return new ValuesList(initialSize); - } - - private final class NameValueIterator implements Iterator> { - private final Iterator> iter; - private T name; - private List values; - private int valuesIdx; - - NameValueIterator(Iterator> iter) { - this.iter = iter; + /** + * @return the first value inserted whose hash code equals {@code h} and whose name is equal to {@code name}. + */ + private T remove0(int h, int i, T name) { + HeaderEntry e = entries[i]; + if (e == null) { + return null; } - @Override - public boolean hasNext() { - return iter.hasNext() || values != null; - } - - @Override - @SuppressWarnings("unchecked") - public Entry next() { - if (name == null) { - Entry entry = iter.next(); - if (!isList(entry.getValue())) { - return (Entry) entry; - } - initListIterator(entry); + T value = null; + HeaderEntry next = e.next; + while (next != null) { + if (next.hash == h && hashingStrategy.equals(name, next.key)) { + value = next.value; + e.next = next.next; + next.remove(); + --size; + } else { + e = next; } - return fetchNextEntryFromList(); + + next = e.next; } - @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; + e = entries[i]; + if (e.hash == h && hashingStrategy.equals(name, e.key)) { + if (value == null) { + value = e.value; } - return next; + entries[i] = e.next; + e.remove(); + --size; } - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - 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); - } + return value; } /** @@ -1012,7 +937,7 @@ public class DefaultHeaders implements Headers { *

  • Sun Nov 6 08:49:37 1994: obsolete specification
  • * */ - static final class HeaderDateFormat { + public static final class HeaderDateFormat { private static final FastThreadLocal dateFormatThreadLocal = new FastThreadLocal() { @Override @@ -1074,44 +999,104 @@ public class DefaultHeaders implements Headers { } } - public interface NameValidator { - void validate(T name); - } + private final class HeaderIterator implements Iterator> { + private HeaderEntry current = head; - 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 boolean hasNext() { + return current.after != head; } @Override - public void validate(T name) { + public Entry next() { + current = current.after; + + if (current == head) { + throw new NoSuchElementException(); + } + + return current; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("read-only iterator"); } } - public static boolean comparatorEquals(Headers a, Headers b, Comparator valueComparator) { - if (a.size() != b.size()) { - return false; + protected static class HeaderEntry implements Map.Entry { + protected final int hash; + protected final T key; + protected T value; + /** + * In bucket linked list + */ + protected HeaderEntry next; + /** + * Overall insertion order linked list + */ + protected HeaderEntry before, after; + + protected HeaderEntry(int hash, T key) { + this.hash = hash; + this.key = key; } - 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; - } - } + + HeaderEntry(int hash, T key, T value, HeaderEntry next, HeaderEntry head) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + + after = head; + before = head.before; + pointNeighborsToThis(); + } + + HeaderEntry() { + hash = -1; + key = null; + } + + protected final void pointNeighborsToThis() { + before.after = this; + after.before = this; + } + + public final HeaderEntry before() { + return before; + } + + public final HeaderEntry after() { + return after; + } + + protected void remove() { + before.after = after; + after.before = before; + } + + @Override + public final T getKey() { + return key; + } + + @Override + public final T getValue() { + return value; + } + + @Override + public final T setValue(T value) { + checkNotNull(value, "value"); + T oldValue = this.value; + this.value = value; + return oldValue; + } + + @Override + public final String toString() { + return key.toString() + '=' + value.toString(); } - 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 deleted file mode 100644 index 7114c1b0a2..0000000000 --- a/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java +++ /dev/null @@ -1,616 +0,0 @@ -/* - * 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: - * - * 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; - -import static io.netty.util.AsciiString.CHARSEQUENCE_CASE_INSENSITIVE_ORDER; -import static io.netty.util.AsciiString.CHARSEQUENCE_CASE_SENSITIVE_ORDER; -import static io.netty.util.internal.StringUtil.COMMA; -import io.netty.util.AsciiString; -import io.netty.util.internal.PlatformDependent; -import io.netty.util.internal.StringUtil; - -import java.text.ParseException; -import java.util.Iterator; -import java.util.Map; -import java.util.TreeMap; - -public class DefaultTextHeaders extends DefaultConvertibleHeaders implements TextHeaders { - - public static final NameValidator NO_NAME_VALIDATOR = NoNameValidator.instance(); - - private final ValuesComposer valuesComposer; - - public DefaultTextHeaders() { - this(true); - } - - public DefaultTextHeaders(boolean singleHeaderFields) { - this(new TreeMap(CHARSEQUENCE_CASE_INSENSITIVE_ORDER), NO_NAME_VALIDATOR, - CharSequenceConverter.INSTANCE, singleHeaderFields); - } - - 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, - ignoreCase ? CHARSEQUENCE_CASE_INSENSITIVE_ORDER : CHARSEQUENCE_CASE_SENSITIVE_ORDER); - } - - @Override - public TextHeaders add(CharSequence name, CharSequence value) { - return valuesComposer.add(name, value); - } - - @Override - public TextHeaders add(CharSequence name, Iterable values) { - return valuesComposer.add(name, values); - } - - @Override - public TextHeaders add(CharSequence name, CharSequence... values) { - return valuesComposer.add(name, values); - } - - @Override - public TextHeaders addObject(CharSequence name, Object value) { - return valuesComposer.addObject(name, value); - } - - @Override - public TextHeaders addObject(CharSequence name, Iterable values) { - return valuesComposer.addObject(name, values); - } - - @Override - public TextHeaders addObject(CharSequence name, Object... values) { - return valuesComposer.addObject(name, values); - } - - @Override - public TextHeaders addBoolean(CharSequence name, boolean value) { - super.addBoolean(name, value); - return this; - } - - @Override - public TextHeaders addChar(CharSequence name, char value) { - super.addChar(name, value); - return this; - } - - @Override - public TextHeaders addByte(CharSequence name, byte value) { - super.addByte(name, value); - return this; - } - - @Override - public TextHeaders addShort(CharSequence name, short value) { - super.addShort(name, value); - return this; - } - - @Override - public TextHeaders addInt(CharSequence name, int value) { - super.addInt(name, value); - return this; - } - - @Override - public TextHeaders addLong(CharSequence name, long value) { - super.addLong(name, value); - return this; - } - - @Override - public TextHeaders addFloat(CharSequence name, float value) { - super.addFloat(name, value); - return this; - } - - @Override - public TextHeaders addDouble(CharSequence name, double value) { - super.addDouble(name, value); - return this; - } - - @Override - public TextHeaders addTimeMillis(CharSequence name, long value) { - super.addTimeMillis(name, value); - return this; - } - - @Override - public TextHeaders add(TextHeaders headers) { - super.add(headers); - return this; - } - - @Override - public TextHeaders set(CharSequence name, CharSequence value) { - super.set(name, value); - return this; - } - - @Override - public TextHeaders set(CharSequence name, Iterable values) { - return valuesComposer.set(name, values); - } - - @Override - public TextHeaders set(CharSequence name, CharSequence... values) { - return valuesComposer.set(name, values); - } - - @Override - public TextHeaders setObject(CharSequence name, Object value) { - super.setObject(name, value); - return this; - } - - @Override - public TextHeaders setObject(CharSequence name, Iterable values) { - return valuesComposer.setObject(name, values); - } - - @Override - public TextHeaders setObject(CharSequence name, Object... values) { - return valuesComposer.setObject(name, values); - } - - @Override - public TextHeaders setBoolean(CharSequence name, boolean value) { - super.setBoolean(name, value); - return this; - } - - @Override - public TextHeaders setChar(CharSequence name, char value) { - super.setChar(name, value); - return this; - } - - @Override - public TextHeaders setByte(CharSequence name, byte value) { - super.setByte(name, value); - return this; - } - - @Override - public TextHeaders setShort(CharSequence name, short value) { - super.setShort(name, value); - return this; - } - - @Override - public TextHeaders setInt(CharSequence name, int value) { - super.setInt(name, value); - return this; - } - - @Override - public TextHeaders setLong(CharSequence name, long value) { - super.setLong(name, value); - return this; - } - - @Override - public TextHeaders setFloat(CharSequence name, float value) { - super.setFloat(name, value); - return this; - } - - @Override - public TextHeaders setDouble(CharSequence name, double value) { - super.setDouble(name, value); - return this; - } - - @Override - public TextHeaders setTimeMillis(CharSequence name, long value) { - super.setTimeMillis(name, value); - return this; - } - - @Override - public TextHeaders set(TextHeaders headers) { - super.set(headers); - return this; - } - - @Override - public TextHeaders setAll(TextHeaders headers) { - super.setAll(headers); - return this; - } - - @Override - public TextHeaders clear() { - super.clear(); - return this; - } - - /* - * This interface enables different implementations for adding/setting header values. - * Concrete implementations can control how values are added, for example to add all - * values for a header as a comma separated string instead of adding them as multiple - * headers with a single value. - */ - private interface ValuesComposer { - TextHeaders add(CharSequence name, CharSequence value); - TextHeaders add(CharSequence name, CharSequence... values); - TextHeaders add(CharSequence name, Iterable values); - - TextHeaders addObject(CharSequence name, Object value); - TextHeaders addObject(CharSequence name, Iterable values); - TextHeaders addObject(CharSequence name, Object... values); - - TextHeaders set(CharSequence name, CharSequence... values); - TextHeaders set(CharSequence name, Iterable values); - - TextHeaders setObject(CharSequence name, Object... values); - TextHeaders setObject(CharSequence name, Iterable values); - } - - /* - * Will add multiple values for the same header as multiple separate headers. - */ - private final class MultipleFieldsValueComposer implements ValuesComposer { - - @Override - public TextHeaders add(CharSequence name, CharSequence value) { - DefaultTextHeaders.super.add(name, value); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders add(CharSequence name, CharSequence... values) { - DefaultTextHeaders.super.add(name, values); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders add(CharSequence name, Iterable values) { - DefaultTextHeaders.super.add(name, values); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders addObject(CharSequence name, Object value) { - DefaultTextHeaders.super.addObject(name, value); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders addObject(CharSequence name, Iterable values) { - DefaultTextHeaders.super.addObject(name, values); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders addObject(CharSequence name, Object... values) { - DefaultTextHeaders.super.addObject(name, values); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders set(CharSequence name, CharSequence... values) { - DefaultTextHeaders.super.set(name, values); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders set(CharSequence name, Iterable values) { - DefaultTextHeaders.super.set(name, values); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders setObject(CharSequence name, Object... values) { - DefaultTextHeaders.super.setObject(name, values); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders setObject(CharSequence name, Iterable values) { - DefaultTextHeaders.super.setObject(name, values); - return DefaultTextHeaders.this; - } - } - - /** - * Will add multiple values for the same header as single header with a comma separated list of values. - * - * Please refer to section 3.2.2 Field Order - * of RFC-7230 for details. - */ - private final class SingleHeaderValuesComposer implements ValuesComposer { - - private final ValueConverter valueConverter = valueConverter(); - private CsvValueEscaper objectEscaper; - private CsvValueEscaper charSequenceEscaper; - - private CsvValueEscaper objectEscaper() { - if (objectEscaper == null) { - objectEscaper = new CsvValueEscaper() { - @Override - public CharSequence escape(Object value) { - return StringUtil.escapeCsv(valueConverter.convertObject(value)); - } - }; - } - return objectEscaper; - } - - private CsvValueEscaper charSequenceEscaper() { - if (charSequenceEscaper == null) { - charSequenceEscaper = new CsvValueEscaper() { - @Override - public CharSequence escape(CharSequence value) { - return StringUtil.escapeCsv(value); - } - }; - } - return charSequenceEscaper; - } - - @Override - public TextHeaders add(CharSequence name, CharSequence value) { - return addEscapedValue(name, StringUtil.escapeCsv(value)); - } - - @Override - public TextHeaders add(CharSequence name, CharSequence... values) { - return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values)); - } - - @Override - public TextHeaders add(CharSequence name, Iterable values) { - return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values)); - } - - @Override - public TextHeaders addObject(CharSequence name, Object value) { - return addEscapedValue(name, objectEscaper().escape(value)); - } - - @Override - public TextHeaders addObject(CharSequence name, Iterable values) { - return addEscapedValue(name, commaSeparate(objectEscaper(), values)); - } - - @Override - public TextHeaders addObject(CharSequence name, Object... values) { - return addEscapedValue(name, commaSeparate(objectEscaper(), values)); - } - - @Override - public TextHeaders set(CharSequence name, CharSequence... values) { - DefaultTextHeaders.super.set(name, commaSeparate(charSequenceEscaper(), values)); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders set(CharSequence name, Iterable values) { - DefaultTextHeaders.super.set(name, commaSeparate(charSequenceEscaper(), values)); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders setObject(CharSequence name, Object... values) { - DefaultTextHeaders.super.set(name, commaSeparate(objectEscaper(), values)); - return DefaultTextHeaders.this; - } - - @Override - public TextHeaders setObject(CharSequence name, Iterable values) { - DefaultTextHeaders.super.set(name, commaSeparate(objectEscaper(), values)); - return DefaultTextHeaders.this; - } - - private TextHeaders addEscapedValue(CharSequence name, CharSequence escapedValue) { - CharSequence currentValue = DefaultTextHeaders.super.get(name); - if (currentValue == null) { - DefaultTextHeaders.super.add(name, escapedValue); - } else { - DefaultTextHeaders.super.set(name, commaSeparateEscapedValues(currentValue, escapedValue)); - } - return DefaultTextHeaders.this; - } - - private CharSequence commaSeparate(CsvValueEscaper escaper, T... values) { - 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++) { - sb.append(escaper.escape(values[i])).append(COMMA); - } - sb.append(escaper.escape(values[end])); - } - return sb; - } - - private CharSequence commaSeparate(CsvValueEscaper escaper, Iterable values) { - StringBuilder sb = new StringBuilder(); - Iterator iterator = values.iterator(); - if (iterator.hasNext()) { - T next = iterator.next(); - while (iterator.hasNext()) { - sb.append(escaper.escape(next)).append(COMMA); - next = iterator.next(); - } - sb.append(escaper.escape(next)); - } - return sb; - } - - private CharSequence commaSeparateEscapedValues(CharSequence currentValue, CharSequence value) { - return new StringBuilder(currentValue.length() + 1 + value.length()) - .append(currentValue) - .append(COMMA) - .append(value); - } - } - - /** - * Escapes comma separated values (CSV). - * - * @param The type that a concrete implementation handles - */ - private interface CsvValueEscaper { - /** - * Appends the value to the specified {@link StringBuilder}, escaping if necessary. - * - * @param value the value to be appended, escaped if necessary - */ - CharSequence escape(T value); - } - - private static final class CharSequenceToStringConverter implements TypeConverter { - - private static final CharSequenceToStringConverter INSTANCE = new CharSequenceToStringConverter(); - - @Override - public String toConvertedType(CharSequence value) { - return value.toString(); - } - - @Override - public CharSequence toUnconvertedType(String value) { - return value; - } - } - - public 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/EmptyBinaryHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyBinaryHeaders.java deleted file mode 100644 index 98f248b2ac..0000000000 --- a/codec/src/main/java/io/netty/handler/codec/EmptyBinaryHeaders.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 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: - * - * 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; - -import io.netty.util.ByteString; - -public class EmptyBinaryHeaders extends EmptyHeaders implements BinaryHeaders { - protected EmptyBinaryHeaders() { - } - - @Override - public BinaryHeaders add(ByteString name, ByteString value) { - super.add(name, value); - return this; - } - - @Override - public BinaryHeaders add(ByteString name, Iterable values) { - super.add(name, values); - return this; - } - - @Override - public BinaryHeaders add(ByteString name, ByteString... values) { - super.add(name, values); - return this; - } - - @Override - public BinaryHeaders addObject(ByteString name, Object value) { - super.addObject(name, value); - return this; - } - - @Override - public BinaryHeaders addObject(ByteString name, Iterable values) { - super.addObject(name, values); - return this; - } - - @Override - public BinaryHeaders addObject(ByteString name, Object... values) { - super.addObject(name, values); - return this; - } - - @Override - public BinaryHeaders addBoolean(ByteString name, boolean value) { - super.addBoolean(name, value); - return this; - } - - @Override - public BinaryHeaders addChar(ByteString name, char value) { - super.addChar(name, value); - return this; - } - - @Override - public BinaryHeaders addByte(ByteString name, byte value) { - super.addByte(name, value); - return this; - } - - @Override - public BinaryHeaders addShort(ByteString name, short value) { - super.addShort(name, value); - return this; - } - - @Override - public BinaryHeaders addInt(ByteString name, int value) { - super.addInt(name, value); - return this; - } - - @Override - public BinaryHeaders addLong(ByteString name, long value) { - super.addLong(name, value); - return this; - } - - @Override - public BinaryHeaders addFloat(ByteString name, float value) { - super.addFloat(name, value); - return this; - } - - @Override - public BinaryHeaders addDouble(ByteString name, double value) { - super.addDouble(name, value); - return this; - } - - @Override - public BinaryHeaders addTimeMillis(ByteString name, long value) { - super.addTimeMillis(name, value); - return this; - } - - @Override - public BinaryHeaders add(BinaryHeaders headers) { - super.add(headers); - return this; - } - - @Override - public BinaryHeaders set(ByteString name, ByteString value) { - super.set(name, value); - return this; - } - - @Override - public BinaryHeaders set(ByteString name, Iterable values) { - super.set(name, values); - return this; - } - - @Override - public BinaryHeaders set(ByteString name, ByteString... values) { - super.set(name, values); - return this; - } - - @Override - public BinaryHeaders setObject(ByteString name, Object value) { - super.setObject(name, value); - return this; - } - - @Override - public BinaryHeaders setObject(ByteString name, Iterable values) { - super.setObject(name, values); - return this; - } - - @Override - public BinaryHeaders setObject(ByteString name, Object... values) { - super.setObject(name, values); - return this; - } - - @Override - public BinaryHeaders setBoolean(ByteString name, boolean value) { - super.setBoolean(name, value); - return this; - } - - @Override - public BinaryHeaders setChar(ByteString name, char value) { - super.setChar(name, value); - return this; - } - - @Override - public BinaryHeaders setByte(ByteString name, byte value) { - super.setByte(name, value); - return this; - } - - @Override - public BinaryHeaders setShort(ByteString name, short value) { - super.setShort(name, value); - return this; - } - - @Override - public BinaryHeaders setInt(ByteString name, int value) { - super.setInt(name, value); - return this; - } - - @Override - public BinaryHeaders setLong(ByteString name, long value) { - super.setLong(name, value); - return this; - } - - @Override - public BinaryHeaders setFloat(ByteString name, float value) { - super.setFloat(name, value); - return this; - } - - @Override - public BinaryHeaders setDouble(ByteString name, double value) { - super.setDouble(name, value); - return this; - } - - @Override - public BinaryHeaders setTimeMillis(ByteString name, long value) { - super.setTimeMillis(name, value); - return this; - } - - @Override - public BinaryHeaders set(BinaryHeaders headers) { - super.set(headers); - return this; - } - - @Override - public BinaryHeaders setAll(BinaryHeaders headers) { - super.setAll(headers); - return this; - } - - @Override - public BinaryHeaders clear() { - super.clear(); - return this; - } -} diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java deleted file mode 100644 index ea3b077bbe..0000000000 --- a/codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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: - * - * 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; - -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; - -public class EmptyConvertibleHeaders extends - EmptyHeaders implements ConvertibleHeaders { - - @Override - public ConvertedType getAndConvert(UnconvertedType name) { - return null; - } - - @Override - public ConvertedType getAndConvert(UnconvertedType name, ConvertedType defaultValue) { - return defaultValue; - } - - @Override - public ConvertedType getAndRemoveAndConvert(UnconvertedType name) { - return null; - } - - @Override - public ConvertedType getAndRemoveAndConvert(UnconvertedType name, ConvertedType defaultValue) { - return defaultValue; - } - - @Override - public List getAllAndConvert(UnconvertedType name) { - return Collections.emptyList(); - } - - @Override - public List getAllAndRemoveAndConvert(UnconvertedType name) { - return Collections.emptyList(); - } - - @Override - public Iterator> iteratorConverted() { - List> empty = Collections.emptyList(); - return empty.iterator(); - } - - @Override - public Set namesAndConvert(Comparator comparator) { - return Collections.emptySet(); - } -} 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 47e9c5e266..299c4bc969 100644 --- a/codec/src/main/java/io/netty/handler/codec/EmptyHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/EmptyHeaders.java @@ -15,7 +15,6 @@ package io.netty.handler.codec; import java.util.Collections; -import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; @@ -292,11 +291,6 @@ public class EmptyHeaders implements Headers { return false; } - @Override - public boolean contains(T name, T value, Comparator valueComparator) { - return false; - } - @Override public int size() { return 0; diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java deleted file mode 100644 index d9db4e31cf..0000000000 --- a/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * 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: - * - * 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; - -public class EmptyTextHeaders extends EmptyConvertibleHeaders implements TextHeaders { - protected EmptyTextHeaders() { - } - - @Override - public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { - return false; - } - - @Override - public TextHeaders add(CharSequence name, CharSequence value) { - super.add(name, value); - return this; - } - - @Override - public TextHeaders add(CharSequence name, Iterable values) { - super.add(name, values); - return this; - } - - @Override - public TextHeaders add(CharSequence name, CharSequence... values) { - super.add(name, values); - return this; - } - - @Override - public TextHeaders addObject(CharSequence name, Object value) { - super.addObject(name, value); - return this; - } - - @Override - public TextHeaders addObject(CharSequence name, Iterable values) { - super.addObject(name, values); - return this; - } - - @Override - public TextHeaders addObject(CharSequence name, Object... values) { - super.addObject(name, values); - return this; - } - - @Override - public TextHeaders addBoolean(CharSequence name, boolean value) { - super.addBoolean(name, value); - return this; - } - - @Override - public TextHeaders addChar(CharSequence name, char value) { - super.addChar(name, value); - return this; - } - - @Override - public TextHeaders addByte(CharSequence name, byte value) { - super.addByte(name, value); - return this; - } - - @Override - public TextHeaders addShort(CharSequence name, short value) { - super.addShort(name, value); - return this; - } - - @Override - public TextHeaders addInt(CharSequence name, int value) { - super.addInt(name, value); - return this; - } - - @Override - public TextHeaders addLong(CharSequence name, long value) { - super.addLong(name, value); - return this; - } - - @Override - public TextHeaders addFloat(CharSequence name, float value) { - super.addFloat(name, value); - return this; - } - - @Override - public TextHeaders addDouble(CharSequence name, double value) { - super.addDouble(name, value); - return this; - } - - @Override - public TextHeaders addTimeMillis(CharSequence name, long value) { - super.addTimeMillis(name, value); - return this; - } - - @Override - public TextHeaders add(TextHeaders headers) { - super.add(headers); - return this; - } - - @Override - public TextHeaders set(CharSequence name, CharSequence value) { - super.set(name, value); - return this; - } - - @Override - public TextHeaders set(CharSequence name, Iterable values) { - super.set(name, values); - return this; - } - - @Override - public TextHeaders set(CharSequence name, CharSequence... values) { - super.set(name, values); - return this; - } - - @Override - public TextHeaders setObject(CharSequence name, Object value) { - super.setObject(name, value); - return this; - } - - @Override - public TextHeaders setObject(CharSequence name, Iterable values) { - super.setObject(name, values); - return this; - } - - @Override - public TextHeaders setObject(CharSequence name, Object... values) { - super.setObject(name, values); - return this; - } - - @Override - public TextHeaders setBoolean(CharSequence name, boolean value) { - super.setBoolean(name, value); - return this; - } - - @Override - public TextHeaders setChar(CharSequence name, char value) { - super.setChar(name, value); - return this; - } - - @Override - public TextHeaders setByte(CharSequence name, byte value) { - super.setByte(name, value); - return this; - } - - @Override - public TextHeaders setShort(CharSequence name, short value) { - super.setShort(name, value); - return this; - } - - @Override - public TextHeaders setInt(CharSequence name, int value) { - super.setInt(name, value); - return this; - } - - @Override - public TextHeaders setLong(CharSequence name, long value) { - super.setLong(name, value); - return this; - } - - @Override - public TextHeaders setFloat(CharSequence name, float value) { - super.setFloat(name, value); - return this; - } - - @Override - public TextHeaders setDouble(CharSequence name, double value) { - super.setDouble(name, value); - return this; - } - - @Override - public TextHeaders setTimeMillis(CharSequence name, long value) { - super.setTimeMillis(name, value); - return this; - } - - @Override - public TextHeaders set(TextHeaders headers) { - super.set(headers); - return this; - } - - @Override - public TextHeaders setAll(TextHeaders headers) { - super.setAll(headers); - return this; - } - - @Override - public TextHeaders clear() { - super.clear(); - return this; - } -} diff --git a/codec/src/main/java/io/netty/handler/codec/Headers.java b/codec/src/main/java/io/netty/handler/codec/Headers.java index 5d9e7ebdf4..ccce0091f3 100644 --- a/codec/src/main/java/io/netty/handler/codec/Headers.java +++ b/codec/src/main/java/io/netty/handler/codec/Headers.java @@ -14,57 +14,12 @@ */ package io.netty.handler.codec; -import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Set; public interface Headers extends Iterable> { - - /** - * Converts to/from a generic object to the type of the headers. - */ - interface ValueConverter { - T convertObject(Object value); - - T convertBoolean(boolean value); - - boolean convertToBoolean(T value); - - T convertByte(byte value); - - byte convertToByte(T value); - - T convertChar(char value); - - char convertToChar(T value); - - T convertShort(short value); - - short convertToShort(T value); - - T convertInt(int value); - - int convertToInt(T value); - - T convertLong(long value); - - long convertToLong(T value); - - T convertTimeMillis(long value); - - long convertToTimeMillis(T value); - - T convertFloat(float value); - - float convertToFloat(T value); - - T convertDouble(double value); - - double convertToDouble(T value); - } - /** * 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. @@ -313,7 +268,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @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}. @@ -324,7 +281,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @param defaultValue the default value * @return the {@code boolean} value of the first value in insertion order or {@code defaultValue} if there is no @@ -336,7 +295,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @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}. @@ -347,7 +308,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @param defaultValue the default value * @return the {@code byte} value of the first value in insertion order or {@code defaultValue} if there is no @@ -359,7 +322,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @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}. @@ -370,7 +335,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @param defaultValue the default value * @return the {@code char} value of the first value in insertion order or {@code defaultValue} if there is no @@ -382,7 +349,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @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}. @@ -393,7 +362,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @param defaultValue the default value * @return the {@code short} value of the first value in insertion order or {@code defaultValue} if there is no @@ -405,7 +376,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @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}. @@ -416,7 +389,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @param defaultValue the default value * @return the {@code int} value of the first value in insertion order or {@code defaultValue} if there is no @@ -428,7 +403,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @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}. @@ -439,7 +416,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @param defaultValue the default value * @return the {@code long} value of the first value in insertion order or {@code defaultValue} if there is no @@ -451,7 +430,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @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}. @@ -462,7 +443,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @param defaultValue the default value * @return the {@code float} value of the first value in insertion order or {@code defaultValue} if there is no @@ -474,7 +457,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @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}. @@ -485,7 +470,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @param name the name of the header to search * @param defaultValue the default value * @return the {@code double} value of the first value in insertion order or {@code defaultValue} if there is no @@ -497,7 +484,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @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. @@ -508,7 +497,9 @@ public interface Headers extends Iterable> { * 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. - * + *

    + * If an exception occurs during the translation from type {@code T} all entries with {@code name} may still + * be removed. * @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 @@ -529,6 +520,7 @@ public interface Headers extends Iterable> { * 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 of the header to find */ boolean contains(T name, T value); @@ -622,16 +614,6 @@ public interface Headers extends Iterable> { */ boolean containsTimeMillis(T name, long value); - /** - * 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 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 valueComparator); - /** * Returns the number of headers in this object. */ @@ -687,8 +669,7 @@ public interface Headers extends Iterable> { Headers add(T name, T... values); /** - * 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)}. + * Adds a new header. Before the {@code value} is added, it's converted to type {@code T}. * * @param name the header name * @param value the value of the header @@ -858,7 +839,7 @@ public interface Headers extends Iterable> { /** * 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)}. + * converted to type {@code T}. * * @param name the header name * @param value the value of the header diff --git a/codec/src/main/java/io/netty/handler/codec/HeadersUtils.java b/codec/src/main/java/io/netty/handler/codec/HeadersUtils.java new file mode 100644 index 0000000000..ac7c5973b9 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/HeadersUtils.java @@ -0,0 +1,282 @@ +/* + * 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; + +import java.util.AbstractList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +import static io.netty.util.internal.ObjectUtil.checkNotNull; + +/** + * Provides utility methods related to {@link Headers}. + */ +public final class HeadersUtils { + + private HeadersUtils() { + } + + /** + * {@link Headers#get(Object)} and convert each element of {@link List} to a {@link String}. + * @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. + */ + public static List getAllAsString(Headers headers, T name) { + final List allNames = headers.getAll(name); + return new AbstractList() { + @Override + public String get(int index) { + T value = allNames.get(index); + return value != null ? value.toString() : null; + } + + @Override + public int size() { + return allNames.size(); + } + }; + } + + /** + * {@link Headers#get(Object)} and convert the result to a {@link String}. + * @param headers the headers to get the {@code name} from + * @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 entry. + */ + public static String getAsString(Headers headers, T name) { + T orig = headers.get(name); + return orig != null ? orig.toString() : null; + } + + /** + * {@link Headers#iterator()} which converts each {@link Entry}'s key and value to a {@link String}. + */ + public static Iterator> iteratorAsString( + Iterable> headers) { + return new StringEntryIterator(headers.iterator()); + } + + /** + * {@link Headers#names()} and convert each element of {@link Set} to a {@link String}. + * @param headers the headers to get the names from + * @return a {@link Set} of header values or an empty {@link Set} if no values are found. + */ + public static Set namesAsString(Headers headers) { + return new CharSequenceDelegatingStringSet(headers.names()); + } + + private static final class StringEntryIterator implements Iterator> { + private final Iterator> iter; + + public StringEntryIterator(Iterator> iter) { + this.iter = iter; + } + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public Entry next() { + return new StringEntry(iter.next()); + } + + @Override + public void remove() { + iter.remove(); + } + } + + private static final class StringEntry implements Entry { + private final Entry entry; + private String name; + private String value; + + StringEntry(Entry entry) { + this.entry = entry; + } + + @Override + public String getKey() { + if (name == null) { + name = entry.getKey().toString(); + } + return name; + } + + @Override + public String getValue() { + if (value == null && entry.getValue() != null) { + value = entry.getValue().toString(); + } + return value; + } + + @Override + public String setValue(String value) { + String old = getValue(); + entry.setValue(value); + return old; + } + + @Override + public String toString() { + return entry.toString(); + } + } + + private static final class StringIterator implements Iterator { + private final Iterator iter; + + public StringIterator(Iterator iter) { + this.iter = iter; + } + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public String next() { + T next = iter.next(); + return next != null ? next.toString() : null; + } + + @Override + public void remove() { + iter.remove(); + } + } + + private static final class CharSequenceDelegatingStringSet extends DelegatingStringSet { + public CharSequenceDelegatingStringSet(Set allNames) { + super(allNames); + } + + @Override + public boolean add(String e) { + return allNames.add(e); + } + + @Override + public boolean addAll(Collection c) { + return allNames.addAll(c); + } + } + + private abstract static class DelegatingStringSet implements Set { + protected final Set allNames; + + public DelegatingStringSet(Set allNames) { + this.allNames = checkNotNull(allNames, "allNames"); + } + + @Override + public int size() { + return allNames.size(); + } + + @Override + public boolean isEmpty() { + return allNames.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return allNames.contains(o.toString()); + } + + @Override + public Iterator iterator() { + return new StringIterator(allNames.iterator()); + } + + @Override + public Object[] toArray() { + Object[] arr = new String[size()]; + fillArray(arr); + return arr; + } + + @SuppressWarnings("unchecked") + @Override + public X[] toArray(X[] a) { + if (a == null || a.length < size()) { + X[] arr = (X[]) new Object[size()]; + fillArray(arr); + return arr; + } + fillArray(a); + return a; + } + + private void fillArray(Object[] arr) { + Iterator itr = allNames.iterator(); + for (int i = 0; i < size(); ++i) { + arr[i] = itr.next(); + } + } + + @Override + public boolean remove(Object o) { + return allNames.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) { + if (!contains(o)) { + return false; + } + } + return true; + } + + @Override + public boolean removeAll(Collection c) { + boolean modified = false; + for (Object o : c) { + if (remove(o)) { + modified = true; + } + } + return modified; + } + + @Override + public boolean retainAll(Collection c) { + boolean modified = false; + Iterator it = iterator(); + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + @Override + public void clear() { + allNames.clear(); + } + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/TextHeaders.java b/codec/src/main/java/io/netty/handler/codec/TextHeaders.java deleted file mode 100644 index 394ed9d403..0000000000 --- a/codec/src/main/java/io/netty/handler/codec/TextHeaders.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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: - * - * 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; - -/** - * A typical string multimap used by text protocols such as HTTP for the representation of arbitrary key-value data. One - * thing to note is that it uses {@link CharSequence} as its primary key and value type rather than {@link String}. When - * you invoke the operations that produce {@link String}s such as {@link #get(Object)}, a {@link CharSequence} is - * implicitly converted to a {@link String}. This is particularly useful for speed optimization because this multimap - * can hold a special {@link CharSequence} implementation that a codec can treat specially, such as {@link CharSequence} - * . - */ -public interface TextHeaders extends ConvertibleHeaders { - - /** - * 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 contains(CharSequence name, CharSequence value, boolean ignoreCase); - - @Override - TextHeaders add(CharSequence name, CharSequence value); - - @Override - TextHeaders add(CharSequence name, Iterable values); - - @Override - TextHeaders add(CharSequence name, CharSequence... values); - - @Override - TextHeaders addObject(CharSequence name, Object value); - - @Override - TextHeaders addObject(CharSequence name, Iterable values); - - @Override - TextHeaders addObject(CharSequence name, Object... values); - - @Override - TextHeaders addBoolean(CharSequence name, boolean value); - - @Override - TextHeaders addByte(CharSequence name, byte value); - - @Override - TextHeaders addChar(CharSequence name, char value); - - @Override - TextHeaders addShort(CharSequence name, short value); - - @Override - TextHeaders addInt(CharSequence name, int value); - - @Override - TextHeaders addLong(CharSequence name, long value); - - @Override - TextHeaders addFloat(CharSequence name, float value); - - @Override - TextHeaders addDouble(CharSequence name, double value); - - @Override - TextHeaders addTimeMillis(CharSequence name, long value); - - /** - * See {@link Headers#add(Headers)} - */ - TextHeaders add(TextHeaders headers); - - @Override - TextHeaders set(CharSequence name, CharSequence value); - - @Override - TextHeaders set(CharSequence name, Iterable values); - - @Override - TextHeaders set(CharSequence name, CharSequence... values); - - @Override - TextHeaders setObject(CharSequence name, Object value); - - @Override - TextHeaders setObject(CharSequence name, Iterable values); - - @Override - TextHeaders setObject(CharSequence name, Object... values); - - @Override - TextHeaders setBoolean(CharSequence name, boolean value); - - @Override - TextHeaders setByte(CharSequence name, byte value); - - @Override - TextHeaders setChar(CharSequence name, char value); - - @Override - TextHeaders setShort(CharSequence name, short value); - - @Override - TextHeaders setInt(CharSequence name, int value); - - @Override - TextHeaders setLong(CharSequence name, long value); - - @Override - TextHeaders setFloat(CharSequence name, float value); - - @Override - TextHeaders setDouble(CharSequence name, double value); - - @Override - TextHeaders setTimeMillis(CharSequence name, long value); - - /** - * See {@link Headers#set(Headers)} - */ - TextHeaders set(TextHeaders headers); - - /** - * See {@link Headers#setAll(Headers)} - */ - TextHeaders setAll(TextHeaders headers); - - @Override - TextHeaders clear(); -} diff --git a/codec/src/main/java/io/netty/handler/codec/ValueConverter.java b/codec/src/main/java/io/netty/handler/codec/ValueConverter.java new file mode 100644 index 0000000000..57541c3ed4 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/ValueConverter.java @@ -0,0 +1,57 @@ +/* + * 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; + +/** + * Converts to/from a generic object to the type. + */ +public interface ValueConverter { + T convertObject(Object value); + + T convertBoolean(boolean value); + + boolean convertToBoolean(T value); + + T convertByte(byte value); + + byte convertToByte(T value); + + T convertChar(char value); + + char convertToChar(T value); + + T convertShort(short value); + + short convertToShort(T value); + + T convertInt(int value); + + int convertToInt(T value); + + T convertLong(long value); + + long convertToLong(T value); + + T convertTimeMillis(long value); + + long convertToTimeMillis(T value); + + T convertFloat(float value); + + float convertToFloat(T value); + + T convertDouble(double value); + + double convertToDouble(T value); +} diff --git a/codec/src/test/java/io/netty/handler/codec/DefaultBinaryHeadersTest.java b/codec/src/test/java/io/netty/handler/codec/DefaultHeadersTest.java similarity index 77% rename from codec/src/test/java/io/netty/handler/codec/DefaultBinaryHeadersTest.java rename to codec/src/test/java/io/netty/handler/codec/DefaultHeadersTest.java index 3da629c4ef..88ce63f87b 100644 --- a/codec/src/test/java/io/netty/handler/codec/DefaultBinaryHeadersTest.java +++ b/codec/src/test/java/io/netty/handler/codec/DefaultHeadersTest.java @@ -21,28 +21,30 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import io.netty.util.ByteString; - -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 io.netty.util.CharsetUtil; import org.junit.Test; +import io.netty.util.ByteString; +import io.netty.util.CharsetUtil; + /** - * Tests for {@link DefaultBinaryHeaders}. + * Tests for {@link DefaultHeaders}. */ -public class DefaultBinaryHeadersTest { +public class DefaultHeadersTest { + + private Headers newInstance() { + return new DefaultHeaders(ByteStringValueConverter.INSTANCE); + } @Test public void addShouldIncreaseAndRemoveShouldDecreaseTheSize() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); assertEquals(0, headers.size()); headers.add(bs("name1"), bs("value1"), bs("value2")); assertEquals(2, headers.size()); @@ -62,7 +64,7 @@ public class DefaultBinaryHeadersTest { @Test public void afterClearHeadersShouldBeEmpty() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); headers.add(bs("name1"), bs("value1")); headers.add(bs("name2"), bs("value2")); assertEquals(2, headers.size()); @@ -75,7 +77,7 @@ public class DefaultBinaryHeadersTest { @Test public void removingANameForASecondTimeShouldReturnFalse() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); headers.add(bs("name1"), bs("value1")); headers.add(bs("name2"), bs("value2")); assertTrue(headers.remove(bs("name2"))); @@ -84,7 +86,7 @@ public class DefaultBinaryHeadersTest { @Test public void multipleValuesPerNameShouldBeAllowed() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); headers.add(bs("name"), bs("value1")); headers.add(bs("name"), bs("value2")); headers.add(bs("name"), bs("value3")); @@ -97,7 +99,7 @@ public class DefaultBinaryHeadersTest { @Test public void testContains() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); headers.addBoolean(bs("boolean"), true); assertTrue(headers.containsBoolean(bs("boolean"), true)); assertFalse(headers.containsBoolean(bs("boolean"), false)); @@ -147,7 +149,7 @@ public class DefaultBinaryHeadersTest { @Test public void canMixConvertedAndNormalValues() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); headers.add(bs("name"), bs("value")); headers.addInt(bs("name"), 100); headers.addBoolean(bs("name"), false); @@ -161,7 +163,7 @@ public class DefaultBinaryHeadersTest { @Test public void testGetAndRemove() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); headers.add(bs("name1"), bs("value1")); headers.add(bs("name2"), bs("value2"), bs("value3")); headers.add(bs("name3"), bs("value4"), bs("value5"), bs("value6")); @@ -177,14 +179,14 @@ public class DefaultBinaryHeadersTest { @Test public void whenNameContainsMultipleValuesGetShouldReturnTheFirst() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); headers.add(bs("name1"), bs("value1"), bs("value2")); assertEquals(bs("value1"), headers.get(bs("name1"))); } @Test public void getWithDefaultValueWorks() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); headers.add(bs("name1"), bs("value1")); assertEquals(bs("value1"), headers.get(bs("name1"), bs("defaultvalue"))); @@ -193,7 +195,7 @@ public class DefaultBinaryHeadersTest { @Test public void setShouldOverWritePreviousValue() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); headers.set(bs("name"), bs("value1")); headers.set(bs("name"), bs("value2")); assertEquals(1, headers.size()); @@ -204,19 +206,19 @@ public class DefaultBinaryHeadersTest { @Test public void setAllShouldOverwriteSomeAndLeaveOthersUntouched() { - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); + Headers h1 = newInstance(); 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(); + Headers h2 = newInstance(); h2.add(bs("name1"), bs("value5")); h2.add(bs("name2"), bs("value6")); h2.add(bs("name1"), bs("value7")); - DefaultBinaryHeaders expected = new DefaultBinaryHeaders(); + Headers expected = newInstance(); expected.add(bs("name1"), bs("value5")); expected.add(bs("name2"), bs("value6")); expected.add(bs("name1"), bs("value7")); @@ -229,12 +231,12 @@ public class DefaultBinaryHeadersTest { @Test public void headersWithSameNamesAndValuesShouldBeEquivalent() { - DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + Headers headers1 = newInstance(); headers1.add(bs("name1"), bs("value1")); headers1.add(bs("name2"), bs("value2")); headers1.add(bs("name2"), bs("value3")); - DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + Headers headers2 = newInstance(); headers2.add(bs("name1"), bs("value1")); headers2.add(bs("name2"), bs("value2")); headers2.add(bs("name2"), bs("value3")); @@ -250,8 +252,8 @@ public class DefaultBinaryHeadersTest { @Test public void emptyHeadersShouldBeEqual() { - DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); - DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + Headers headers1 = newInstance(); + Headers headers2 = newInstance(); assertNotSame(headers1, headers2); assertEquals(headers1, headers2); assertEquals(headers1.hashCode(), headers2.hashCode()); @@ -259,28 +261,28 @@ public class DefaultBinaryHeadersTest { @Test public void headersWithSameNamesButDifferentValuesShouldNotBeEquivalent() { - DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + Headers headers1 = newInstance(); headers1.add(bs("name1"), bs("value1")); - DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + Headers headers2 = newInstance(); headers1.add(bs("name1"), bs("value2")); assertNotEquals(headers1, headers2); } @Test public void subsetOfHeadersShouldNotBeEquivalent() { - DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + Headers headers1 = newInstance(); headers1.add(bs("name1"), bs("value1")); headers1.add(bs("name2"), bs("value2")); - DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + Headers headers2 = newInstance(); headers1.add(bs("name1"), bs("value1")); assertNotEquals(headers1, headers2); } @Test public void headersWithDifferentNamesAndValuesShouldNotBeEquivalent() { - DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); + Headers h1 = newInstance(); h1.set(bs("name1"), bs("value1")); - DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); + Headers h2 = newInstance(); h2.set(bs("name2"), bs("value2")); assertNotEquals(h1, h2); assertNotEquals(h2, h1); @@ -290,23 +292,22 @@ public class DefaultBinaryHeadersTest { @Test(expected = NoSuchElementException.class) public void iterateEmptyHeadersShouldThrow() { - Iterator> iterator = new DefaultBinaryHeaders().iterator(); + Iterator> iterator = newInstance().iterator(); assertFalse(iterator.hasNext()); iterator.next(); } @Test public void iteratorShouldReturnAllNameValuePairs() { - DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + Headers headers1 = newInstance(); 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()); - DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + Headers headers2 = newInstance(); for (Entry entry : headers1) { - Object v = entry.getValue(); headers2.add(entry.getKey(), entry.getValue()); } @@ -315,7 +316,7 @@ public class DefaultBinaryHeadersTest { @Test public void iteratorSetValueShouldChangeHeaderValue() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); headers.add(bs("name1"), bs("value1"), bs("value2"), bs("value3")); headers.add(bs("name2"), bs("value4")); assertEquals(4, headers.size()); @@ -342,65 +343,16 @@ public class DefaultBinaryHeadersTest { @Test public void getAllReturnsEmptyListForUnknownName() { - DefaultBinaryHeaders headers = new DefaultBinaryHeaders(); + Headers headers = newInstance(); assertEquals(0, headers.getAll(bs("noname")).size()); } - @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 - } - } - @Test public void setHeadersShouldClearAndOverwrite() { - DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + Headers headers1 = newInstance(); headers1.add(bs("name"), bs("value")); - DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + Headers headers2 = newInstance(); headers2.add(bs("name"), bs("newvalue")); headers2.add(bs("name1"), bs("value1")); @@ -410,15 +362,15 @@ public class DefaultBinaryHeadersTest { @Test public void setAllHeadersShouldOnlyOverwriteHeaders() { - DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders(); + Headers headers1 = newInstance(); headers1.add(bs("name"), bs("value")); headers1.add(bs("name1"), bs("value1")); - DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders(); + Headers headers2 = newInstance(); headers2.add(bs("name"), bs("newvalue")); headers2.add(bs("name2"), bs("value2")); - DefaultBinaryHeaders expected = new DefaultBinaryHeaders(); + Headers expected = newInstance(); expected.add(bs("name"), bs("newvalue")); expected.add(bs("name1"), bs("value1")); expected.add(bs("name2"), bs("value2")); diff --git a/codec/src/test/java/io/netty/handler/codec/DefaultTextHeadersTest.java b/codec/src/test/java/io/netty/handler/codec/DefaultTextHeadersTest.java deleted file mode 100644 index b4c5b332ab..0000000000 --- a/codec/src/test/java/io/netty/handler/codec/DefaultTextHeadersTest.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * 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: - * - * 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; - -import org.junit.Test; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static io.netty.util.internal.StringUtil.COMMA; -import static io.netty.util.internal.StringUtil.DOUBLE_QUOTE; -import static org.junit.Assert.assertEquals; - -public class DefaultTextHeadersTest { - - private static final String HEADER_NAME = "testHeader"; - - @Test - public void addCharSequences() { - final TextHeaders headers = newDefaultTextHeaders(); - headers.add(HEADER_NAME, HeaderValue.THREE.asArray()); - assertDefaultValues(headers, HeaderValue.THREE); - } - - @Test - public void addCharSequencesCsv() { - final TextHeaders headers = newCsvTextHeaders(); - headers.add(HEADER_NAME, HeaderValue.THREE.asArray()); - assertCsvValues(headers, HeaderValue.THREE); - } - - @Test - public void addCharSequencesCsvWithExistingHeader() { - final TextHeaders headers = newCsvTextHeaders(); - headers.add(HEADER_NAME, HeaderValue.THREE.asArray()); - headers.add(HEADER_NAME, HeaderValue.FIVE.subset(4)); - assertCsvValues(headers, HeaderValue.FIVE); - } - - @Test - public void addCharSequencesCsvWithValueContainingComma() { - final TextHeaders headers = newCsvTextHeaders(); - headers.add(HEADER_NAME, HeaderValue.SIX_QUOTED.subset(4)); - assertEquals(HeaderValue.SIX_QUOTED.subsetAsCsvString(4), headers.getAndConvert(HEADER_NAME)); - assertEquals(HeaderValue.SIX_QUOTED.subsetAsCsvString(4), headers.getAllAndConvert(HEADER_NAME).get(0)); - } - - @Test - public void addCharSequencesCsvWithValueContainingCommas() { - final TextHeaders headers = newCsvTextHeaders(); - headers.add(HEADER_NAME, HeaderValue.EIGHT.subset(6)); - assertEquals(HeaderValue.EIGHT.subsetAsCsvString(6), headers.getAndConvert(HEADER_NAME)); - assertEquals(HeaderValue.EIGHT.subsetAsCsvString(6), headers.getAllAndConvert(HEADER_NAME).get(0)); - } - - @Test (expected = NullPointerException.class) - public void addCharSequencesCsvNullValue() { - final TextHeaders headers = newCsvTextHeaders(); - final String value = null; - headers.add(HEADER_NAME, value); - } - - @Test - public void addCharSequencesCsvMultipleTimes() { - final TextHeaders headers = newCsvTextHeaders(); - for (int i = 0; i < 5; ++i) { - headers.add(HEADER_NAME, "value"); - } - assertEquals("value,value,value,value,value", headers.getAndConvert(HEADER_NAME)); - } - - @Test - public void addCharSequenceCsv() { - final TextHeaders headers = newCsvTextHeaders(); - addValues(headers, HeaderValue.ONE, HeaderValue.TWO, HeaderValue.THREE); - assertCsvValues(headers, HeaderValue.THREE); - } - - @Test - public void addCharSequenceCsvSingleValue() { - final TextHeaders headers = newCsvTextHeaders(); - addValues(headers, HeaderValue.ONE); - assertCsvValue(headers, HeaderValue.ONE); - } - - @Test - public void addIterable() { - final TextHeaders headers = newDefaultTextHeaders(); - headers.add(HEADER_NAME, HeaderValue.THREE.asList()); - assertDefaultValues(headers, HeaderValue.THREE); - } - - @Test - public void addIterableCsv() { - final TextHeaders headers = newCsvTextHeaders(); - headers.add(HEADER_NAME, HeaderValue.THREE.asList()); - assertCsvValues(headers, HeaderValue.THREE); - } - - @Test - public void addIterableCsvWithExistingHeader() { - final TextHeaders headers = newCsvTextHeaders(); - headers.add(HEADER_NAME, HeaderValue.THREE.asArray()); - headers.add(HEADER_NAME, HeaderValue.FIVE.subset(4)); - assertCsvValues(headers, HeaderValue.FIVE); - } - - @Test - public void addIterableCsvSingleValue() { - final TextHeaders headers = newCsvTextHeaders(); - headers.add(HEADER_NAME, HeaderValue.ONE.asList()); - assertCsvValue(headers, HeaderValue.ONE); - } - - @Test - public void addIterableCsvEmtpy() { - final TextHeaders headers = newCsvTextHeaders(); - headers.add(HEADER_NAME, Collections.emptyList()); - assertEquals("", headers.getAllAndConvert(HEADER_NAME).get(0)); - } - - @Test - public void addObjectCsv() { - final TextHeaders headers = newCsvTextHeaders(); - addObjectValues(headers, HeaderValue.ONE, HeaderValue.TWO, HeaderValue.THREE); - assertCsvValues(headers, HeaderValue.THREE); - } - - @Test - public void addObjects() { - final TextHeaders headers = newDefaultTextHeaders(); - headers.addObject(HEADER_NAME, HeaderValue.THREE.asArray()); - assertDefaultValues(headers, HeaderValue.THREE); - } - - @Test - public void addObjectsCsv() { - final TextHeaders headers = newCsvTextHeaders(); - headers.addObject(HEADER_NAME, HeaderValue.THREE.asArray()); - assertCsvValues(headers, HeaderValue.THREE); - } - - @Test - public void addObjectsIterableCsv() { - final TextHeaders headers = newCsvTextHeaders(); - headers.addObject(HEADER_NAME, HeaderValue.THREE.asList()); - assertCsvValues(headers, HeaderValue.THREE); - } - - @Test - public void addObjectsCsvWithExistingHeader() { - final TextHeaders headers = newCsvTextHeaders(); - headers.addObject(HEADER_NAME, HeaderValue.THREE.asArray()); - headers.addObject(HEADER_NAME, HeaderValue.FIVE.subset(4)); - assertCsvValues(headers, HeaderValue.FIVE); - } - - @Test - public void setCharSequences() { - final TextHeaders headers = newDefaultTextHeaders(); - headers.set(HEADER_NAME, HeaderValue.THREE.asArray()); - assertDefaultValues(headers, HeaderValue.THREE); - } - - @Test - public void setCharSequenceCsv() { - final TextHeaders headers = newCsvTextHeaders(); - headers.set(HEADER_NAME, HeaderValue.THREE.asArray()); - assertCsvValues(headers, HeaderValue.THREE); - } - - @Test - public void setIterable() { - final TextHeaders headers = newDefaultTextHeaders(); - headers.set(HEADER_NAME, HeaderValue.THREE.asList()); - assertDefaultValues(headers, HeaderValue.THREE); - } - - @Test - public void setIterableCsv() { - final TextHeaders headers = newCsvTextHeaders(); - headers.set(HEADER_NAME, HeaderValue.THREE.asList()); - assertCsvValues(headers, HeaderValue.THREE); - } - - @Test - public void setObjectObjects() { - final TextHeaders headers = newDefaultTextHeaders(); - headers.setObject(HEADER_NAME, HeaderValue.THREE.asArray()); - assertDefaultValues(headers, HeaderValue.THREE); - } - - @Test - public void setObjectObjectsCsv() { - final TextHeaders headers = newCsvTextHeaders(); - headers.setObject(HEADER_NAME, HeaderValue.THREE.asArray()); - assertCsvValues(headers, HeaderValue.THREE); - } - - @Test - public void setObjectIterable() { - final TextHeaders headers = newDefaultTextHeaders(); - headers.setObject(HEADER_NAME, HeaderValue.THREE.asList()); - assertDefaultValues(headers, HeaderValue.THREE); - } - - @Test - public void setObjectIterableCsv() { - final TextHeaders headers = newCsvTextHeaders(); - headers.setObject(HEADER_NAME, HeaderValue.THREE.asList()); - assertCsvValues(headers, HeaderValue.THREE); - } - - private static void assertDefaultValues(final TextHeaders headers, final HeaderValue headerValue) { - assertEquals(headerValue.asArray()[0], headers.get(HEADER_NAME)); - assertEquals(headerValue.asList(), headers.getAll(HEADER_NAME)); - } - - private static void assertCsvValues(final TextHeaders headers, final HeaderValue headerValue) { - assertEquals(headerValue.asCsv(), headers.getAndConvert(HEADER_NAME)); - assertEquals(headerValue.asCsv(), headers.getAllAndConvert(HEADER_NAME).get(0)); - } - - private static void assertCsvValue(final TextHeaders headers, final HeaderValue headerValue) { - assertEquals(headerValue.toString(), headers.getAndConvert(HEADER_NAME)); - assertEquals(headerValue.toString(), headers.getAllAndConvert(HEADER_NAME).get(0)); - } - - private static TextHeaders newDefaultTextHeaders() { - return new DefaultTextHeaders(false); - } - - private static TextHeaders newCsvTextHeaders() { - return new DefaultTextHeaders(true); - } - - private static void addValues(final TextHeaders headers, HeaderValue... headerValues) { - for (HeaderValue v: headerValues) { - headers.add(HEADER_NAME, v.toString()); - } - } - - private static void addObjectValues(final TextHeaders headers, HeaderValue... headerValues) { - for (HeaderValue v: headerValues) { - headers.addObject(HEADER_NAME, v.toString()); - } - } - - private enum HeaderValue { - UNKNOWN("unknown", 0), - ONE("one", 1), - TWO("two", 2), - THREE("three", 3), - FOUR("four", 4), - FIVE("five", 5), - SIX_QUOTED("six,", 6), - SEVEN_QUOTED("seven; , GMT", 7), - EIGHT("eight", 8); - - private final int nr; - private final String value; - private String[] array; - private static final String DOUBLE_QUOTE_STRING = String.valueOf(DOUBLE_QUOTE); - - HeaderValue(final String value, final int nr) { - this.nr = nr; - this.value = value; - } - - @Override - public String toString() { - return value; - } - - public String[] asArray() { - if (array == null) { - final String[] arr = new String[nr]; - for (int i = 1, y = 0; i <= nr; i++, y++) { - arr[y] = of(i).toString(); - } - array = arr; - } - return array; - } - - public String[] subset(final int from) { - final int size = from - 1; - final String[] arr = new String[nr - size]; - System.arraycopy(asArray(), size, arr, 0, arr.length); - return arr; - } - - public String subsetAsCsvString(final int from) { - final String[] subset = subset(from); - return asCsv(subset); - } - - public List asList() { - return Arrays.asList(asArray()); - } - - public String asCsv(final String[] arr) { - final StringBuilder sb = new StringBuilder(); - int end = arr.length - 1; - for (int i = 0; i < end; i++) { - final String value = arr[i]; - quoted(sb, value).append(COMMA); - } - quoted(sb, arr[end]); - return sb.toString(); - } - - public String asCsv() { - return asCsv(asArray()); - } - - private static StringBuilder quoted(final StringBuilder sb, final String value) { - if (value.contains(String.valueOf(COMMA)) && !value.contains(DOUBLE_QUOTE_STRING)) { - return sb.append(DOUBLE_QUOTE).append(value).append(DOUBLE_QUOTE); - } - return sb.append(value); - } - - public static String quoted(final String value) { - return quoted(new StringBuilder(), value).toString(); - } - - private static final Map MAP; - - static { - final Map map = new HashMap(); - for (HeaderValue v : values()) { - final int nr = v.nr; - map.put(Integer.valueOf(nr), v); - } - MAP = map; - } - - public static HeaderValue of(final int nr) { - final HeaderValue v = MAP.get(Integer.valueOf(nr)); - return v == null ? UNKNOWN : v; - } - } -} diff --git a/common/src/main/java/io/netty/util/AsciiString.java b/common/src/main/java/io/netty/util/AsciiString.java index 097fe33d6c..ea6aa6365b 100644 --- a/common/src/main/java/io/netty/util/AsciiString.java +++ b/common/src/main/java/io/netty/util/AsciiString.java @@ -15,8 +15,6 @@ */ package io.netty.util; -import static io.netty.util.internal.ObjectUtil.checkNotNull; -import static io.netty.util.internal.StringUtil.UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET; import io.netty.util.ByteProcessor.IndexOfProcessor; import io.netty.util.internal.EmptyArrays; import io.netty.util.internal.PlatformDependent; @@ -24,11 +22,12 @@ import io.netty.util.internal.PlatformDependent; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import static io.netty.util.internal.ObjectUtil.checkNotNull; + /** * A string which has been encoded into a character encoding whose character always takes a single byte, similarly to * ASCII. It internally keeps its content in a byte array unlike {@link String}, which uses a character array, for @@ -40,124 +39,28 @@ public final class AsciiString extends ByteString implements CharSequence, Compa private static final char MAX_CHAR_VALUE = 255; public static final AsciiString EMPTY_STRING = new AsciiString(""); - public static final Comparator CASE_INSENSITIVE_ORDER = new Comparator() { + public static final HashingStrategy CASE_INSENSITIVE_HASHER = + new HashingStrategy() { @Override - public int compare(AsciiString o1, AsciiString o2) { - return CHARSEQUENCE_CASE_INSENSITIVE_ORDER.compare(o1, o2); + public int hashCode(CharSequence o) { + return AsciiString.caseInsensitiveHashCode(o); + } + + @Override + public boolean equals(CharSequence a, CharSequence b) { + return AsciiString.contentEqualsIgnoreCase(a, b); } }; - public static final Comparator CASE_SENSITIVE_ORDER = new Comparator() { + public static final HashingStrategy CASE_SENSITIVE_HASHER = + new HashingStrategy() { @Override - public int compare(AsciiString o1, AsciiString o2) { - return CHARSEQUENCE_CASE_SENSITIVE_ORDER.compare(o1, o2); + public int hashCode(CharSequence o) { + return AsciiString.hashCode(o); } - }; - - public static final Comparator CHARSEQUENCE_CASE_INSENSITIVE_ORDER = new Comparator() { @Override - public int compare(CharSequence o1, CharSequence o2) { - int len1 = o1.length(); - int delta = len1 - o2.length(); - if (delta != 0) { - return delta; - } - 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 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 = 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 0; - } - }; - - public static final Comparator CHARSEQUENCE_CASE_SENSITIVE_ORDER = new Comparator() { - @Override - public int compare(CharSequence o1, CharSequence o2) { - if (o1 == o2) { - return 0; - } - - 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(); - for (int i = a1.arrayOffset(), j = a2.arrayOffset(); i < a1Len; i++, j++) { - byte v1 = a1.value[i]; - byte v2 = a2.value[j]; - result = v1 - v2; - if (result != 0) { - return result; - } - } - } else if (a1 != null) { - for (int i = a1.arrayOffset(), j = 0; j < minLength; i++, j++) { - int c1 = a1.value[i]; - int c2 = 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 = o1.charAt(i); - int c2 = a2.value[j]; - result = c1 - c2; - if (result != 0) { - return result; - } - } - } else { - for (int i = 0; i < minLength; i++) { - int c1 = o1.charAt(i); - int c2 = o2.charAt(i); - result = c1 - c2; - if (result != 0) { - return result; - } - } - } - - return length1 - length2; + public boolean equals(CharSequence a, CharSequence b) { + return AsciiString.contentEquals(a, b); } }; @@ -171,94 +74,8 @@ public final class AsciiString extends ByteString implements CharSequence, Compa } }; - /** - * Returns the case-insensitive hash code of the specified string. Note that this method uses the same hashing - * algorithm with {@link #hashCode()} so that you can put both {@link AsciiString}s and arbitrary - * {@link CharSequence}s into the same {@link TextHeaders}. - */ - public static int caseInsensitiveHashCode(CharSequence value) { - if (value instanceof AsciiString) { - try { - ByteProcessor processor = new ByteProcessor() { - private int hash; - @Override - public boolean process(byte value) throws Exception { - hash = hash * HASH_CODE_PRIME ^ toLowerCase(value) & HASH_CODE_PRIME; - return true; - } - - @Override - public int hashCode() { - return hash; - } - }; - ((AsciiString) value).forEachByte(processor); - return processor.hashCode(); - } catch (Exception e) { - PlatformDependent.throwException(e); - } - } - - int hash = 0; - final int end = value.length(); - for (int i = 0; i < end; i++) { - hash = hash * HASH_CODE_PRIME ^ toLowerCase(value.charAt(i)) & HASH_CODE_PRIME; - } - return hash; - } - - /** - * Returns {@code true} if both {@link CharSequence}'s are equals when ignore the case. This only supports 8-bit - * ASCII. - */ - public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) { - if (a == b) { - return true; - } - - if (a instanceof AsciiString) { - AsciiString aa = (AsciiString) a; - return aa.equalsIgnoreCase(b); - } - - if (b instanceof AsciiString) { - AsciiString ab = (AsciiString) b; - return ab.equalsIgnoreCase(a); - } - - if (a == null || b == null) { - return false; - } - - return a.toString().equalsIgnoreCase(b.toString()); - } - - /** - * Returns {@code true} if both {@link CharSequence}'s are equals. This only supports 8-bit ASCII. - */ - public static boolean equals(CharSequence a, CharSequence b) { - if (a == b) { - return true; - } - - if (a instanceof AsciiString) { - AsciiString aa = (AsciiString) a; - return aa.equals(b); - } - - if (b instanceof AsciiString) { - AsciiString ab = (AsciiString) b; - return ab.equals(a); - } - - if (a == null || b == null) { - return false; - } - - return a.equals(b); - } - private String string; + private int caseInsensitiveHash; /** * Returns an {@link AsciiString} containing the given character sequence. If the given string is already a @@ -326,47 +143,16 @@ public final class AsciiString extends ByteString implements CharSequence, Compa @Override public char charAt(int index) { - return b2c(byteAt(index)); + return (char) (byteAt(index) & 0xFF); } @Override public void arrayChanged() { string = null; + caseInsensitiveHash = 0; super.arrayChanged(); } - private static byte c2b(char c) { - if (c > MAX_CHAR_VALUE) { - return '?'; - } - return (byte) c; - } - - private static char b2c(byte b) { - return (char) (b & 0xFF); - } - - private static byte toLowerCase(byte b) { - if ('A' <= b && b <= 'Z') { - return (byte) (b + UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET); - } - return b; - } - - private static char toLowerCase(char c) { - if ('A' <= c && c <= 'Z') { - return (char) (c + UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET); - } - return c; - } - - private static byte toUpperCase(byte b) { - if ('a' <= b && b <= 'z') { - return (byte) (b - UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET); - } - return b; - } - @Override public String toString(Charset charset, int start, int end) { if (start == 0 && end == length()) { @@ -403,7 +189,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa int length2 = string.length(); int minLength = Math.min(length1, length2); for (int i = 0, j = arrayOffset(); i < minLength; i++, j++) { - result = b2c(value[j]) - string.charAt(i); + result = (char) (value[j] & 0xFF) - string.charAt(i); if (result != 0) { return result; } @@ -412,24 +198,6 @@ public final class AsciiString extends ByteString implements CharSequence, Compa return length1 - length2; } - /** - * Compares the specified string to this string using the ASCII values of the characters, ignoring case differences. - * Returns 0 if the strings contain the same characters in the same order. Returns a negative integer if the first - * non-equal character in this string has an ASCII value which is less than the ASCII value of the character at the - * same position in the specified string, or if this string is a prefix of the specified string. Returns a positive - * integer if the first non-equal character in this string has an ASCII value which is greater than the ASCII value - * of the character at the same position in the specified string, or if the specified string is a prefix of this - * string. - * - * @param string the string to compare. - * @return 0 if the strings are equal, a negative integer if this string is before the specified string, or a - * positive integer if this string is after the specified string. - * @throws NullPointerException if {@code string} is {@code null}. - */ - public int compareToIgnoreCase(CharSequence string) { - return CHARSEQUENCE_CASE_INSENSITIVE_ORDER.compare(this, string); - } - /** * Concatenates this string and the specified string. * @@ -487,24 +255,26 @@ public final class AsciiString extends ByteString implements CharSequence, Compa * @param string the string to compare. * @return {@code true} if the specified string is equal to this string, {@code false} otherwise. */ - public boolean equalsIgnoreCase(CharSequence string) { - if (string == this) { + public boolean contentEqualsIgnoreCase(CharSequence string) { + if (string == null || string.length() != length()) { + return false; + } + + if (string.getClass() == AsciiString.class) { + AsciiString rhs = (AsciiString) string; + for (int i = arrayOffset(), j = rhs.arrayOffset(); i < length(); ++i, ++j) { + byte c1 = value[i]; + byte c2 = value[j]; + if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) { + return false; + } + } return true; } - if (string == null) { - return false; - } - - final int thisLen = value.length; - final int thatLen = string.length(); - if (thisLen != thatLen) { - return false; - } - - for (int i = 0, j = arrayOffset(); i < thisLen; i++, j++) { - char c1 = b2c(value[j]); - char c2 = string.charAt(i); + for (int i = arrayOffset(), j = 0; i < length(); ++i, ++j) { + char c1 = (char) (value[i] & 0xFF); + char c2 = string.charAt(j); if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) { return false; } @@ -539,7 +309,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa final char[] buffer = new char[length]; for (int i = 0, j = start + arrayOffset(); i < length; i++, j++) { - buffer[i] = b2c(value[j]); + buffer[i] = (char) (value[j] & 0xFF); } return buffer; } @@ -564,7 +334,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa final int dstEnd = dstIdx + length; for (int i = dstIdx, j = srcIdx + arrayOffset(); i < dstEnd; i++, j++) { - dst[i] = b2c(value[j]); + dst[i] = (char) (value[j] & 0xFF); } } @@ -633,7 +403,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa return -1; // handles subCount > count || start >= count } int o1 = i, o2 = 0; - while (++o2 < subCount && b2c(value[++o1 + arrayOffset()]) == subString.charAt(o2)) { + while (++o2 < subCount && (char) (value[++o1 + arrayOffset()] & 0xFF) == subString.charAt(o2)) { // Intentionally empty } if (o2 == subCount) { @@ -698,7 +468,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa return -1; } int o1 = i, o2 = 0; - while (++o2 < subCount && b2c(value[++o1 + arrayOffset()]) == subString.charAt(o2)) { + while (++o2 < subCount && (char) (value[++o1 + arrayOffset()] & 0xFF) == subString.charAt(o2)) { // Intentionally empty } if (o2 == subCount) { @@ -743,7 +513,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa final int thatEnd = start + length; for (int i = start, j = thisStart + arrayOffset(); i < thatEnd; i++, j++) { - if (b2c(value[j]) != string.charAt(i)) { + if ((char) (value[j] & 0xFF) != string.charAt(i)) { return false; } } @@ -782,7 +552,7 @@ public final class AsciiString extends ByteString implements CharSequence, Compa thisStart += arrayOffset(); final int thisEnd = thisStart + length; while (thisStart < thisEnd) { - char c1 = b2c(value[thisStart++]); + char c1 = (char) (value[thisStart++] & 0xFF); char c2 = string.charAt(start++); if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) { return false; @@ -936,23 +706,26 @@ public final class AsciiString extends ByteString implements CharSequence, Compa /** * Compares a {@code CharSequence} to this {@code String} to determine if their contents are equal. * - * @param cs the character sequence to compare to. + * @param a the character sequence to compare to. * @return {@code true} if equal, otherwise {@code false} */ - public boolean contentEquals(CharSequence cs) { - if (cs == null) { - throw new NullPointerException(); + public boolean contentEquals(CharSequence a) { + if (this == a) { + return true; + } + if (a instanceof AsciiString) { + return equals(a); } - int length1 = length(); - int length2 = cs.length(); - if (length1 != length2) { + if (a.length() != length()) { return false; - } else if (length1 == 0) { - return true; // since both are empty strings } - - return regionMatches(0, cs, 0, length2); + for (int i = arrayOffset(), j = 0; j < a.length(); ++i, ++j) { + if ((char) (value[i] & 0xFF) != a.charAt(j)) { + return false; + } + } + return true; } /** @@ -982,12 +755,11 @@ public final class AsciiString extends ByteString implements CharSequence, Compa return toAsciiStringArray(Pattern.compile(expr).split(this, max)); } - private static AsciiString[] toAsciiStringArray(String[] jdkResult) { - AsciiString[] res = new AsciiString[jdkResult.length]; - for (int i = 0; i < jdkResult.length; i++) { - res[i] = new AsciiString(jdkResult[i]); + private static byte c2b(char c) { + if (c > MAX_CHAR_VALUE) { + return '?'; } - return res; + return (byte) c; } /** @@ -1030,6 +802,130 @@ public final class AsciiString extends ByteString implements CharSequence, Compa return res.toArray(new AsciiString[res.size()]); } + /** + * Generate a hash code that will be consistent regardless of ASCII character casing. + *

    + * NOTE: This must be compatible with {@link #caseInsensitiveHashCode(CharSequence)}. + */ + public int hashCodeCaseInsensitive() { + int h = caseInsensitiveHash; + if (h == 0) { + final int end = arrayOffset() + length(); + for (int i = arrayOffset(); i < end; ++i) { + h = h * HASH_CODE_PRIME + toLowerCase((char) (value[i] & 0xFF)); + } + + caseInsensitiveHash = h; + } + return caseInsensitiveHash; + } + + /** + * Returns the case-insensitive hash code of the specified string. Note that this method uses the same hashing + * algorithm with {@link #hashCode()} so that you can put both {@link AsciiString}s and arbitrary + * {@link CharSequence}s into the same {@link TextHeaders}. + */ + public static int caseInsensitiveHashCode(CharSequence value) { + if (value.getClass() == AsciiString.class) { + return ((AsciiString) value).hashCodeCaseInsensitive(); + } + + int hash = 0; + for (int i = 0; i < value.length(); ++i) { + hash = hash * HASH_CODE_PRIME + toLowerCase(value.charAt(i)); + } + return hash; + } + + /** + * A case-sensitive version of {@link caseInsensitiveHashCode(CharSequence)}. + * @param value + * @return + */ + public static int hashCode(CharSequence value) { + if (value.getClass() == AsciiString.class) { + return ((AsciiString) value).hashCode(); + } + + int hash = 0; + for (int i = 0; i < value.length(); ++i) { + hash = hash * HASH_CODE_PRIME + value.charAt(i); + } + return hash; + } + + /** + * Returns {@code true} if both {@link CharSequence}'s are equals when ignore the case. This only supports 8-bit + * ASCII. + */ + public static boolean contentEqualsIgnoreCase(CharSequence a, CharSequence b) { + if (a == b) { + return true; + } + + if (a == null || b == null) { + return false; + } + + if (a.getClass() == AsciiString.class) { + return ((AsciiString) a).contentEqualsIgnoreCase(b); + } + if (b.getClass() == AsciiString.class) { + return ((AsciiString) b).contentEqualsIgnoreCase(a); + } + + if (a.length() != b.length()) { + return false; + } + for (int i = 0, j = 0; i < a.length(); ++i, ++j) { + char c1 = a.charAt(i); + char c2 = b.charAt(j); + if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} if the content of both {@link CharSequence}'s are equals. This only supports 8-bit ASCII. + */ + public static boolean contentEquals(CharSequence a, CharSequence b) { + if (a == b) { + return true; + } + + if (a == null || b == null) { + return false; + } + + if (a.getClass() == AsciiString.class) { + return ((AsciiString) a).contentEquals(b); + } + + if (b.getClass() == AsciiString.class) { + return ((AsciiString) b).contentEquals(a); + } + + if (a.length() != b.length()) { + return false; + } + for (int i = 0; i < a.length(); ++i) { + if (a.charAt(i) != b.charAt(i)) { + return false; + } + } + return true; + } + + private static AsciiString[] toAsciiStringArray(String[] jdkResult) { + AsciiString[] res = new AsciiString[jdkResult.length]; + for (int i = 0; i < jdkResult.length; i++) { + res[i] = new AsciiString(jdkResult[i]); + } + return res; + } + /** * Determines if this {@code String} contains the sequence of characters in the {@code CharSequence} passed. * @@ -1042,4 +938,25 @@ public final class AsciiString extends ByteString implements CharSequence, Compa } return indexOf(cs) >= 0; } + + private static byte toLowerCase(byte b) { + if ('A' <= b && b <= 'Z') { + return (byte) (b + 32); + } + return b; + } + + private static char toLowerCase(char c) { + if ('A' <= c && c <= 'Z') { + return (char) (c + 32); + } + return c; + } + + private static byte toUpperCase(byte b) { + if ('a' <= b && b <= 'z') { + return (byte) (b - 32); + } + return b; + } } diff --git a/common/src/main/java/io/netty/util/ByteString.java b/common/src/main/java/io/netty/util/ByteString.java index f1e313eecb..394ec0246b 100644 --- a/common/src/main/java/io/netty/util/ByteString.java +++ b/common/src/main/java/io/netty/util/ByteString.java @@ -16,15 +16,14 @@ package io.netty.util; import static io.netty.util.internal.ObjectUtil.checkNotNull; -import io.netty.util.internal.PlatformDependent; -import io.netty.util.internal.StringUtil; - import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.Arrays; -import java.util.Comparator; + +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.StringUtil; /** * The primary use case for this class is to function as an immutable array of bytes. For performance reasons this @@ -33,31 +32,6 @@ import java.util.Comparator; * this object is immutable. */ public class ByteString { - /** - * A byte wise comparator between two {@link ByteString} objects. - */ - public static final Comparator DEFAULT_COMPARATOR = new Comparator() { - @Override - public int compare(ByteString o1, ByteString o2) { - if (o1 == o2) { - return 0; - } - - int result; - int length1 = o1.length(); - int length2 = o2.length(); - int minLength = Math.min(length1, length2); - for (int i = o1.offset, j = o2.offset; i < minLength; i++, j++) { - result = o1.value[i] - o2.value[j]; - if (result != 0) { - return result; - } - } - - return length1 - length2; - } - }; - /** * Allows sub classes to take advantage of {@link ByteString} operations which need to generate new * ByteString objects. @@ -77,7 +51,7 @@ public class ByteString { }; public static final ByteString EMPTY_STRING = new ByteString(0); - protected static final int HASH_CODE_PRIME = 31;; + protected static final int HASH_CODE_PRIME = 31; /** * If this value is modified outside the constructor then call {@link #arrayChanged()}. @@ -127,16 +101,15 @@ public class ByteString { * will be shared. */ public ByteString(byte[] value, int start, int length, boolean copy) { - if (start < 0 || start > checkNotNull(value, "value").length - length) { - throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length - + ") <= " + "value.length(" + value.length + ')'); - } - if (copy) { this.value = Arrays.copyOfRange(value, start, start + length); - offset = 0; + this.offset = 0; this.length = length; } else { + if (start < 0 || start > value.length - length) { + throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + + length + ") <= " + "value.length(" + value.length + ')'); + } this.value = value; this.offset = start; this.length = length; @@ -268,6 +241,14 @@ public class ByteString { this.length = this.value.length; } + /** + * Create a new {@link ByteString} assuming ASCII encoding of {@code value}. + * @param value value to translate assuming ASCII encoding. + */ + public static final ByteString fromAscii(CharSequence value) { + return new ByteString(value, CharsetUtil.US_ASCII); + } + /** * Iterates over the readable bytes of this buffer with the specified {@code processor} in ascending order. * @@ -275,7 +256,7 @@ public class ByteString { * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}. */ public final int forEachByte(ByteProcessor visitor) throws Exception { - return forEachByte(0, length(), visitor); + return forEachByte0(0, length(), visitor); } /** @@ -291,6 +272,10 @@ public class ByteString { + ") <= " + "length(" + length() + ')'); } + return forEachByte0(index, length, visitor); + } + + private int forEachByte0(int index, int length, ByteProcessor visitor) throws Exception { final int len = offset + length; for (int i = offset + index; i < len; ++i) { if (!visitor.process(value[i])) { @@ -307,7 +292,7 @@ public class ByteString { * The last-visited index If the {@link ByteProcessor#process(byte)} returned {@code false}. */ public final int forEachByteDesc(ByteProcessor visitor) throws Exception { - return forEachByteDesc(0, length(), visitor); + return forEachByteDesc0(0, length(), visitor); } /** @@ -323,6 +308,10 @@ public class ByteString { + ") <= " + "length(" + length() + ')'); } + return forEachByteDesc0(index, length, visitor); + } + + private int forEachByteDesc0(int index, int length, ByteProcessor visitor) throws Exception { final int end = offset + index; for (int i = offset + index + length - 1; i >= end; --i) { if (!visitor.process(value[i])) { @@ -423,7 +412,7 @@ public class ByteString { if (h == 0) { final int end = offset + length; for (int i = offset; i < end; ++i) { - h = h * HASH_CODE_PRIME ^ value[i] & HASH_CODE_PRIME; + h = h * HASH_CODE_PRIME + value[i]; } hash = h; diff --git a/common/src/main/java/io/netty/util/HashingStrategy.java b/common/src/main/java/io/netty/util/HashingStrategy.java new file mode 100644 index 0000000000..35e8b43bb7 --- /dev/null +++ b/common/src/main/java/io/netty/util/HashingStrategy.java @@ -0,0 +1,75 @@ +/* + * 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.util; + +import io.netty.util.internal.ObjectUtil; + +/** + * Abstraction for hash code generation and equality comparison. + */ +public interface HashingStrategy { + /** + * Generate a hash code for {@code obj}. + *

    + * This method must obey the same relationship that {@link java.lang.Object#hashCode()} has with + * {@link java.lang.Object#equals(Object)}: + *

      + *
    • Calling this method multiple times with the same {@code obj} should return the same result
    • + *
    • If {@link #equals(Object, Object)} with parameters {@code a} and {@code b} returns {@code true} + * then the return value for this method for parameters {@code a} and {@code b} must return the same result
    • + *
    • If {@link #equals(Object, Object)} with parameters {@code a} and {@code b} returns {@code false} + * then the return value for this method for parameters {@code a} and {@code b} does not have to + * return different results results. However this property is desirable.
    • + *
    • if {@code obj} is {@code null} then this method return {@code 0}
    • + *
    + */ + int hashCode(T obj); + + /** + * Returns {@code true} if the arguments are equal to each other and {@code false} otherwise. + * This method has the following restrictions: + *
      + *
    • reflexive - {@code equals(a, a)} should return true
    • + *
    • symmetric - {@code equals(a, b)} returns {@code true} iff {@code equals(b, a)} returns + * {@code true}
    • + *
    • transitive - if {@code equals(a, b)} returns {@code true} and {@code equals(a, c)} returns + * {@code true} then {@code equals(b, c)} should also return {@code true}
    • + *
    • consistent - {@code equals(a, b)} should return the same result when called multiple times + * assuming {@code a} and {@code b} remain unchanged relative to the comparison criteria
    • + *
    • if {@code a} and {@code b} are both {@code null} then this method returns {@code true}
    • + *
    • if {@code a} is {@code null} and {@code b} is non-{@code null}, or {@code a} is non-{@code null} and + * {@code b} is {@code null} then this method returns {@code false}
    • + *
    + */ + boolean equals(T a, T b); + + /** + * A {@link HashingStrategy} which delegates to java's {@link ObjectUtil#hashCode(Object)} + * and {@link ObjectUtil#equals(Object, Object)}. + */ + @SuppressWarnings("rawtypes") + HashingStrategy JAVA_HASHER = new HashingStrategy() { + @Override + public int hashCode(Object obj) { + return obj != null ? obj.hashCode() : 0; + } + + @Override + public boolean equals(Object a, Object b) { + return (a == b) || (a != null && a.equals(b)); + } + }; +} diff --git a/common/src/main/java/io/netty/util/internal/StringUtil.java b/common/src/main/java/io/netty/util/internal/StringUtil.java index a9bc1d6ac5..b73d0046ac 100644 --- a/common/src/main/java/io/netty/util/internal/StringUtil.java +++ b/common/src/main/java/io/netty/util/internal/StringUtil.java @@ -37,7 +37,6 @@ public final class StringUtil { public static final char CARRIAGE_RETURN = '\r'; public static final char TAB = '\t'; - public static final byte UPPER_CASE_TO_LOWER_CASE_ASCII_OFFSET = 'a' - 'A'; private static final String[] BYTE2HEX_PAD = new String[256]; private static final String[] BYTE2HEX_NOPAD = new String[256]; diff --git a/common/src/test/java/io/netty/util/AsciiStringTest.java b/common/src/test/java/io/netty/util/AsciiStringTest.java index 7b12b06fd6..21b4fe587d 100644 --- a/common/src/test/java/io/netty/util/AsciiStringTest.java +++ b/common/src/test/java/io/netty/util/AsciiStringTest.java @@ -15,14 +15,18 @@ */ package io.netty.util; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertArrayEquals; - -import java.nio.charset.Charset; - import org.junit.Assert; import org.junit.Test; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.util.Random; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static io.netty.util.AsciiString.caseInsensitiveHashCode; + /** * Test for the {@link AsciiString} class */ @@ -87,6 +91,8 @@ public class AsciiStringTest { final int end = init.length; AsciiString sub1 = ascii.subSequence(start, end, false); AsciiString sub2 = ascii.subSequence(start, end, true); + assertEquals(sub1.hashCode(), sub2.hashCode()); + assertEquals(sub1.hashCodeCaseInsensitive(), sub2.hashCode()); assertEquals(sub1, sub2); for (int i = start; i < end; ++i) { assertEquals(init[i], sub1.byteAt(i - start)); @@ -94,9 +100,77 @@ public class AsciiStringTest { } @Test - public void caseInsensativeHasher() { - String s1 = new String("TransfeR-EncodinG"); - AsciiString s2 = new AsciiString("transfer-encoding"); - assertEquals(AsciiString.caseInsensitiveHashCode(s1), AsciiString.caseInsensitiveHashCode(s2)); + public void testCaseSensitivity() { + Random r = new Random(); + int i = 0; + for (; i < 32; i++) { + doCaseSensitivity(r, i); + } + final int min = i; + final int max = 4000; + final int len = r.nextInt((max - min) + 1) + min; + doCaseSensitivity(r, len); + } + + private static void doCaseSensitivity(Random r, int len) { + // Build an upper case and lower case string + final int upperA = 'A'; + final int upperZ = 'Z'; + final int upperToLower = (int) 'a' - upperA; + byte[] lowerCaseBytes = new byte[len]; + StringBuilder upperCaseBuilder = new StringBuilder(len); + for (int i = 0; i < len; ++i) { + char upper = (char) (r.nextInt((upperZ - upperA) + 1) + upperA); + upperCaseBuilder.append(upper); + lowerCaseBytes[i] = (byte) (upper + upperToLower); + } + String upperCaseString = upperCaseBuilder.toString(); + String lowerCaseString = new String(lowerCaseBytes); + AsciiString lowerCaseAscii = new AsciiString(lowerCaseBytes, false); + AsciiString upperCaseAscii = new AsciiString(upperCaseString); + ByteString lowerCaseByteString = new ByteString(lowerCaseBytes); + ByteString upperCaseByteString = new ByteString(upperCaseString, CharsetUtil.US_ASCII); + final String errorString = "len: " + len; + // Test upper case hash codes are equal + final int upperCaseExpected = upperCaseAscii.hashCode(); + assertEquals(errorString, upperCaseExpected, AsciiString.hashCode(upperCaseBuilder)); + assertEquals(errorString, upperCaseExpected, AsciiString.hashCode(upperCaseString)); + assertEquals(errorString, upperCaseExpected, upperCaseAscii.hashCode()); + assertEquals(errorString, upperCaseExpected, upperCaseByteString.hashCode()); + + // Test lower case hash codes are equal + final int lowerCaseExpected = lowerCaseAscii.hashCode(); + assertEquals(errorString, lowerCaseExpected, AsciiString.hashCode(lowerCaseAscii)); + assertEquals(errorString, lowerCaseExpected, AsciiString.hashCode(lowerCaseString)); + assertEquals(errorString, lowerCaseExpected, lowerCaseAscii.hashCode()); + assertEquals(errorString, lowerCaseExpected, lowerCaseByteString.hashCode()); + + // Test case insensitive hash codes are equal + final int expectedCaseInsensative = lowerCaseAscii.hashCodeCaseInsensitive(); + assertEquals(errorString, expectedCaseInsensative, caseInsensitiveHashCode(upperCaseBuilder)); + assertEquals(errorString, expectedCaseInsensative, caseInsensitiveHashCode(upperCaseString)); + assertEquals(errorString, expectedCaseInsensative, caseInsensitiveHashCode(lowerCaseString)); + assertEquals(errorString, expectedCaseInsensative, caseInsensitiveHashCode(lowerCaseAscii)); + assertEquals(errorString, expectedCaseInsensative, caseInsensitiveHashCode(upperCaseAscii)); + assertEquals(errorString, expectedCaseInsensative, lowerCaseAscii.hashCodeCaseInsensitive()); + assertEquals(errorString, expectedCaseInsensative, upperCaseAscii.hashCodeCaseInsensitive()); + + // Test that opposite cases are not equal + if (len != 0) { + assertNotEquals(errorString, lowerCaseAscii.hashCode(), AsciiString.hashCode(upperCaseString)); + assertNotEquals(errorString, upperCaseAscii.hashCode(), AsciiString.hashCode(lowerCaseString)); + } + } + + @Test + public void caseInsensitiveHasherCharBuffer() { + String s1 = new String("TRANSFER-ENCODING"); + char[] array = new char[128]; + final int offset = 100; + for (int i = 0; i < s1.length(); ++i) { + array[offset + i] = s1.charAt(i); + } + CharBuffer buffer = CharBuffer.wrap(array, offset, s1.length()); + assertEquals(caseInsensitiveHashCode(s1), caseInsensitiveHashCode(buffer)); } } diff --git a/common/src/test/java/io/netty/util/ByteStringTest.java b/common/src/test/java/io/netty/util/ByteStringTest.java index 5379b296ca..7e459e938d 100644 --- a/common/src/test/java/io/netty/util/ByteStringTest.java +++ b/common/src/test/java/io/netty/util/ByteStringTest.java @@ -17,7 +17,6 @@ package io.netty.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; @@ -36,8 +35,6 @@ public class ByteStringTest { private int length = 100; private ByteString aByteString; private ByteString bByteString; - private ByteString greaterThanAByteString; - private ByteString lessThanAByteString; private Random r = new Random(); @Before @@ -52,66 +49,6 @@ public class ByteStringTest { System.arraycopy(a, aOffset, b, bOffset, length); aByteString = new ByteString(a, aOffset, length, false); bByteString = new ByteString(b, bOffset, length, false); - - int i; - final int end = aOffset + length; - // Find an element that can be decremented - for (i = aOffset + 1; i < end; ++i) { - if (a[i] > Byte.MIN_VALUE) { - --a[i]; - break; - } - } - if (i == end) { - throw new IllegalStateException("Couldn't find an index to decrement, all random numbers Byte.MIN_VALUE"); - } - lessThanAByteString = new ByteString(a, aOffset, length, true); - ++a[i]; // Restore the a array to the original value - - // Find an element that can be incremented - for (i = aOffset + 1; i < end; ++i) { - if (a[i] < Byte.MAX_VALUE) { - ++a[i]; - break; - } - } - if (i == end) { - throw new IllegalStateException("Couldn't find an index to increment, all random numbers Byte.MAX_VALUE"); - } - greaterThanAByteString = new ByteString(a, aOffset, length, true); - --a[i]; // Restore the a array to the original value - } - - @Test - public void testEqualsComparareSelf() { - assertTrue(ByteString.DEFAULT_COMPARATOR.compare(aByteString, aByteString) == 0); - assertEquals(bByteString, bByteString); - } - - @Test - public void testEqualsComparatorAgainstAnother1() { - assertTrue(ByteString.DEFAULT_COMPARATOR.compare(bByteString, aByteString) == 0); - assertEquals(bByteString, aByteString); - assertEquals(bByteString.hashCode(), aByteString.hashCode()); - } - - @Test - public void testEqualsComparatorAgainstAnother2() { - assertTrue(ByteString.DEFAULT_COMPARATOR.compare(aByteString, bByteString) == 0); - assertEquals(aByteString, bByteString); - assertEquals(aByteString.hashCode(), bByteString.hashCode()); - } - - @Test - public void testLessThan() { - assertTrue(ByteString.DEFAULT_COMPARATOR.compare(lessThanAByteString, aByteString) < 0); - assertTrue(ByteString.DEFAULT_COMPARATOR.compare(aByteString, lessThanAByteString) > 0); - } - - @Test - public void testGreaterThan() { - assertTrue(ByteString.DEFAULT_COMPARATOR.compare(greaterThanAByteString, aByteString) > 0); - assertTrue(ByteString.DEFAULT_COMPARATOR.compare(aByteString, greaterThanAByteString) < 0); } @Test 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 fe0ec4b634..de39748cda 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 @@ -264,7 +264,7 @@ public final class HttpUploadClient { */ private static void formpostmultipart( Bootstrap bootstrap, String host, int port, URI uriFile, HttpDataFactory factory, - List> headers, List bodylist) throws Exception { + Iterable> headers, List bodylist) throws Exception { // XXX /formpostmultipart // Start the connection attempt. ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)); diff --git a/example/src/main/java/io/netty/example/http2/tiles/HttpServer.java b/example/src/main/java/io/netty/example/http2/tiles/HttpServer.java index 8b639cdc42..f46d01afd2 100644 --- a/example/src/main/java/io/netty/example/http2/tiles/HttpServer.java +++ b/example/src/main/java/io/netty/example/http2/tiles/HttpServer.java @@ -21,7 +21,6 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; -import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; diff --git a/example/src/main/java/io/netty/example/stomp/StompClientHandler.java b/example/src/main/java/io/netty/example/stomp/StompClientHandler.java index 2a51be6f53..c27d8ea268 100644 --- a/example/src/main/java/io/netty/example/stomp/StompClientHandler.java +++ b/example/src/main/java/io/netty/example/stomp/StompClientHandler.java @@ -62,7 +62,7 @@ public class StompClientHandler extends SimpleChannelInboundHandler ctx.writeAndFlush(subscribeFrame); break; case RECEIPT: - String receiptHeader = frame.headers().getAndConvert(StompHeaders.RECEIPT_ID); + String receiptHeader = frame.headers().getAsString(StompHeaders.RECEIPT_ID); if (state == ClientState.AUTHENTICATED && receiptHeader.equals(subscrReceiptId)) { StompFrame msgFrame = new DefaultStompFrame(StompCommand.SEND); msgFrame.headers().set(StompHeaders.DESTINATION, StompClient.TOPIC); diff --git a/microbench/src/main/java/io/netty/microbench/headers/ExampleHeaders.java b/microbench/src/main/java/io/netty/microbench/headers/ExampleHeaders.java index 606ead4d70..abfd9814bd 100644 --- a/microbench/src/main/java/io/netty/microbench/headers/ExampleHeaders.java +++ b/microbench/src/main/java/io/netty/microbench/headers/ExampleHeaders.java @@ -80,7 +80,7 @@ public final class ExampleHeaders { 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("cookie", "noneofyourbusiness"); header.put("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)"); EXAMPLES.put(HeaderExample.ELEVEN, header); diff --git a/microbench/src/main/java/io/netty/microbench/headers/HeadersBenchmark.java b/microbench/src/main/java/io/netty/microbench/headers/HeadersBenchmark.java index c3637e5906..0d0956d528 100644 --- a/microbench/src/main/java/io/netty/microbench/headers/HeadersBenchmark.java +++ b/microbench/src/main/java/io/netty/microbench/headers/HeadersBenchmark.java @@ -83,6 +83,14 @@ public class HeadersBenchmark extends AbstractMicrobenchmark { } } + @Benchmark + @BenchmarkMode(Mode.AverageTime) + public void httpRemove(Blackhole bh) { + for (AsciiString name : httpNames) { + bh.consume(httpHeaders.remove(name)); + } + } + @Benchmark @BenchmarkMode(Mode.AverageTime) public void httpGet(Blackhole bh) { @@ -110,6 +118,14 @@ public class HeadersBenchmark extends AbstractMicrobenchmark { } } + @Benchmark + @BenchmarkMode(Mode.AverageTime) + public void http2Remove(Blackhole bh) { + for (ByteString name : http2Names) { + bh.consume(http2Headers.remove(name)); + } + } + @Benchmark @BenchmarkMode(Mode.AverageTime) public void http2Get(Blackhole bh) {