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..ba8588a402 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/CombinedHttpHeaders.java @@ -0,0 +1,173 @@ +/* + * 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.internal.StringUtil; + +import java.util.Collection; +import java.util.Iterator; + +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 { + /** + * An estimate of the size of a header value. + */ + private static final int VALUE_LENGTH_ESTIMATE = 10; + private CsvValueEscaper objectEscaper; + private CsvValueEscaper charSequenceEscaper; + + public CombinedHttpHeaders(boolean validate) { + super(validate); + } + + 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 CombinedHttpHeaders add(CharSequence name, CharSequence value) { + return addEscapedValue(name, StringUtil.escapeCsv(value)); + } + + @Override + public CombinedHttpHeaders add(CharSequence name, CharSequence... values) { + return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values)); + } + + @Override + public CombinedHttpHeaders add(CharSequence name, Iterable values) { + return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values)); + } + + @Override + public CombinedHttpHeaders addObject(CharSequence name, Iterable values) { + return addEscapedValue(name, commaSeparate(objectEscaper(), values)); + } + + @Override + public CombinedHttpHeaders addObject(CharSequence name, Object... values) { + return addEscapedValue(name, commaSeparate(objectEscaper(), values)); + } + + @Override + public CombinedHttpHeaders set(CharSequence name, CharSequence... values) { + super.set(name, commaSeparate(charSequenceEscaper(), values)); + return this; + } + + @Override + public CombinedHttpHeaders set(CharSequence name, Iterable values) { + super.set(name, commaSeparate(charSequenceEscaper(), values)); + return this; + } + + @Override + public CombinedHttpHeaders setObject(CharSequence name, Object... values) { + super.set(name, commaSeparate(objectEscaper(), values)); + return this; + } + + @Override + public CombinedHttpHeaders setObject(CharSequence name, Iterable values) { + super.set(name, commaSeparate(objectEscaper(), values)); + return this; + } + + private CombinedHttpHeaders 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 29d4495bd9..6db955d248 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 ab384e43cc..e1fd297314 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,60 +15,65 @@ */ 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.TextHeaders; +import io.netty.handler.codec.Headers; +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.Calendar; -import java.util.Comparator; import java.util.Date; +import java.util.Iterator; import java.util.List; -import java.util.TreeMap; +import java.util.Map.Entry; +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; -public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeaders { - - private static final int HIGHEST_INVALID_NAME_CHAR_MASK = ~63; +public class DefaultHttpHeaders extends DefaultHeaders implements HttpHeaders { private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15; - - /** - * 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 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)); + } + } + } + }; public DefaultHttpHeaders() { this(true); } + @SuppressWarnings("unchecked") public DefaultHttpHeaders(boolean validate) { - this(validate, false); + super(CASE_INSENSITIVE_HASHER, valueConverter(validate), + validate ? HttpNameValidator : NameValidator.NOT_NULL); } - protected DefaultHttpHeaders(boolean validate, boolean singleHeaderFields) { - this(true, validate ? HeaderNameValidator.INSTANCE : NO_NAME_VALIDATOR, singleHeaderFields); - } - - protected DefaultHttpHeaders(boolean validate, - DefaultHeaders.NameValidator nameValidator, - boolean singleHeaderFields) { - super(new TreeMap(AsciiString.CHARSEQUENCE_CASE_INSENSITIVE_ORDER), - nameValidator, - validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE, - singleHeaderFields); + protected DefaultHttpHeaders(boolean validateValue, NameValidator nameValidator) { + super(CASE_INSENSITIVE_HASHER, valueConverter(validateValue), nameValidator); } @Override @@ -162,7 +167,7 @@ public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeader } @Override - public HttpHeaders add(TextHeaders headers) { + public HttpHeaders add(Headers headers) { super.add(headers); return this; } @@ -258,13 +263,13 @@ public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeader } @Override - public HttpHeaders set(TextHeaders headers) { + public HttpHeaders set(Headers headers) { super.set(headers); return this; } @Override - public HttpHeaders setAll(TextHeaders headers) { + public HttpHeaders setAll(Headers headers) { super.setAll(headers); return this; } @@ -276,73 +281,96 @@ public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeader } @Override - public int hashCode() { - return size(); + public boolean equals(Object o) { + if (!(o instanceof HttpHeaders)) { + return false; + } + return equals((HttpHeaders) o, CASE_SENSITIVE_HASHER); } @Override - public boolean equals(Object other) { - if (!(other instanceof HttpHeaders)) { - return false; - } - HttpHeaders headers = (HttpHeaders) other; - return DefaultHeaders.comparatorEquals(this, headers, AsciiString.CHARSEQUENCE_CASE_SENSITIVE_ORDER); + public int hashCode() { + return hashCode(CASE_SENSITIVE_HASHER); } - static final class HeaderNameValidator implements DefaultHeaders.NameValidator { + @Override + public String getAsString(CharSequence name) { + return HeadersUtils.getAsString(this, name); + } - public static final HeaderNameValidator INSTANCE = new HeaderNameValidator(); + @Override + public List getAllAsString(CharSequence name) { + return HeadersUtils.getAllAsString(this, name); + } - private HeaderNameValidator() { - } + @Override + public Iterator> iteratorAsString() { + return HeadersUtils.iteratorAsString(this); + } - @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); + @Override + public boolean contains(CharSequence name, CharSequence value) { + return contains(name, value, false); + } - // 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); - } + @Override + public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { + return contains(name, value, + ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER); + } - // 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 { + private static ValueConverter valueConverter(boolean validate) { + return validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE; + } - public static final HeaderValueConverter INSTANCE = new HeaderValueConverter(); + 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 efb6e0bd4a..b92619035d 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 2a0a7ccd6d..5d075a4234 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 d43a16fca0..99c1960f23 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 @@ -49,12 +49,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 3836e0b059..0ff4b77dcd 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 @@ -15,18 +15,16 @@ */ package io.netty.handler.codec.http; +import java.util.Map; + import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.handler.codec.DefaultHeaders; import io.netty.util.internal.StringUtil; -import java.util.Map; - /** * The default {@link LastHttpContent} implementation. */ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHttpContent { - private final HttpHeaders trailingHeaders; private final boolean validateHeaders; @@ -108,24 +106,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 : 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 index 275807eb9c..e222c45417 100644 --- 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 @@ -16,12 +16,35 @@ package io.netty.handler.codec.http; -import io.netty.handler.codec.EmptyTextHeaders; -import io.netty.handler.codec.TextHeaders; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Map.Entry; -public class EmptyHttpHeaders extends EmptyTextHeaders implements HttpHeaders { +import io.netty.handler.codec.EmptyHeaders; +import io.netty.handler.codec.Headers; + +public class EmptyHttpHeaders extends EmptyHeaders implements HttpHeaders { public static final EmptyHttpHeaders INSTANCE = new EmptyHttpHeaders(); + private static final Iterator> EMPTY_STRING_ITERATOR = + new Iterator>() { + @Override + public boolean hasNext() { + return false; + } + + @Override + public Entry next() { + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + }; protected EmptyHttpHeaders() { } @@ -117,7 +140,7 @@ public class EmptyHttpHeaders extends EmptyTextHeaders implements HttpHeaders { } @Override - public HttpHeaders add(TextHeaders headers) { + public HttpHeaders add(Headers headers) { super.add(headers); return this; } @@ -213,13 +236,13 @@ public class EmptyHttpHeaders extends EmptyTextHeaders implements HttpHeaders { } @Override - public HttpHeaders set(TextHeaders headers) { + public HttpHeaders set(Headers headers) { super.set(headers); return this; } @Override - public HttpHeaders setAll(TextHeaders headers) { + public HttpHeaders setAll(Headers headers) { super.setAll(headers); return this; } @@ -229,4 +252,24 @@ public class EmptyHttpHeaders extends EmptyTextHeaders implements HttpHeaders { super.clear(); return this; } + + @Override + public String getAsString(CharSequence name) { + return null; + } + + @Override + public List getAllAsString(CharSequence name) { + return Collections.emptyList(); + } + + @Override + public Iterator> iteratorAsString() { + return EMPTY_STRING_ITERATOR; + } + + @Override + public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { + return false; + } } 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 6ba33719b7..4e29c5084e 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 @@ -182,7 +182,7 @@ public class HttpClientUpgradeHandler extends HttpObjectAggregator { 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 0183da70f6..9e9c0ddf4c 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, CharSequence acceptEncoding) throws Exception { CharSequence 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/HttpContentDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecoder.java index 2de627c68c..ce52f96ffc 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentDecoder.java @@ -77,7 +77,7 @@ public abstract class HttpContentDecoder extends MessageToMessageDecoder values = - new ArrayList(m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING)); + List values = m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING); if (values.isEmpty()) { return; } 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 8be81f871a..df01db9690 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 @@ -15,13 +15,17 @@ */ package io.netty.handler.codec.http; -import io.netty.handler.codec.TextHeaders; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; + +import io.netty.handler.codec.Headers; /** * Provides the constants for the standard HTTP header names and values and * commonly used utility methods that accesses an {@link HttpMessage}. */ -public interface HttpHeaders extends TextHeaders { +public interface HttpHeaders extends Headers { @Override HttpHeaders add(CharSequence name, CharSequence value); @@ -68,7 +72,7 @@ public interface HttpHeaders extends TextHeaders { HttpHeaders addTimeMillis(CharSequence name, long value); @Override - HttpHeaders add(TextHeaders headers); + HttpHeaders add(Headers headers); @Override HttpHeaders set(CharSequence name, CharSequence value); @@ -116,11 +120,41 @@ public interface HttpHeaders extends TextHeaders { HttpHeaders setTimeMillis(CharSequence name, long value); @Override - HttpHeaders set(TextHeaders headers); + HttpHeaders set(Headers headers); @Override - HttpHeaders setAll(TextHeaders headers); + HttpHeaders setAll(Headers headers); @Override HttpHeaders 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/http/HttpMethod.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java index f9c624e1e5..530d13fb6c 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 @@ -15,6 +15,7 @@ */ package io.netty.handler.codec.http; +import static io.netty.util.internal.ObjectUtil.checkNotNull; import io.netty.util.AsciiString; import java.util.HashMap; @@ -85,8 +86,7 @@ public class HttpMethod implements Comparable { */ public static final HttpMethod CONNECT = new HttpMethod("CONNECT"); - private static final Map methodMap = - new HashMap(); + private static final Map methodMap = new HashMap(); static { methodMap.put(OPTIONS.toString(), OPTIONS); @@ -106,21 +106,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 AsciiString name; @@ -134,11 +121,7 @@ public class HttpMethod implements Comparable { * ICAP */ public HttpMethod(String name) { - if (name == null) { - 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 f89a65a5ca..f8f9ca6a11 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 d65a399b1b..a06784687c 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 @@ -183,7 +183,7 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest this.factory = factory; // Fill default values - setMultipart(this.request.headers().getAndConvert(HttpHeaderNames.CONTENT_TYPE)); + setMultipart(this.request.headers().getAsString(HttpHeaderNames.CONTENT_TYPE)); if (request instanceof HttpContent) { // Offer automatically if the given request is als type of HttpContent // See #1089 @@ -688,13 +688,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 @@ -722,7 +722,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,7 +733,7 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest throw new ErrorDataDecoderException(e); } currentFieldAttributes.put(HttpHeaderNames.CONTENT_TRANSFER_ENCODING.toString(), 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,9 +744,9 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest throw new ErrorDataDecoderException(e); } currentFieldAttributes.put(HttpHeaderNames.CONTENT_LENGTH.toString(), 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/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java index 7fcfa4ab4b..2450bbd858 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java @@ -141,7 +141,7 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder { */ public static boolean isMultipart(HttpRequest request) { if (request.headers().contains(HttpHeaderNames.CONTENT_TYPE)) { - return getMultipartDataBoundary(request.headers().getAndConvert(HttpHeaderNames.CONTENT_TYPE)) != null; + return getMultipartDataBoundary(request.headers().getAsString(HttpHeaderNames.CONTENT_TYPE)) != null; } else { return false; } 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 4ceaf24721..6433c0ef85 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 @@ -717,7 +717,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { } HttpHeaders headers = request.headers(); - List contentTypes = headers.getAllAndConvert(HttpHeaderNames.CONTENT_TYPE); + List contentTypes = headers.getAllAsString(HttpHeaderNames.CONTENT_TYPE); List transferEncoding = headers.getAll(HttpHeaderNames.TRANSFER_ENCODING); if (contentTypes != null) { headers.remove(HttpHeaderNames.CONTENT_TYPE); @@ -754,7 +754,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/WebSocketClientHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java index 0997584b6e..c7ee50106f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java @@ -215,7 +215,7 @@ public abstract class WebSocketClientHandshaker { // Verify the subprotocol that we received from the server. // This must be one of our expected subprotocols - or null/empty if we didn't want to speak a subprotocol - String receivedProtocol = response.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); + String receivedProtocol = response.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); receivedProtocol = receivedProtocol != null ? receivedProtocol.trim() : null; String expectedProtocol = expectedSubprotocol != null ? expectedSubprotocol : ""; boolean protocolValid = false; 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 51d69e02f4..3579bc07b0 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 a3dc0671e4..705b7222da 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 @@ -109,8 +109,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)) - || !WEBSOCKET.equalsIgnoreCase(req.headers().get(HttpHeaderNames.UPGRADE))) { + if (!HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(req.headers().get(HttpHeaderNames.CONNECTION)) + || !WEBSOCKET.contentEqualsIgnoreCase(req.headers().get(HttpHeaderNames.UPGRADE))) { throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade"); } @@ -133,7 +133,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().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); + String subprotocols = req.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); if (subprotocols != null) { String selectedSubprotocol = selectSubprotocol(subprotocols); if (selectedSubprotocol == null) { @@ -146,8 +146,8 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker { } // Calculate the answer of the challenge. - String key1 = req.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_KEY1); - String key2 = req.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_KEY2); + String key1 = req.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_KEY1); + String key2 = req.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_KEY2); int a = (int) (Long.parseLong(BEGINNING_DIGIT.matcher(key1).replaceAll("")) / BEGINNING_SPACE.matcher(key1).replaceAll("").length()); int b = (int) (Long.parseLong(BEGINNING_DIGIT.matcher(key2).replaceAll("")) / @@ -162,7 +162,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().getAndConvert(HttpHeaderNames.WEBSOCKET_PROTOCOL); + String protocol = req.headers().getAsString(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 ebc94fa910..c467a476e6 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,7 +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().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); + String subprotocols = req.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); if (subprotocols != null) { String selectedSubprotocol = selectSubprotocol(subprotocols); if (selectedSubprotocol == null) { 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 8eb5193871..4697ab5fd2 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,7 +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().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); + String subprotocols = req.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); if (subprotocols != null) { String selectedSubprotocol = selectSubprotocol(subprotocols); if (selectedSubprotocol == null) { 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 cdca072246..8df20f3106 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,7 +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().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); + String subprotocols = req.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL); if (subprotocols != null) { String selectedSubprotocol = selectSubprotocol(subprotocols); if (selectedSubprotocol == null) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandler.java index 421a2beb7d..b0e41e8e41 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandler.java @@ -63,7 +63,7 @@ public class WebSocketClientExtensionHandler extends ChannelHandlerAdapter { public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (msg instanceof HttpRequest && WebSocketExtensionUtil.isWebsocketUpgrade((HttpRequest) msg)) { HttpRequest request = (HttpRequest) msg; - String headerValue = request.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS); + String headerValue = request.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS); for (WebSocketClientExtensionHandshaker extentionHandshaker : extensionHandshakers) { WebSocketExtensionData extensionData = extentionHandshaker.newRequestData(); @@ -84,7 +84,7 @@ public class WebSocketClientExtensionHandler extends ChannelHandlerAdapter { HttpResponse response = (HttpResponse) msg; if (WebSocketExtensionUtil.isWebsocketUpgrade(response)) { - String extensionsHeader = response.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS); + String extensionsHeader = response.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS); if (extensionsHeader != null) { List extensions = diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandler.java index bf685db38f..b9ec9803d1 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandler.java @@ -69,7 +69,7 @@ public class WebSocketServerExtensionHandler extends ChannelHandlerAdapter { HttpRequest request = (HttpRequest) msg; if (WebSocketExtensionUtil.isWebsocketUpgrade(request)) { - String extensionsHeader = request.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS); + String extensionsHeader = request.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS); if (extensionsHeader != null) { List extensions = @@ -107,7 +107,7 @@ public class WebSocketServerExtensionHandler extends ChannelHandlerAdapter { if (msg instanceof HttpResponse && WebSocketExtensionUtil.isWebsocketUpgrade((HttpResponse) msg) && validExtensions != null) { HttpResponse response = (HttpResponse) msg; - String headerValue = response.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS); + String headerValue = response.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS); for (WebSocketServerExtension extension : validExtensions) { WebSocketExtensionData extensionData = extension.newReponseData(); 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 e91340a62f..0af0528681 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..6b2327977a --- /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.asArray()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void addCharSequencesCsvWithExistingHeader() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.add(HEADER_NAME, HeaderValue.THREE.asArray()); + 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.asArray()); + 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.addObject(HEADER_NAME, HeaderValue.THREE.asArray()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void addObjectsIterableCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.addObject(HEADER_NAME, HeaderValue.THREE.asList()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void addObjectsCsvWithExistingHeader() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.addObject(HEADER_NAME, HeaderValue.THREE.asArray()); + headers.addObject(HEADER_NAME, HeaderValue.FIVE.subset(4)); + assertCsvValues(headers, HeaderValue.FIVE); + } + + @Test + public void setCharSequenceCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.set(HEADER_NAME, HeaderValue.THREE.asArray()); + 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.setObject(HEADER_NAME, HeaderValue.THREE.asArray()); + assertCsvValues(headers, HeaderValue.THREE); + } + + @Test + public void setObjectIterableCsv() { + final CombinedHttpHeaders headers = newCombinedHttpHeaders(); + headers.setObject(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.addObject(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 e19a5b4d48..199cb82bdf 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,14 +15,23 @@ */ 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.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() { @@ -56,4 +65,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.asArray()); + 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.addObject(HEADER_NAME, HeaderValue.THREE.asArray()); + assertDefaultValues(headers, HeaderValue.THREE); + } + + @Test + public void setCharSequences() { + final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); + headers.set(HEADER_NAME, HeaderValue.THREE.asArray()); + 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.setObject(HEADER_NAME, HeaderValue.THREE.asArray()); + assertDefaultValues(headers, HeaderValue.THREE); + } + + @Test + public void setObjectIterable() { + final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders(); + headers.setObject(HEADER_NAME, HeaderValue.THREE.asList()); + assertDefaultValues(headers, HeaderValue.THREE); + } + + private static void assertDefaultValues(final DefaultHttpHeaders headers, final HeaderValue headerValue) { + assertTrue(contentEquals(headerValue.asArray()[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/HttpContentCompressorTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java index b81767709c..232307efd9 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java @@ -303,8 +303,8 @@ public class HttpContentCompressorTest { HttpResponse res = (HttpResponse) o; assertThat(res, is(not(instanceOf(HttpContent.class)))); - assertThat(res.headers().getAndConvert(HttpHeaderNames.TRANSFER_ENCODING), is("chunked")); + assertThat(res.headers().getAsString(HttpHeaderNames.TRANSFER_ENCODING), is("chunked")); assertThat(res.headers().get(HttpHeaderNames.CONTENT_LENGTH), is(nullValue())); - assertThat(res.headers().getAndConvert(HttpHeaderNames.CONTENT_ENCODING), is("gzip")); + assertThat(res.headers().getAsString(HttpHeaderNames.CONTENT_ENCODING), is("gzip")); } } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentEncoderTest.java index c343b7443f..8d3c6f0ea6 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentEncoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentEncoderTest.java @@ -333,7 +333,7 @@ public class HttpContentEncoderTest { HttpResponse res = (HttpResponse) o; assertThat(res, is(not(instanceOf(HttpContent.class)))); - assertThat(res.headers().getAndConvert(HttpHeaderNames.TRANSFER_ENCODING), is("chunked")); + assertThat(res.headers().getAsString(HttpHeaderNames.TRANSFER_ENCODING), is("chunked")); assertThat(res.headers().get(HttpHeaderNames.CONTENT_LENGTH), is(nullValue())); HttpContent chunk = ch.readOutbound(); @@ -348,8 +348,8 @@ public class HttpContentEncoderTest { HttpResponse res = (HttpResponse) o; assertThat(res, is(not(instanceOf(HttpContent.class)))); - assertThat(res.headers().getAndConvert(HttpHeaderNames.TRANSFER_ENCODING), is("chunked")); + assertThat(res.headers().getAsString(HttpHeaderNames.TRANSFER_ENCODING), is("chunked")); assertThat(res.headers().get(HttpHeaderNames.CONTENT_LENGTH), is(nullValue())); - assertThat(res.headers().getAndConvert(HttpHeaderNames.CONTENT_ENCODING), is("test")); + assertThat(res.headers().getAsString(HttpHeaderNames.CONTENT_ENCODING), is("test")); } } 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 index 596cfb556d..ca393f2e8d 100644 --- 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 @@ -52,9 +52,9 @@ public class HttpHeaderUtilTest { @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)); } } 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 deleted file mode 100644 index b98e3fd1f6..0000000000 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013 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 HttpHeadersTest { - - @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.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)); - } - - @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); - } -} 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..9b2e72030a --- /dev/null +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTestUtils.java @@ -0,0 +1,126 @@ +/* + * 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.Arrays; +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 CharSequence[] array; + + HeaderValue(final String value, final int nr) { + this.nr = nr; + this.value = value; + } + + @Override + public String toString() { + return value; + } + + public CharSequence[] 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 CharSequence[] arr) { + final StringBuilder sb = new StringBuilder(); + int end = arr.length - 1; + for (int i = 0; i < end; i++) { + final CharSequence value = arr[i]; + quoted(sb, value).append(COMMA); + } + quoted(sb, arr[end]); + return sb.toString(); + } + + public CharSequence asCsv() { + return asCsv(asArray()); + } + + 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/http/HttpResponseDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java index 52b0df9d32..7407b43890 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java @@ -248,7 +248,7 @@ public class HttpResponseDecoderTest { HttpResponse res = ch.readInbound(); assertThat(res.protocolVersion(), sameInstance(HttpVersion.HTTP_1_1)); assertThat(res.status(), is(HttpResponseStatus.OK)); - assertThat(res.headers().getAndConvert(HttpHeaderNames.TRANSFER_ENCODING), is("chunked")); + assertThat(res.headers().getAsString(HttpHeaderNames.TRANSFER_ENCODING), is("chunked")); assertThat(ch.readInbound(), is(nullValue())); // Close the connection without sending anything. @@ -269,7 +269,7 @@ public class HttpResponseDecoderTest { HttpResponse res = ch.readInbound(); assertThat(res.protocolVersion(), sameInstance(HttpVersion.HTTP_1_1)); assertThat(res.status(), is(HttpResponseStatus.OK)); - assertThat(res.headers().getAndConvert(HttpHeaderNames.TRANSFER_ENCODING), is("chunked")); + assertThat(res.headers().getAsString(HttpHeaderNames.TRANSFER_ENCODING), is("chunked")); // Read the partial content. HttpContent content = ch.readInbound(); @@ -339,7 +339,7 @@ public class HttpResponseDecoderTest { HttpResponse res = ch.readInbound(); assertThat(res.protocolVersion(), sameInstance(HttpVersion.HTTP_1_1)); assertThat(res.status(), is(HttpResponseStatus.OK)); - assertThat(res.headers().getAndConvert("X-Header"), is("h2=h2v2; Expires=Wed, 09-Jun-2021 10:18:14 GMT")); + assertThat(res.headers().getAsString("X-Header"), is("h2=h2v2; Expires=Wed, 09-Jun-2021 10:18:14 GMT")); assertThat(ch.readInbound(), is(nullValue())); ch.writeInbound(Unpooled.wrappedBuffer(new byte[1024])); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java index 2ab2b54614..133fee9477 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java @@ -91,7 +91,7 @@ public class HttpServerCodecTest { // Ensure the aggregator generates a full request. FullHttpRequest req = ch.readInbound(); - assertThat(req.headers().getAndConvert(HttpHeaderNames.CONTENT_LENGTH), is("1")); + assertThat(req.headers().getAsString(HttpHeaderNames.CONTENT_LENGTH), is("1")); assertThat(req.content().readableBytes(), is(1)); assertThat(req.content().readByte(), is((byte) 42)); req.release(); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsConfigTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsConfigTest.java index a209d857e2..8c4faca2c8 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsConfigTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsConfigTest.java @@ -95,20 +95,20 @@ public class CorsConfigTest { @Test public void preflightResponseHeadersSingleValue() { final CorsConfig cors = withAnyOrigin().preflightResponseHeader("SingleValue", "value").build(); - assertThat(cors.preflightResponseHeaders().getAndConvert("SingleValue"), equalTo("value")); + assertThat(cors.preflightResponseHeaders().getAsString("SingleValue"), equalTo("value")); } @Test public void preflightResponseHeadersMultipleValues() { final CorsConfig cors = withAnyOrigin().preflightResponseHeader("MultipleValues", "value1", "value2").build(); - assertThat(cors.preflightResponseHeaders().getAllAndConvert("MultipleValues"), hasItems("value1", "value2")); + assertThat(cors.preflightResponseHeaders().getAllAsString("MultipleValues"), hasItems("value1", "value2")); } @Test public void defaultPreflightResponseHeaders() { final CorsConfig cors = withAnyOrigin().build(); assertThat(cors.preflightResponseHeaders().get(HttpHeaderNames.DATE), is(notNullValue())); - assertThat(cors.preflightResponseHeaders().getAndConvert(HttpHeaderNames.CONTENT_LENGTH), is("0")); + assertThat(cors.preflightResponseHeaders().getAsString(HttpHeaderNames.CONTENT_LENGTH), is("0")); } @Test diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsHandlerTest.java index d3a4270b29..4e35fea09f 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsHandlerTest.java @@ -47,14 +47,14 @@ public class CorsHandlerTest { @Test public void simpleRequestWithAnyOrigin() { final HttpResponse response = simpleRequest(CorsConfig.withAnyOrigin().build(), "http://localhost:7777"); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_ORIGIN), is("*")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_ORIGIN), is("*")); } @Test public void simpleRequestWithOrigin() { final String origin = "http://localhost:8888"; final HttpResponse response = simpleRequest(CorsConfig.withOrigin(origin).build(), origin); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_ORIGIN), is(origin)); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_ORIGIN), is(origin)); } @Test @@ -63,9 +63,9 @@ public class CorsHandlerTest { final String origin2 = "https://localhost:8888"; final String[] origins = {origin1, origin2}; final HttpResponse response1 = simpleRequest(CorsConfig.withOrigins(origins).build(), origin1); - assertThat(response1.headers().getAndConvert(ACCESS_CONTROL_ALLOW_ORIGIN), is(origin1)); + assertThat(response1.headers().getAsString(ACCESS_CONTROL_ALLOW_ORIGIN), is(origin1)); final HttpResponse response2 = simpleRequest(CorsConfig.withOrigins(origins).build(), origin2); - assertThat(response2.headers().getAndConvert(ACCESS_CONTROL_ALLOW_ORIGIN), is(origin2)); + assertThat(response2.headers().getAsString(ACCESS_CONTROL_ALLOW_ORIGIN), is(origin2)); } @Test @@ -81,10 +81,10 @@ public class CorsHandlerTest { .allowedRequestMethods(GET, DELETE) .build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_ORIGIN), is("http://localhost:8888")); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_METHODS), containsString("GET")); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_METHODS), containsString("DELETE")); - assertThat(response.headers().getAndConvert(VARY), equalTo(ORIGIN.toString())); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_ORIGIN), is("http://localhost:8888")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_METHODS), containsString("GET")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_METHODS), containsString("DELETE")); + assertThat(response.headers().getAsString(VARY), equalTo(ORIGIN.toString())); } @Test @@ -94,21 +94,21 @@ public class CorsHandlerTest { .allowedRequestHeaders("content-type", "xheader1") .build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_ORIGIN), is("http://localhost:8888")); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_METHODS), containsString("OPTIONS")); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_METHODS), containsString("GET")); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_HEADERS), containsString("content-type")); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_HEADERS), containsString("xheader1")); - assertThat(response.headers().getAndConvert(VARY), equalTo(ORIGIN.toString())); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_ORIGIN), is("http://localhost:8888")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_METHODS), containsString("OPTIONS")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_METHODS), containsString("GET")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_HEADERS), containsString("content-type")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_HEADERS), containsString("xheader1")); + assertThat(response.headers().getAsString(VARY), equalTo(ORIGIN.toString())); } @Test public void preflightRequestWithDefaultHeaders() { final CorsConfig config = CorsConfig.withOrigin("http://localhost:8888").build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); - assertThat(response.headers().getAndConvert(CONTENT_LENGTH), is("0")); + assertThat(response.headers().getAsString(CONTENT_LENGTH), is("0")); assertThat(response.headers().get(DATE), is(notNullValue())); - assertThat(response.headers().getAndConvert(VARY), equalTo(ORIGIN.toString())); + assertThat(response.headers().getAsString(VARY), equalTo(ORIGIN.toString())); } @Test @@ -117,8 +117,8 @@ public class CorsHandlerTest { .preflightResponseHeader("CustomHeader", "somevalue") .build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); - assertThat(response.headers().getAndConvert("CustomHeader"), equalTo("somevalue")); - assertThat(response.headers().getAndConvert(VARY), equalTo(ORIGIN.toString())); + assertThat(response.headers().getAsString("CustomHeader"), equalTo("somevalue")); + assertThat(response.headers().getAsString(VARY), equalTo(ORIGIN.toString())); } @Test @@ -131,7 +131,7 @@ public class CorsHandlerTest { .build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); assertValues(response, headerName, value1, value2); - assertThat(response.headers().getAndConvert(VARY), equalTo(ORIGIN.toString())); + assertThat(response.headers().getAsString(VARY), equalTo(ORIGIN.toString())); } @Test @@ -144,7 +144,7 @@ public class CorsHandlerTest { .build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); assertValues(response, headerName, value1, value2); - assertThat(response.headers().getAndConvert(VARY), equalTo(ORIGIN.toString())); + assertThat(response.headers().getAsString(VARY), equalTo(ORIGIN.toString())); } @Test @@ -157,8 +157,8 @@ public class CorsHandlerTest { } }).build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); - assertThat(response.headers().getAndConvert("GenHeader"), equalTo("generatedValue")); - assertThat(response.headers().getAndConvert(VARY), equalTo(ORIGIN.toString())); + assertThat(response.headers().getAsString("GenHeader"), equalTo("generatedValue")); + assertThat(response.headers().getAsString(VARY), equalTo(ORIGIN.toString())); } @Test @@ -169,9 +169,9 @@ public class CorsHandlerTest { .allowCredentials() .build(); final HttpResponse response = preflightRequest(config, origin, "content-type, xheader1"); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_ORIGIN), is(equalTo("*"))); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_ORIGIN), is(equalTo("*"))); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_CREDENTIALS), is(nullValue())); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_ORIGIN), is(equalTo("*"))); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_ORIGIN), is(equalTo("*"))); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_CREDENTIALS), is(nullValue())); } @Test @@ -179,7 +179,7 @@ public class CorsHandlerTest { final String origin = "null"; final CorsConfig config = CorsConfig.withOrigin(origin).allowCredentials().build(); final HttpResponse response = preflightRequest(config, origin, "content-type, xheader1"); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_CREDENTIALS), is(equalTo("true"))); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_CREDENTIALS), is(equalTo("true"))); } @Test @@ -194,16 +194,16 @@ public class CorsHandlerTest { public void simpleRequestCustomHeaders() { final CorsConfig config = CorsConfig.withAnyOrigin().exposeHeaders("custom1", "custom2").build(); final HttpResponse response = simpleRequest(config, "http://localhost:7777"); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_ORIGIN), equalTo("*")); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_EXPOSE_HEADERS), containsString("custom1")); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_EXPOSE_HEADERS), containsString("custom2")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_ORIGIN), equalTo("*")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_EXPOSE_HEADERS), containsString("custom1")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_EXPOSE_HEADERS), containsString("custom2")); } @Test public void simpleRequestAllowCredentials() { final CorsConfig config = CorsConfig.withAnyOrigin().allowCredentials().build(); final HttpResponse response = simpleRequest(config, "http://localhost:7777"); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_CREDENTIALS), equalTo("true")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_CREDENTIALS), equalTo("true")); } @Test @@ -217,17 +217,17 @@ public class CorsHandlerTest { public void anyOriginAndAllowCredentialsShouldEchoRequestOrigin() { final CorsConfig config = CorsConfig.withAnyOrigin().allowCredentials().build(); final HttpResponse response = simpleRequest(config, "http://localhost:7777"); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_CREDENTIALS), equalTo("true")); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_ALLOW_ORIGIN), equalTo("http://localhost:7777")); - assertThat(response.headers().getAndConvert(VARY), equalTo(ORIGIN.toString())); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_CREDENTIALS), equalTo("true")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_ALLOW_ORIGIN), equalTo("http://localhost:7777")); + assertThat(response.headers().getAsString(VARY), equalTo(ORIGIN.toString())); } @Test public void simpleRequestExposeHeaders() { final CorsConfig config = CorsConfig.withAnyOrigin().exposeHeaders("one", "two").build(); final HttpResponse response = simpleRequest(config, "http://localhost:7777"); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_EXPOSE_HEADERS), containsString("one")); - assertThat(response.headers().getAndConvert(ACCESS_CONTROL_EXPOSE_HEADERS), containsString("two")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_EXPOSE_HEADERS), containsString("one")); + assertThat(response.headers().getAsString(ACCESS_CONTROL_EXPOSE_HEADERS), containsString("two")); } @Test @@ -328,7 +328,7 @@ public class CorsHandlerTest { } private static void assertValues(final HttpResponse response, final String headerName, final String... values) { - final String header = response.headers().getAndConvert(headerName); + final String header = response.headers().getAsString(headerName); for (String value : values) { assertThat(header, containsString(value)); } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandlerTest.java index e01ab6c626..c03aef60bd 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandlerTest.java @@ -67,14 +67,14 @@ public class WebSocketClientExtensionHandlerTest { HttpRequest req2 = ch.readOutbound(); List reqExts = WebSocketExtensionUtil.extractExtensions( - req2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + req2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); HttpResponse res = newUpgradeResponse("main"); ch.writeInbound(res); HttpResponse res2 = ch.readInbound(); List resExts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); // test assertEquals(2, reqExts.size()); @@ -117,14 +117,14 @@ public class WebSocketClientExtensionHandlerTest { HttpRequest req2 = ch.readOutbound(); List reqExts = WebSocketExtensionUtil.extractExtensions( - req2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + req2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); HttpResponse res = newUpgradeResponse("fallback"); ch.writeInbound(res); HttpResponse res2 = ch.readInbound(); List resExts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); // test assertEquals(2, reqExts.size()); @@ -180,14 +180,14 @@ public class WebSocketClientExtensionHandlerTest { HttpRequest req2 = ch.readOutbound(); List reqExts = WebSocketExtensionUtil.extractExtensions( - req2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + req2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); HttpResponse res = newUpgradeResponse("main, fallback"); ch.writeInbound(res); HttpResponse res2 = ch.readInbound(); List resExts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); // test assertEquals(2, reqExts.size()); @@ -237,7 +237,7 @@ public class WebSocketClientExtensionHandlerTest { HttpRequest req2 = ch.readOutbound(); List reqExts = WebSocketExtensionUtil.extractExtensions( - req2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + req2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); HttpResponse res = newUpgradeResponse("main, fallback"); ch.writeInbound(res); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandlerTest.java index 1282f5b7ef..d037e430a0 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandlerTest.java @@ -76,7 +76,7 @@ public class WebSocketServerExtensionHandlerTest { HttpResponse res2 = ch.readOutbound(); List resExts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); // test assertEquals(1, resExts.size()); @@ -127,7 +127,7 @@ public class WebSocketServerExtensionHandlerTest { HttpResponse res2 = ch.readOutbound(); List resExts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); // test assertEquals(2, resExts.size()); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/WebSocketServerCompressionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/WebSocketServerCompressionHandlerTest.java index f6a02a67ad..b2dc052314 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/WebSocketServerCompressionHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/WebSocketServerCompressionHandlerTest.java @@ -45,7 +45,7 @@ public class WebSocketServerCompressionHandlerTest { HttpResponse res2 = ch.readOutbound(); List exts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name()); assertTrue(exts.get(0).parameters().isEmpty()); @@ -66,7 +66,7 @@ public class WebSocketServerCompressionHandlerTest { HttpResponse res2 = ch.readOutbound(); List exts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name()); assertEquals("10", exts.get(0).parameters().get(CLIENT_MAX_WINDOW)); @@ -87,7 +87,7 @@ public class WebSocketServerCompressionHandlerTest { HttpResponse res2 = ch.readOutbound(); List exts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name()); assertTrue(exts.get(0).parameters().isEmpty()); @@ -108,7 +108,7 @@ public class WebSocketServerCompressionHandlerTest { HttpResponse res2 = ch.readOutbound(); List exts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name()); assertEquals("10", exts.get(0).parameters().get(SERVER_MAX_WINDOW)); @@ -163,7 +163,7 @@ public class WebSocketServerCompressionHandlerTest { HttpResponse res2 = ch.readOutbound(); List exts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name()); assertTrue(exts.get(0).parameters().isEmpty()); @@ -185,7 +185,7 @@ public class WebSocketServerCompressionHandlerTest { HttpResponse res2 = ch.readOutbound(); List exts = WebSocketExtensionUtil.extractExtensions( - res2.headers().getAndConvert(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); + res2.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS)); assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name()); assertTrue(exts.get(0).parameters().isEmpty()); 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 a249b9d80c..6c734be957 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 86fb67cece..f0ccfff3de 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 @@ -274,7 +274,7 @@ public final class HttpUtil { out.path(new AsciiString(request.uri())); out.method(new AsciiString(request.method().toString())); - String value = inHeaders.getAndConvert(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 @@ -321,8 +321,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); } } @@ -331,7 +331,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 6d047b6af3..a3963d068d 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,11 +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.Map.Entry; - import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.FullHttpMessage; @@ -26,7 +21,11 @@ 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.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} @@ -135,12 +134,8 @@ public final class InboundHttp2ToHttpPriorityAdapter extends InboundHttp2ToHttpA * @param http2Headers The target HTTP/2 headers */ private static void addHttpHeadersToHttp2Headers(HttpHeaders httpHeaders, final Http2Headers http2Headers) { - try { - for (Entry entry : httpHeaders) { - http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue())); - } - } catch (Exception ex) { - PlatformDependent.throwException(ex); + for (Entry entry : httpHeaders) { + 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 c898c829e5..fa444bc8ac 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 6e08fcac90..09781e2530 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,27 @@ */ 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.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 +42,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 +119,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 +134,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 +163,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 +170,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); + } + + protected 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 +241,48 @@ public class DefaultHeaders implements Headers { @Override public boolean isEmpty() { - return map.isEmpty(); + return head == head.after; } @Override public Set names() { - return unmodifiableSet(map.keySet()); + 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 +361,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 +436,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 +524,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 +560,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 +719,142 @@ 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; } - @Override @SuppressWarnings("unchecked") + @Override public boolean equals(Object o) { - if (!(o instanceof DefaultHeaders)) { + if (!(o instanceof Headers)) { return false; } - 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(); + protected 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. + */ + protected 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 +872,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().equals(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 +932,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 +994,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 633488cb90..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(false); - } - - 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; - } - } - - protected static class CharSequenceConverter implements ValueConverter { - - public static final CharSequenceConverter INSTANCE = new CharSequenceConverter(); - - @Override - public CharSequence convertObject(Object value) { - if (value instanceof CharSequence) { - return (CharSequence) value; - } - return value.toString(); - } - - @Override - public CharSequence convertInt(int value) { - return String.valueOf(value); - } - - @Override - public CharSequence convertLong(long value) { - return String.valueOf(value); - } - - @Override - public CharSequence convertDouble(double value) { - return String.valueOf(value); - } - - @Override - public CharSequence convertChar(char value) { - return String.valueOf(value); - } - - @Override - public CharSequence convertBoolean(boolean value) { - return String.valueOf(value); - } - - @Override - public CharSequence convertFloat(float value) { - return String.valueOf(value); - } - - @Override - public boolean convertToBoolean(CharSequence value) { - return Boolean.parseBoolean(value.toString()); - } - - @Override - public CharSequence convertByte(byte value) { - return String.valueOf(value); - } - - @Override - public byte convertToByte(CharSequence value) { - return Byte.valueOf(value.toString()); - } - - @Override - public char convertToChar(CharSequence value) { - if (value.length() == 0) { - throw new IllegalArgumentException("'value' is empty."); - } - return value.charAt(0); - } - - @Override - public CharSequence convertShort(short value) { - return String.valueOf(value); - } - - @Override - public short convertToShort(CharSequence value) { - return Short.valueOf(value.toString()); - } - - @Override - public int convertToInt(CharSequence value) { - return Integer.parseInt(value.toString()); - } - - @Override - public long convertToLong(CharSequence value) { - return Long.parseLong(value.toString()); - } - - @Override - public CharSequence convertTimeMillis(long value) { - return String.valueOf(value); - } - - @Override - public long convertToTimeMillis(CharSequence value) { - try { - return HeaderDateFormat.get().parse(value.toString()); - } catch (ParseException e) { - PlatformDependent.throwException(e); - } - return 0; - } - - @Override - public float convertToFloat(CharSequence value) { - return Float.valueOf(value.toString()); - } - - @Override - public double convertToDouble(CharSequence value) { - return Double.valueOf(value.toString()); - } - } -} diff --git a/codec/src/main/java/io/netty/handler/codec/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..7d811c077b --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/HeadersUtils.java @@ -0,0 +1,131 @@ +/* + * 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.Iterator; +import java.util.List; +import java.util.Map.Entry; + +/** + * Provides utility methods related to {@link Headers}. + */ +public final class HeadersUtils { + + private HeadersUtils() { + } + + /** + * {@link #getAll(CharSequence)} 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(CharSequence)} 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 #iterator()} that converts each {@link Entry}'s key and value to a {@link String}. + */ + public static Iterator> iteratorAsString( + Iterable> headers) { + return new StringIterator(headers.iterator()); + } + + 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 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(); + } + } +} 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 142f0aacc7..0000000000 --- a/codec/src/test/java/io/netty/handler/codec/DefaultTextHeadersTest.java +++ /dev/null @@ -1,358 +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 org.junit.Assert.assertEquals; -import static io.netty.util.internal.StringUtil.COMMA; -import static io.netty.util.internal.StringUtil.DOUBLE_QUOTE; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.Test; - -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/file/HttpStaticFileServerHandler.java b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java index 20885fcc13..9ae3059ee8 100644 --- a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java +++ b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java @@ -150,7 +150,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler } // Encode the cookie. - String cookieString = request.headers().getAndConvert(COOKIE); + String cookieString = request.headers().getAsString(COOKIE); if (cookieString != null) { Set cookies = ServerCookieDecoder.STRICT.decode(cookieString); if (!cookies.isEmpty()) { 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 41fc23b7c9..ab014726d7 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 @@ -195,7 +195,7 @@ public final class HttpUploadClient { // convert headers to list List> entries = new ArrayList>(headers.size()); - Iterator> iterConverted = headers.iteratorConverted(); + Iterator> iterConverted = headers.iteratorAsString(); while (iterConverted.hasNext()) { entries.add(iterConverted.next()); } @@ -270,7 +270,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/http/upload/HttpUploadServerHandler.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java index 40630a6b4f..235032f870 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java @@ -123,7 +123,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler cookies; - String value = request.headers().getAndConvert(HttpHeaderNames.COOKIE); + String value = request.headers().getAsString(HttpHeaderNames.COOKIE); if (value == null) { cookies = Collections.emptySet(); } else { @@ -335,7 +335,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler cookies; - String value = request.headers().getAndConvert(HttpHeaderNames.COOKIE); + String value = request.headers().getAsString(HttpHeaderNames.COOKIE); if (value == null) { cookies = Collections.emptySet(); } else { diff --git a/example/src/main/java/io/netty/example/http2/helloworld/server/Http2Server.java b/example/src/main/java/io/netty/example/http2/helloworld/server/Http2Server.java index b99b3755a3..43f45d37ab 100644 --- a/example/src/main/java/io/netty/example/http2/helloworld/server/Http2Server.java +++ b/example/src/main/java/io/netty/example/http2/helloworld/server/Http2Server.java @@ -19,6 +19,7 @@ package io.netty.example.http2.helloworld.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; diff --git a/example/src/main/java/io/netty/example/http2/tiles/Http2RequestHandler.java b/example/src/main/java/io/netty/example/http2/tiles/Http2RequestHandler.java index e40256c949..4f13e2ed4a 100644 --- a/example/src/main/java/io/netty/example/http2/tiles/Http2RequestHandler.java +++ b/example/src/main/java/io/netty/example/http2/tiles/Http2RequestHandler.java @@ -107,7 +107,7 @@ public class Http2RequestHandler 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 fc57991f9d..0f30f02276 100644 --- a/microbench/src/main/java/io/netty/microbench/headers/HeadersBenchmark.java +++ b/microbench/src/main/java/io/netty/microbench/headers/HeadersBenchmark.java @@ -82,6 +82,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) { @@ -108,6 +116,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) {