Headers Performance Boost and Interface Simplification
Motivation: A degradation in performance has been observed from the 4.0 branch as documented in https://github.com/netty/netty/issues/3962. Modifications: - Simplify Headers class hierarchy. - Restore the DefaultHeaders to be based upon DefaultHttpHeaders from 4.0. - Make various other modifications that are causing hot spots. Result: Performance is now on par with 4.0.
This commit is contained in:
parent
e6ca9882a1
commit
ba6ce5449e
@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.handler.codec.DefaultHeaders;
|
||||
import io.netty.handler.codec.ValueConverter;
|
||||
import io.netty.util.HashingStrategy;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
|
||||
import static io.netty.util.internal.StringUtil.COMMA;
|
||||
|
||||
/**
|
||||
* Will add multiple values for the same header as single header with a comma separated list of values.
|
||||
* <p>
|
||||
* Please refer to section <a href="https://tools.ietf.org/html/rfc7230#section-3.2.2">RFC 7230, 3.2.2</a>.
|
||||
*/
|
||||
public class CombinedHttpHeaders extends DefaultHttpHeaders {
|
||||
public CombinedHttpHeaders(boolean validate) {
|
||||
super(new CombinedHttpHeadersImpl(CASE_INSENSITIVE_HASHER, valueConverter(validate), nameValidator(validate)));
|
||||
}
|
||||
|
||||
private static final class CombinedHttpHeadersImpl extends DefaultHeaders<CharSequence> {
|
||||
/**
|
||||
* An estimate of the size of a header value.
|
||||
*/
|
||||
private static final int VALUE_LENGTH_ESTIMATE = 10;
|
||||
private CsvValueEscaper<Object> objectEscaper;
|
||||
private CsvValueEscaper<CharSequence> charSequenceEscaper;
|
||||
|
||||
private CsvValueEscaper<Object> objectEscaper() {
|
||||
if (objectEscaper == null) {
|
||||
objectEscaper = new CsvValueEscaper<Object>() {
|
||||
@Override
|
||||
public CharSequence escape(Object value) {
|
||||
return StringUtil.escapeCsv(valueConverter().convertObject(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
return objectEscaper;
|
||||
}
|
||||
|
||||
private CsvValueEscaper<CharSequence> charSequenceEscaper() {
|
||||
if (charSequenceEscaper == null) {
|
||||
charSequenceEscaper = new CsvValueEscaper<CharSequence>() {
|
||||
@Override
|
||||
public CharSequence escape(CharSequence value) {
|
||||
return StringUtil.escapeCsv(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
return charSequenceEscaper;
|
||||
}
|
||||
|
||||
public CombinedHttpHeadersImpl(HashingStrategy<CharSequence> nameHashingStrategy,
|
||||
ValueConverter<CharSequence> valueConverter,
|
||||
io.netty.handler.codec.DefaultHeaders.NameValidator<CharSequence> nameValidator) {
|
||||
super(nameHashingStrategy, valueConverter, nameValidator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedHttpHeadersImpl add(CharSequence name, CharSequence value) {
|
||||
return addEscapedValue(name, StringUtil.escapeCsv(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedHttpHeadersImpl add(CharSequence name, CharSequence... values) {
|
||||
return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedHttpHeadersImpl add(CharSequence name, Iterable<? extends CharSequence> values) {
|
||||
return addEscapedValue(name, commaSeparate(charSequenceEscaper(), values));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedHttpHeadersImpl addObject(CharSequence name, Iterable<?> values) {
|
||||
return addEscapedValue(name, commaSeparate(objectEscaper(), values));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedHttpHeadersImpl addObject(CharSequence name, Object... values) {
|
||||
return addEscapedValue(name, commaSeparate(objectEscaper(), values));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedHttpHeadersImpl set(CharSequence name, CharSequence... values) {
|
||||
super.set(name, commaSeparate(charSequenceEscaper(), values));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedHttpHeadersImpl set(CharSequence name, Iterable<? extends CharSequence> values) {
|
||||
super.set(name, commaSeparate(charSequenceEscaper(), values));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedHttpHeadersImpl setObject(CharSequence name, Object... values) {
|
||||
super.set(name, commaSeparate(objectEscaper(), values));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedHttpHeadersImpl setObject(CharSequence name, Iterable<?> values) {
|
||||
super.set(name, commaSeparate(objectEscaper(), values));
|
||||
return this;
|
||||
}
|
||||
|
||||
private CombinedHttpHeadersImpl addEscapedValue(CharSequence name, CharSequence escapedValue) {
|
||||
CharSequence currentValue = super.get(name);
|
||||
if (currentValue == null) {
|
||||
super.add(name, escapedValue);
|
||||
} else {
|
||||
super.set(name, commaSeparateEscapedValues(currentValue, escapedValue));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static <T> CharSequence commaSeparate(CsvValueEscaper<T> 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 <T> CharSequence commaSeparate(CsvValueEscaper<T> escaper, Iterable<? extends T> values) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
final StringBuilder sb = values instanceof Collection
|
||||
? new StringBuilder(((Collection) values).size() * VALUE_LENGTH_ESTIMATE) : new StringBuilder();
|
||||
Iterator<? extends T> 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 <T> The type that a concrete implementation handles
|
||||
*/
|
||||
private interface CsvValueEscaper<T> {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,14 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.handler.codec.CharSequenceValueConverter;
|
||||
import io.netty.handler.codec.DefaultHeaders;
|
||||
import io.netty.handler.codec.DefaultTextHeaders;
|
||||
import io.netty.handler.codec.DefaultTextHeaders.CharSequenceConverter;
|
||||
import io.netty.handler.codec.TextHeaders;
|
||||
import io.netty.handler.codec.DefaultHeaders.NameValidator;
|
||||
import io.netty.handler.codec.HeadersUtils;
|
||||
import io.netty.handler.codec.ValueConverter;
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.ByteProcessor;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
@ -30,56 +33,58 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
|
||||
import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link HttpHeaders}.
|
||||
*/
|
||||
public class DefaultHttpHeaders extends HttpHeaders {
|
||||
private static final int HIGHEST_INVALID_NAME_CHAR_MASK = ~63;
|
||||
private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15;
|
||||
private static final ByteProcessor HEADER_NAME_VALIDATOR = new ByteProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
validateChar((char) (value & 0xFF));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
static final NameValidator<CharSequence> HttpNameValidator = new NameValidator<CharSequence>() {
|
||||
@Override
|
||||
public void validateName(CharSequence name) {
|
||||
if (name instanceof AsciiString) {
|
||||
try {
|
||||
((AsciiString) name).forEachByte(HEADER_NAME_VALIDATOR);
|
||||
} catch (Exception e) {
|
||||
PlatformDependent.throwException(e);
|
||||
}
|
||||
} else {
|
||||
checkNotNull(name, "name");
|
||||
// Go through each character in the name
|
||||
for (int index = 0; index < name.length(); ++index) {
|
||||
validateChar(name.charAt(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A look-up table used for checking if a character in a header name is prohibited.
|
||||
*/
|
||||
private static final byte[] LOOKUP_TABLE = new byte[~HIGHEST_INVALID_NAME_CHAR_MASK + 1];
|
||||
|
||||
static {
|
||||
LOOKUP_TABLE['\t'] = -1;
|
||||
LOOKUP_TABLE['\n'] = -1;
|
||||
LOOKUP_TABLE[0x0b] = -1;
|
||||
LOOKUP_TABLE['\f'] = -1;
|
||||
LOOKUP_TABLE[' '] = -1;
|
||||
LOOKUP_TABLE[','] = -1;
|
||||
LOOKUP_TABLE[':'] = -1;
|
||||
LOOKUP_TABLE[';'] = -1;
|
||||
LOOKUP_TABLE['='] = -1;
|
||||
}
|
||||
|
||||
private final TextHeaders headers;
|
||||
private final DefaultHeaders<CharSequence> headers;
|
||||
|
||||
public DefaultHttpHeaders() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public DefaultHttpHeaders(boolean validate) {
|
||||
this(validate, false);
|
||||
this(validate, nameValidator(validate));
|
||||
}
|
||||
|
||||
protected DefaultHttpHeaders(boolean validate, boolean singleHeaderFields) {
|
||||
this(true, validate ? HeaderNameValidator.INSTANCE : DefaultTextHeaders.NO_NAME_VALIDATOR, singleHeaderFields);
|
||||
protected DefaultHttpHeaders(boolean validate, NameValidator<CharSequence> nameValidator) {
|
||||
this(new DefaultHeaders<CharSequence>(CASE_INSENSITIVE_HASHER, valueConverter(validate), nameValidator));
|
||||
}
|
||||
|
||||
protected DefaultHttpHeaders(boolean validate,
|
||||
DefaultHeaders.NameValidator<CharSequence> nameValidator,
|
||||
boolean singleHeaderFields) {
|
||||
headers = new DefaultTextHeaders(
|
||||
new TreeMap<CharSequence, Object>(AsciiString.CHARSEQUENCE_CASE_INSENSITIVE_ORDER),
|
||||
nameValidator,
|
||||
validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE,
|
||||
singleHeaderFields);
|
||||
protected DefaultHttpHeaders(DefaultHeaders<CharSequence> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,24 +107,28 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpHeaders add(String name, Object value) {
|
||||
headers.addObject(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpHeaders add(CharSequence name, Object value) {
|
||||
headers.addObject(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpHeaders add(String name, Iterable<?> values) {
|
||||
headers.addObject(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpHeaders add(CharSequence name, Iterable<?> values) {
|
||||
headers.addObject(name, values);
|
||||
@ -138,36 +147,42 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpHeaders remove(String name) {
|
||||
headers.remove(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpHeaders remove(CharSequence name) {
|
||||
headers.remove(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpHeaders set(String name, Object value) {
|
||||
headers.setObject(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpHeaders set(CharSequence name, Object value) {
|
||||
headers.setObject(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpHeaders set(String name, Iterable<?> values) {
|
||||
headers.setObject(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public HttpHeaders set(CharSequence name, Iterable<?> values) {
|
||||
headers.setObject(name, values);
|
||||
@ -192,14 +207,16 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public String get(String name) {
|
||||
return headers.getAndConvert(name);
|
||||
return get((CharSequence) name);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public String get(CharSequence name) {
|
||||
return headers.getAndConvert(name);
|
||||
return HeadersUtils.getAsString(headers, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -232,16 +249,19 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
||||
return headers.getTimeMillis(name, defaultValue);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public List<String> getAll(String name) {
|
||||
return headers.getAllAndConvert(name);
|
||||
return getAll((CharSequence) name);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public List<String> getAll(CharSequence name) {
|
||||
return headers.getAllAndConvert(name);
|
||||
return HeadersUtils.getAllAsString(headers, name);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public List<Entry<String, String>> entries() {
|
||||
if (isEmpty()) {
|
||||
@ -255,30 +275,22 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
||||
return entriesConverted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #iteratorCharSequence()}.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
@Override
|
||||
public Iterator<Map.Entry<String, String>> iterator() {
|
||||
return headers.iteratorConverted();
|
||||
return HeadersUtils.iteratorAsString(headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Future major releases will have this be the default iterator.
|
||||
* Get an iterator to traverse over the underlying name/value pairs.
|
||||
* <p>
|
||||
* This iterator should be preferred over {@link #iterator()} if {@link AsciiString} objects are used.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
@Override
|
||||
public Iterator<Entry<CharSequence, CharSequence>> iteratorCharSequence() {
|
||||
return headers.iterator();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean contains(String name) {
|
||||
return headers.contains(name);
|
||||
return contains((CharSequence) name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -298,88 +310,90 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
||||
|
||||
@Override
|
||||
public boolean contains(String name, String value, boolean ignoreCase) {
|
||||
return headers.contains(name, value, ignoreCase);
|
||||
return contains((CharSequence) name, (CharSequence) value, ignoreCase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
|
||||
return headers.contains(name, value, ignoreCase);
|
||||
return headers.contains(name, value, ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public Set<String> names() {
|
||||
return HeadersUtils.namesAsString(headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> names() {
|
||||
return headers.namesAndConvert(String.CASE_INSENSITIVE_ORDER);
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof DefaultHttpHeaders)) {
|
||||
return false;
|
||||
}
|
||||
return headers.equals(((DefaultHttpHeaders) o).headers, CASE_SENSITIVE_HASHER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return headers.size();
|
||||
return headers.hashCode(CASE_SENSITIVE_HASHER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof DefaultHttpHeaders)) {
|
||||
return false;
|
||||
}
|
||||
DefaultHttpHeaders headers = (DefaultHttpHeaders) other;
|
||||
return DefaultHeaders.comparatorEquals(this.headers, headers.headers,
|
||||
AsciiString.CHARSEQUENCE_CASE_SENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
static final class HeaderNameValidator implements DefaultHeaders.NameValidator<CharSequence> {
|
||||
|
||||
public static final HeaderNameValidator INSTANCE = new HeaderNameValidator();
|
||||
|
||||
private HeaderNameValidator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(CharSequence name) {
|
||||
// Go through each character in the name
|
||||
for (int index = 0; index < name.length(); index++) {
|
||||
char character = name.charAt(index);
|
||||
|
||||
// Check to see if the character is not an ASCII character
|
||||
if (character > 127) {
|
||||
throw new IllegalArgumentException("a header name cannot contain non-ASCII characters: " + name);
|
||||
}
|
||||
|
||||
// Check for prohibited characters.
|
||||
if ((character & HIGHEST_INVALID_NAME_CHAR_MASK) == 0 && LOOKUP_TABLE[character] != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
|
||||
name);
|
||||
}
|
||||
private static void validateChar(char character) {
|
||||
switch (character) {
|
||||
case '\t':
|
||||
case '\n':
|
||||
case 0x0b:
|
||||
case '\f':
|
||||
case '\r':
|
||||
case ' ':
|
||||
case ',':
|
||||
case ':':
|
||||
case ';':
|
||||
case '=':
|
||||
throw new IllegalArgumentException(
|
||||
"a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " +
|
||||
character);
|
||||
default:
|
||||
// Check to see if the character is not an ASCII character, or invalid
|
||||
if (character > 127) {
|
||||
throw new IllegalArgumentException("a header name cannot contain non-ASCII character: " +
|
||||
character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class HeaderValueConverter extends CharSequenceConverter {
|
||||
static ValueConverter<CharSequence> valueConverter(boolean validate) {
|
||||
return validate ? HeaderValueConverterAndValidator.INSTANCE : HeaderValueConverter.INSTANCE;
|
||||
}
|
||||
|
||||
public static final HeaderValueConverter INSTANCE = new HeaderValueConverter();
|
||||
@SuppressWarnings("unchecked")
|
||||
static NameValidator<CharSequence> nameValidator(boolean validate) {
|
||||
return validate ? HttpNameValidator : NameValidator.NOT_NULL;
|
||||
}
|
||||
|
||||
private static class HeaderValueConverter extends CharSequenceValueConverter {
|
||||
static final HeaderValueConverter INSTANCE = new HeaderValueConverter();
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
checkNotNull(value, "value");
|
||||
CharSequence seq;
|
||||
if (value instanceof CharSequence) {
|
||||
seq = (CharSequence) value;
|
||||
} else if (value instanceof Number) {
|
||||
seq = value.toString();
|
||||
} else if (value instanceof Date) {
|
||||
seq = HttpHeaderDateFormat.get().format((Date) value);
|
||||
} else if (value instanceof Calendar) {
|
||||
seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
|
||||
} else {
|
||||
seq = value.toString();
|
||||
return (CharSequence) value;
|
||||
}
|
||||
return seq;
|
||||
if (value instanceof Number) {
|
||||
return value.toString();
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return HttpHeaderDateFormat.get().format((Date) value);
|
||||
}
|
||||
if (value instanceof Calendar) {
|
||||
return HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HeaderValueConverterAndValidator extends HeaderValueConverter {
|
||||
|
||||
public static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
|
||||
static final HeaderValueConverterAndValidator INSTANCE = new HeaderValueConverterAndValidator();
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -50,12 +50,15 @@ public class DefaultHttpResponse extends DefaultHttpMessage implements HttpRespo
|
||||
* @param version the HTTP version of this response
|
||||
* @param status the getStatus of this response
|
||||
* @param validateHeaders validate the header names and values when adding them to the {@link HttpHeaders}
|
||||
* @param singleHeaderFields determines if HTTP headers with multiple values should be added as a single
|
||||
* field or as multiple header fields.
|
||||
* @param singleFieldHeaders {@code true} to check and enforce that headers with the same name are appended
|
||||
* to the same entry and comma separated.
|
||||
* See <a href="https://tools.ietf.org/html/rfc7230#section-3.2.2">RFC 7230, 3.2.2</a>.
|
||||
* {@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");
|
||||
}
|
||||
|
@ -17,8 +17,7 @@ package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.DefaultHeaders;
|
||||
import io.netty.handler.codec.DefaultTextHeaders;
|
||||
import io.netty.handler.codec.DefaultHeaders.NameValidator;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
@ -27,7 +26,6 @@ import java.util.Map.Entry;
|
||||
* The default {@link LastHttpContent} implementation.
|
||||
*/
|
||||
public class DefaultLastHttpContent extends DefaultHttpContent implements LastHttpContent {
|
||||
|
||||
private final HttpHeaders trailingHeaders;
|
||||
private final boolean validateHeaders;
|
||||
|
||||
@ -109,26 +107,21 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
|
||||
}
|
||||
|
||||
private static final class TrailingHttpHeaders extends DefaultHttpHeaders {
|
||||
private static final class TrailingHttpHeadersNameValidator implements
|
||||
DefaultHeaders.NameValidator<CharSequence> {
|
||||
|
||||
private static final TrailingHttpHeadersNameValidator INSTANCE = new TrailingHttpHeadersNameValidator();
|
||||
|
||||
private static final NameValidator<CharSequence> TrailerNameValidator = new NameValidator<CharSequence>() {
|
||||
@Override
|
||||
public void validate(CharSequence name) {
|
||||
HeaderNameValidator.INSTANCE.validate(name);
|
||||
if (HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(name)
|
||||
|| HttpHeaderNames.TRANSFER_ENCODING.equalsIgnoreCase(name)
|
||||
|| HttpHeaderNames.TRAILER.equalsIgnoreCase(name)) {
|
||||
public void validateName(CharSequence name) {
|
||||
DefaultHttpHeaders.HttpNameValidator.validateName(name);
|
||||
if (HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(name)
|
||||
|| HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(name)
|
||||
|| HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(name)) {
|
||||
throw new IllegalArgumentException("prohibited trailing header: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
TrailingHttpHeaders(boolean validate) {
|
||||
super(validate,
|
||||
validate ? TrailingHttpHeadersNameValidator.INSTANCE : DefaultTextHeaders.NO_NAME_VALIDATOR,
|
||||
false);
|
||||
super(validate, validate ? TrailerNameValidator : NameValidator.NOT_NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
public class EmptyHttpHeaders extends HttpHeaders {
|
||||
static final Iterator<Entry<CharSequence, CharSequence>> EMPTY_CHARS_ITERATOR =
|
||||
Collections.<Entry<CharSequence, CharSequence>>emptyList().iterator();
|
||||
|
||||
public static final EmptyHttpHeaders INSTANCE = new EmptyHttpHeaders();
|
||||
|
||||
protected EmptyHttpHeaders() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getInt(CharSequence name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(CharSequence name, int defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Short getShort(CharSequence name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(CharSequence name, short defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTimeMillis(CharSequence name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeMillis(CharSequence name, long defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAll(String name) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Entry<String, String>> entries() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> names() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders add(String name, Object value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders add(String name, Iterable<?> values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders addInt(CharSequence name, int value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders addShort(CharSequence name, short value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(String name, Object value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(String name, Iterable<?> values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders setInt(CharSequence name, int value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders setShort(CharSequence name, short value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders remove(String name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders clear() {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<String, String>> iterator() {
|
||||
return entries().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<CharSequence, CharSequence>> iteratorCharSequence() {
|
||||
return EMPTY_CHARS_ITERATOR;
|
||||
}
|
||||
}
|
@ -220,7 +220,7 @@ public class HttpClientUpgradeHandler extends HttpObjectAggregator implements Ch
|
||||
throw new IllegalStateException(
|
||||
"Switching Protocols response missing UPGRADE header");
|
||||
}
|
||||
if (!AsciiString.equalsIgnoreCase(upgradeCodec.protocol(), upgradeHeader)) {
|
||||
if (!AsciiString.contentEqualsIgnoreCase(upgradeCodec.protocol(), upgradeHeader)) {
|
||||
throw new IllegalStateException(
|
||||
"Switching Protocols response with unexpected UPGRADE protocol: "
|
||||
+ upgradeHeader);
|
||||
|
@ -96,7 +96,7 @@ public class HttpContentCompressor extends HttpContentEncoder {
|
||||
protected Result beginEncode(HttpResponse headers, String acceptEncoding) throws Exception {
|
||||
String contentEncoding = headers.headers().get(HttpHeaderNames.CONTENT_ENCODING);
|
||||
if (contentEncoding != null &&
|
||||
!HttpHeaderValues.IDENTITY.equalsIgnoreCase(contentEncoding)) {
|
||||
!HttpHeaderValues.IDENTITY.contentEqualsIgnoreCase(contentEncoding)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,10 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE;
|
||||
import static io.netty.handler.codec.http.HttpHeaderValues.GZIP;
|
||||
import static io.netty.handler.codec.http.HttpHeaderValues.X_DEFLATE;
|
||||
import static io.netty.handler.codec.http.HttpHeaderValues.X_GZIP;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.compression.ZlibCodecFactory;
|
||||
import io.netty.handler.codec.compression.ZlibWrapper;
|
||||
@ -47,16 +51,13 @@ public class HttpContentDecompressor extends HttpContentDecoder {
|
||||
|
||||
@Override
|
||||
protected EmbeddedChannel newContentDecoder(String contentEncoding) throws Exception {
|
||||
if ("gzip".equalsIgnoreCase(contentEncoding) || "x-gzip".equalsIgnoreCase(contentEncoding)) {
|
||||
if (GZIP.contentEqualsIgnoreCase(contentEncoding) ||
|
||||
X_GZIP.contentEqualsIgnoreCase(contentEncoding)) {
|
||||
return new EmbeddedChannel(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
|
||||
}
|
||||
if ("deflate".equalsIgnoreCase(contentEncoding) || "x-deflate".equalsIgnoreCase(contentEncoding)) {
|
||||
ZlibWrapper wrapper;
|
||||
if (strict) {
|
||||
wrapper = ZlibWrapper.ZLIB;
|
||||
} else {
|
||||
wrapper = ZlibWrapper.ZLIB_OR_NONE;
|
||||
}
|
||||
if (DEFLATE.contentEqualsIgnoreCase(contentEncoding) ||
|
||||
X_DEFLATE.contentEqualsIgnoreCase(contentEncoding)) {
|
||||
final ZlibWrapper wrapper = strict ? ZlibWrapper.ZLIB : ZlibWrapper.ZLIB_OR_NONE;
|
||||
// To be strict, 'deflate' means ZLIB, but some servers were not implemented correctly.
|
||||
return new EmbeddedChannel(ZlibCodecFactory.newZlibDecoder(wrapper));
|
||||
}
|
||||
|
@ -32,14 +32,14 @@ public final class HttpHeaderUtil {
|
||||
*/
|
||||
public static boolean isKeepAlive(HttpMessage message) {
|
||||
CharSequence connection = message.headers().get(HttpHeaderNames.CONNECTION);
|
||||
if (connection != null && HttpHeaderValues.CLOSE.equalsIgnoreCase(connection)) {
|
||||
if (connection != null && HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(connection)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (message.protocolVersion().isKeepAliveDefault()) {
|
||||
return !HttpHeaderValues.CLOSE.equalsIgnoreCase(connection);
|
||||
return !HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(connection);
|
||||
} else {
|
||||
return HttpHeaderValues.KEEP_ALIVE.equalsIgnoreCase(connection);
|
||||
return HttpHeaderValues.KEEP_ALIVE.contentEqualsIgnoreCase(connection);
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +193,7 @@ public final class HttpHeaderUtil {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
if (HttpHeaderValues.CONTINUE.equalsIgnoreCase(value)) {
|
||||
if (HttpHeaderValues.CONTINUE.contentEqualsIgnoreCase(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -231,7 +231,6 @@ public final class HttpHeaderUtil {
|
||||
m.headers().add(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
|
||||
m.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
|
||||
} else {
|
||||
// Make a copy to be able to modify values while iterating
|
||||
List<String> encodings = m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING);
|
||||
if (encodings.isEmpty()) {
|
||||
return;
|
||||
@ -240,7 +239,7 @@ public final class HttpHeaderUtil {
|
||||
Iterator<CharSequence> valuesIt = values.iterator();
|
||||
while (valuesIt.hasNext()) {
|
||||
CharSequence value = valuesIt.next();
|
||||
if (HttpHeaderValues.CHUNKED.equalsIgnoreCase(value)) {
|
||||
if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(value)) {
|
||||
valuesIt.remove();
|
||||
}
|
||||
}
|
||||
|
@ -17,11 +17,11 @@ package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.handler.codec.Headers;
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -36,135 +36,11 @@ import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
* commonly used utility methods that accesses an {@link HttpMessage}.
|
||||
*/
|
||||
public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>> {
|
||||
static final Iterator<Entry<CharSequence, CharSequence>> EMPTY_CHARS_ITERATOR =
|
||||
Collections.<Entry<CharSequence, CharSequence>>emptyList().iterator();
|
||||
|
||||
public static final HttpHeaders EMPTY_HEADERS = new HttpHeaders() {
|
||||
@Override
|
||||
public String get(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getInt(CharSequence name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(CharSequence name, int defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Short getShort(CharSequence name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(CharSequence name, short defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTimeMillis(CharSequence name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeMillis(CharSequence name, long defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAll(String name) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Entry<String, String>> entries() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> names() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders add(String name, Object value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders add(String name, Iterable<?> values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders addInt(CharSequence name, int value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders addShort(CharSequence name, short value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(String name, Object value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(String name, Iterable<?> values) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders setInt(CharSequence name, int value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders setShort(CharSequence name, short value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders remove(String name) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders clear() {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<String, String>> iterator() {
|
||||
return entries().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<CharSequence, CharSequence>> iteratorCharSequence() {
|
||||
return EMPTY_CHARS_ITERATOR;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @deprecated Use {@link EmptyHttpHeaders#INSTANCE}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final HttpHeaders EMPTY_HEADERS = EmptyHttpHeaders.INSTANCE;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link HttpHeaderNames} instead.
|
||||
@ -1261,7 +1137,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean equalsIgnoreCase(CharSequence name1, CharSequence name2) {
|
||||
return AsciiString.equalsIgnoreCase(name1, name2);
|
||||
return AsciiString.contentEqualsIgnoreCase(name1, name2);
|
||||
}
|
||||
|
||||
static void encode(HttpHeaders headers, ByteBuf buf) throws Exception {
|
||||
@ -1305,28 +1181,36 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
protected HttpHeaders() { }
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #get(CharSequence)}
|
||||
* @see {@link #get(CharSequence)}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract String get(String name);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getAsString(CharSequence)}
|
||||
* <p>
|
||||
* Returns the value of a header with the specified name. If there are
|
||||
* more than one values for the specified name, the first value is returned.
|
||||
*
|
||||
* @param name The name of the header to search
|
||||
* @return The first header value or {@code null} if there is no such header
|
||||
*/
|
||||
@Deprecated
|
||||
public String get(CharSequence name) {
|
||||
return get(name.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Future releases will use {@link CharSequence} instead of {@link String}.
|
||||
* <p>
|
||||
* Returns the value of a header with the specified name. If there are
|
||||
* more than one values for the specified name, the first value is returned.
|
||||
*
|
||||
* @param name The name of the header to search
|
||||
* @return The first header value or {@code defaultValue} if there is no such header
|
||||
*/
|
||||
@Deprecated
|
||||
public String get(CharSequence name, String defaultValue) {
|
||||
String value = get(name);
|
||||
if (value == null) {
|
||||
@ -1401,39 +1285,55 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
/**
|
||||
* @see {@link #getAll(CharSequence)}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract List<String> getAll(String name);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getAllAsString(CharSequence)}
|
||||
* <p>
|
||||
* Returns the values of headers with the specified name
|
||||
*
|
||||
* @param name The name of the headers to search
|
||||
* @return A {@link List} of header values which will be empty if no values
|
||||
* are found
|
||||
*/
|
||||
@Deprecated
|
||||
public List<String> getAll(CharSequence name) {
|
||||
return getAll(name.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #iteratorCharSequence()}
|
||||
* <p>
|
||||
* Returns a new {@link List} that contains all headers in this object. Note that modifying the
|
||||
* returned {@link List} will not affect the state of this object. If you intend to enumerate over the header
|
||||
* entries only, use {@link #iterator()} instead, which has much less overhead.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract List<Map.Entry<String, String>> entries();
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #contains(CharSequence)}
|
||||
* <p>
|
||||
* @see {@link #contains(CharSequence)}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract boolean contains(String name);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #iteratorCharSequence()}.
|
||||
* @deprecated It is preferred to use {@link #iteratorCharSequence()} unless you need {@link String}.
|
||||
* If {@link String} is required then use {@link #iteratorAsString()}.
|
||||
* <p>
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public abstract Iterator<Entry<String, String>> iterator();
|
||||
|
||||
/**
|
||||
* @deprecated In future releases this method will be renamed to {@code iterator()}.
|
||||
* @return Iterator over the name/value header pairs.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract Iterator<Entry<CharSequence, CharSequence>> iteratorCharSequence();
|
||||
|
||||
@ -1458,18 +1358,24 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
public abstract int size();
|
||||
|
||||
/**
|
||||
* @deprecated Future releases will return a {@link Set} of type {@link CharSequence}.
|
||||
* <p>
|
||||
* Returns a new {@link Set} that contains the names of all headers in this object. Note that modifying the
|
||||
* returned {@link Set} will not affect the state of this object. If you intend to enumerate over the header
|
||||
* entries only, use {@link #iterator()} instead, which has much less overhead.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract Set<String> names();
|
||||
|
||||
/**
|
||||
* @see {@link #add(CharSequence, Object)}
|
||||
* @deprecated Use {@link #add(CharSequence, Object)}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract HttpHeaders add(String name, Object value);
|
||||
|
||||
/**
|
||||
* @deprecated Future releases will have a {@code addObject} method.
|
||||
* <p>
|
||||
* Adds a new header with the specified name and value.
|
||||
*
|
||||
* If the specified value is not a {@link String}, it is converted
|
||||
@ -1482,16 +1388,20 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
*
|
||||
* @return {@code this}
|
||||
*/
|
||||
@Deprecated
|
||||
public HttpHeaders add(CharSequence name, Object value) {
|
||||
return add(name.toString(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #add(CharSequence, Iterable)}
|
||||
* @deprecated Use {@link #add(CharSequence, Iterable)}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract HttpHeaders add(String name, Iterable<?> values);
|
||||
|
||||
/**
|
||||
* @deprecated Future release will have an {@code addObject(...)}.
|
||||
* <p>
|
||||
* Adds a new header with the specified name and values.
|
||||
*
|
||||
* This getMethod can be represented approximately as the following code:
|
||||
@ -1508,6 +1418,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
* @param values The values of the headers being set
|
||||
* @return {@code this}
|
||||
*/
|
||||
@Deprecated
|
||||
public HttpHeaders add(CharSequence name, Iterable<?> values) {
|
||||
return add(name.toString(), values);
|
||||
}
|
||||
@ -1544,11 +1455,14 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
public abstract HttpHeaders addShort(CharSequence name, short value);
|
||||
|
||||
/**
|
||||
* @see {@link #set(CharSequence, Object)}
|
||||
* @deprecated Use {@link #set(CharSequence, Object)}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract HttpHeaders set(String name, Object value);
|
||||
|
||||
/**
|
||||
* @deprecated Future release will have a {@code setObject(...)}.
|
||||
* <p>
|
||||
* Sets a header with the specified name and value.
|
||||
*
|
||||
* If there is an existing header with the same name, it is removed.
|
||||
@ -1561,16 +1475,20 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
* @param value The value of the header being set
|
||||
* @return {@code this}
|
||||
*/
|
||||
@Deprecated
|
||||
public HttpHeaders set(CharSequence name, Object value) {
|
||||
return set(name.toString(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link #set(CharSequence, Iterable)}
|
||||
* @deprecated {@link #set(CharSequence, Iterable)}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract HttpHeaders set(String name, Iterable<?> values);
|
||||
|
||||
/**
|
||||
* @deprecated Future release will have a {@code setObject(...)}.
|
||||
* <p>
|
||||
* Sets a header with the specified name and values.
|
||||
*
|
||||
* If there is an existing header with the same name, it is removed.
|
||||
@ -1589,6 +1507,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
* @param values The values of the headers being set
|
||||
* @return {@code this}
|
||||
*/
|
||||
@Deprecated
|
||||
public HttpHeaders set(CharSequence name, Iterable<?> values) {
|
||||
return set(name.toString(), values);
|
||||
}
|
||||
@ -1649,16 +1568,20 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
public abstract HttpHeaders setShort(CharSequence name, short value);
|
||||
|
||||
/**
|
||||
* @see {@link #remove(CharSequence)}
|
||||
* @deprecated {@link #remove(CharSequence)}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract HttpHeaders remove(String name);
|
||||
|
||||
/**
|
||||
* @deprecated Future releases the signature will change.
|
||||
* <p>
|
||||
* Removes the header with the specified name.
|
||||
*
|
||||
* @param name The name of the header to remove
|
||||
* @return {@code this}
|
||||
*/
|
||||
@Deprecated
|
||||
public HttpHeaders remove(CharSequence name) {
|
||||
return remove(name.toString());
|
||||
}
|
||||
@ -1694,12 +1617,38 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a header with the name and value exists.
|
||||
*
|
||||
* @param name the headername
|
||||
* @param value the value
|
||||
* @param ignoreCase {@code true} if case should be ignored
|
||||
* @return contains {@code true} if it contains it {@code false} otherwise
|
||||
* {@link Headers#get(Object)} and convert the result to a {@link String}.
|
||||
* @param name the name of the header to retrieve
|
||||
* @return the first header value if the header is found. {@code null} if there's no such header.
|
||||
*/
|
||||
public final String getAsString(CharSequence name) {
|
||||
return get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Headers#getAll(Object)} and convert each element of {@link List} to a {@link String}.
|
||||
* @param name the name of the header to retrieve
|
||||
* @return a {@link List} of header values or an empty {@link List} if no values are found.
|
||||
*/
|
||||
public final List<String> getAllAsString(CharSequence name) {
|
||||
return getAll(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Iterator} that converts each {@link Entry}'s key and value to a {@link String}.
|
||||
*/
|
||||
public final Iterator<Entry<String, String>> iteratorAsString() {
|
||||
return iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a header with the {@code name} and {@code value} exists, {@code false} otherwise.
|
||||
* <p>
|
||||
* If {@code ignoreCase} is {@code true} then a case insensitive compare is done on the value.
|
||||
* @param name the name of the header to find
|
||||
* @param value the value of the header to find
|
||||
* @param ignoreCase {@code true} then a case insensitive compare is run to compare values.
|
||||
* otherwise a case sensitive compare is run to compare values.
|
||||
*/
|
||||
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
|
||||
return contains(name.toString(), value.toString(), ignoreCase);
|
||||
|
@ -21,6 +21,8 @@ import io.netty.util.CharsetUtil;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
/**
|
||||
* The request getMethod of HTTP or its derived protocols, such as
|
||||
* <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
|
||||
@ -86,8 +88,7 @@ public class HttpMethod implements Comparable<HttpMethod> {
|
||||
*/
|
||||
public static final HttpMethod CONNECT = new HttpMethod("CONNECT", true);
|
||||
|
||||
private static final Map<String, HttpMethod> methodMap =
|
||||
new HashMap<String, HttpMethod>();
|
||||
private static final Map<String, HttpMethod> methodMap = new HashMap<String, HttpMethod>();
|
||||
|
||||
static {
|
||||
methodMap.put(OPTIONS.toString(), OPTIONS);
|
||||
@ -107,21 +108,8 @@ public class HttpMethod implements Comparable<HttpMethod> {
|
||||
* will be returned. Otherwise, a new instance will be returned.
|
||||
*/
|
||||
public static HttpMethod valueOf(String name) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("name");
|
||||
}
|
||||
|
||||
name = name.trim();
|
||||
if (name.isEmpty()) {
|
||||
throw new IllegalArgumentException("empty name");
|
||||
}
|
||||
|
||||
HttpMethod result = methodMap.get(name);
|
||||
if (result != null) {
|
||||
return result;
|
||||
} else {
|
||||
return new HttpMethod(name);
|
||||
}
|
||||
return result != null ? result : new HttpMethod(name);
|
||||
}
|
||||
|
||||
private final String name;
|
||||
@ -143,7 +131,7 @@ public class HttpMethod implements Comparable<HttpMethod> {
|
||||
throw new NullPointerException("name");
|
||||
}
|
||||
|
||||
name = name.trim();
|
||||
name = checkNotNull(name, "name").trim();
|
||||
if (name.isEmpty()) {
|
||||
throw new IllegalArgumentException("empty name");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -689,13 +689,13 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
|
||||
return null;
|
||||
}
|
||||
String[] contents = splitMultipartHeader(newline);
|
||||
if (HttpHeaderNames.CONTENT_DISPOSITION.equalsIgnoreCase(contents[0])) {
|
||||
if (HttpHeaderNames.CONTENT_DISPOSITION.contentEqualsIgnoreCase(contents[0])) {
|
||||
boolean checkSecondArg;
|
||||
if (currentStatus == MultiPartStatus.DISPOSITION) {
|
||||
checkSecondArg = HttpHeaderValues.FORM_DATA.equalsIgnoreCase(contents[1]);
|
||||
checkSecondArg = HttpHeaderValues.FORM_DATA.contentEqualsIgnoreCase(contents[1]);
|
||||
} else {
|
||||
checkSecondArg = HttpHeaderValues.ATTACHMENT.equalsIgnoreCase(contents[1])
|
||||
|| HttpHeaderValues.FILE.equalsIgnoreCase(contents[1]);
|
||||
checkSecondArg = HttpHeaderValues.ATTACHMENT.contentEqualsIgnoreCase(contents[1])
|
||||
|| HttpHeaderValues.FILE.contentEqualsIgnoreCase(contents[1]);
|
||||
}
|
||||
if (checkSecondArg) {
|
||||
// read next values and store them in the map as Attribute
|
||||
@ -723,7 +723,7 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
|
||||
currentFieldAttributes.put(attribute.getName(), attribute);
|
||||
}
|
||||
}
|
||||
} else if (HttpHeaderNames.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(contents[0])) {
|
||||
} else if (HttpHeaderNames.CONTENT_TRANSFER_ENCODING.contentEqualsIgnoreCase(contents[0])) {
|
||||
Attribute attribute;
|
||||
try {
|
||||
attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_TRANSFER_ENCODING.toString(),
|
||||
@ -733,8 +733,9 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ErrorDataDecoderException(e);
|
||||
}
|
||||
|
||||
currentFieldAttributes.put(HttpHeaderNames.CONTENT_TRANSFER_ENCODING, attribute);
|
||||
} else if (HttpHeaderNames.CONTENT_LENGTH.equalsIgnoreCase(contents[0])) {
|
||||
} else if (HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(contents[0])) {
|
||||
Attribute attribute;
|
||||
try {
|
||||
attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_LENGTH.toString(),
|
||||
@ -744,10 +745,11 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ErrorDataDecoderException(e);
|
||||
}
|
||||
|
||||
currentFieldAttributes.put(HttpHeaderNames.CONTENT_LENGTH, attribute);
|
||||
} else if (HttpHeaderNames.CONTENT_TYPE.equalsIgnoreCase(contents[0])) {
|
||||
} else if (HttpHeaderNames.CONTENT_TYPE.contentEqualsIgnoreCase(contents[0])) {
|
||||
// Take care of possible "multipart/mixed"
|
||||
if (HttpHeaderValues.MULTIPART_MIXED.equalsIgnoreCase(contents[1])) {
|
||||
if (HttpHeaderValues.MULTIPART_MIXED.contentEqualsIgnoreCase(contents[1])) {
|
||||
if (currentStatus == MultiPartStatus.DISPOSITION) {
|
||||
String values = StringUtil.substringAfter(contents[2], '=');
|
||||
multipartMixedBoundary = "--" + values;
|
||||
|
@ -753,7 +753,7 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
|
||||
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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -107,8 +107,8 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
protected FullHttpResponse newHandshakeResponse(FullHttpRequest req, HttpHeaders headers) {
|
||||
|
||||
// Serve the WebSocket handshake request.
|
||||
if (!HttpHeaderValues.UPGRADE.equalsIgnoreCase(req.headers().get(HttpHeaderNames.CONNECTION))
|
||||
|| !HttpHeaderValues.WEBSOCKET.equalsIgnoreCase(req.headers().get(HttpHeaderNames.UPGRADE))) {
|
||||
if (!HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(req.headers().get(HttpHeaderNames.CONNECTION))
|
||||
|| !HttpHeaderValues.WEBSOCKET.contentEqualsIgnoreCase(req.headers().get(HttpHeaderNames.UPGRADE))) {
|
||||
throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
|
||||
}
|
||||
|
||||
@ -131,6 +131,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
// New handshake getMethod with a challenge:
|
||||
res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_ORIGIN, req.headers().get(HttpHeaderNames.ORIGIN));
|
||||
res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_LOCATION, uri());
|
||||
|
||||
String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);
|
||||
if (subprotocols != null) {
|
||||
String selectedSubprotocol = selectSubprotocol(subprotocols);
|
||||
@ -160,6 +161,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
// Old Hixie 75 handshake getMethod with no challenge:
|
||||
res.headers().add(HttpHeaderNames.WEBSOCKET_ORIGIN, req.headers().get(HttpHeaderNames.ORIGIN));
|
||||
res.headers().add(HttpHeaderNames.WEBSOCKET_LOCATION, uri());
|
||||
|
||||
String protocol = req.headers().get(HttpHeaderNames.WEBSOCKET_PROTOCOL);
|
||||
if (protocol != null) {
|
||||
res.headers().add(HttpHeaderNames.WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
|
||||
|
@ -142,6 +142,7 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
|
||||
res.headers().add(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET);
|
||||
res.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE);
|
||||
res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept);
|
||||
|
||||
String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);
|
||||
if (subprotocols != null) {
|
||||
String selectedSubprotocol = selectSubprotocol(subprotocols);
|
||||
|
@ -141,6 +141,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
|
||||
res.headers().add(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET);
|
||||
res.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE);
|
||||
res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept);
|
||||
|
||||
String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);
|
||||
if (subprotocols != null) {
|
||||
String selectedSubprotocol = selectSubprotocol(subprotocols);
|
||||
|
@ -139,6 +139,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
|
||||
res.headers().add(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET);
|
||||
res.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE);
|
||||
res.headers().add(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT, accept);
|
||||
|
||||
String subprotocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);
|
||||
if (subprotocols != null) {
|
||||
String selectedSubprotocol = selectSubprotocol(subprotocols);
|
||||
|
@ -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<CharSequence> implements SpdyHeaders {
|
||||
private static final NameValidator<CharSequence> SpydNameValidator = new NameValidator<CharSequence>() {
|
||||
@Override
|
||||
public void validateName(CharSequence name) {
|
||||
SpdyCodecUtil.validateHeaderName(name);
|
||||
}
|
||||
};
|
||||
|
||||
public DefaultSpdyHeaders() {
|
||||
super(new LinkedHashMap<CharSequence, Object>(),
|
||||
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<? extends CharSequence> 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<? extends CharSequence> headers) {
|
||||
super.set(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpdyHeaders setAll(TextHeaders headers) {
|
||||
public SpdyHeaders setAll(Headers<? extends CharSequence> 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<CharSequence> {
|
||||
|
||||
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<String> getAllAsString(CharSequence name) {
|
||||
return HeadersUtils.getAllAsString(this, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<String, String>> 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 {
|
||||
|
@ -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<CharSequence> {
|
||||
|
||||
/**
|
||||
* 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<? extends CharSequence> 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<? extends CharSequence> headers);
|
||||
|
||||
@Override
|
||||
SpdyHeaders setAll(TextHeaders headers);
|
||||
SpdyHeaders setAll(Headers<? extends CharSequence> 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<String> getAllAsString(CharSequence name);
|
||||
|
||||
/**
|
||||
* {@link #iterator()} that converts each {@link Entry}'s key and value to a {@link String}.
|
||||
*/
|
||||
Iterator<Entry<String, String>> iteratorAsString();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a header with the {@code name} and {@code value} exists, {@code false} otherwise.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
@ -350,9 +350,9 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
||||
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<SpdyFrame> {
|
||||
// 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);
|
||||
|
||||
|
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.handler.codec.http.HttpHeadersTestUtils.HeaderValue;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static io.netty.util.AsciiString.contentEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class CombinedHttpHeadersTest {
|
||||
private static final String HEADER_NAME = "testHeader";
|
||||
|
||||
@Test
|
||||
public void addCharSequencesCsv() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequencesCsvWithExistingHeader() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
headers.add(HEADER_NAME, HeaderValue.FIVE.subset(4));
|
||||
assertCsvValues(headers, HeaderValue.FIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequencesCsvWithValueContainingComma() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.SIX_QUOTED.subset(4));
|
||||
assertTrue(contentEquals(HeaderValue.SIX_QUOTED.subsetAsCsvString(4), headers.get(HEADER_NAME)));
|
||||
assertTrue(contentEquals(HeaderValue.SIX_QUOTED.subsetAsCsvString(4), headers.getAll(HEADER_NAME).get(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequencesCsvWithValueContainingCommas() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.EIGHT.subset(6));
|
||||
assertTrue(contentEquals(HeaderValue.EIGHT.subsetAsCsvString(6), headers.get(HEADER_NAME)));
|
||||
assertTrue(contentEquals(HeaderValue.EIGHT.subsetAsCsvString(6), headers.getAll(HEADER_NAME).get(0)));
|
||||
}
|
||||
|
||||
@Test (expected = NullPointerException.class)
|
||||
public void addCharSequencesCsvNullValue() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
final String value = null;
|
||||
headers.add(HEADER_NAME, value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequencesCsvMultipleTimes() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
headers.add(HEADER_NAME, "value");
|
||||
}
|
||||
assertTrue(contentEquals("value,value,value,value,value", headers.get(HEADER_NAME)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequenceCsv() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
addValues(headers, HeaderValue.ONE, HeaderValue.TWO, HeaderValue.THREE);
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequenceCsvSingleValue() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
addValues(headers, HeaderValue.ONE);
|
||||
assertCsvValue(headers, HeaderValue.ONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIterableCsv() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIterableCsvWithExistingHeader() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
headers.add(HEADER_NAME, HeaderValue.FIVE.subset(4));
|
||||
assertCsvValues(headers, HeaderValue.FIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIterableCsvSingleValue() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.ONE.asList());
|
||||
assertCsvValue(headers, HeaderValue.ONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIterableCsvEmtpy() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, Collections.<CharSequence>emptyList());
|
||||
assertTrue(contentEquals("", headers.getAll(HEADER_NAME).get(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addObjectCsv() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
addObjectValues(headers, HeaderValue.ONE, HeaderValue.TWO, HeaderValue.THREE);
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addObjectsCsv() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addObjectsIterableCsv() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addObjectsCsvWithExistingHeader() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
headers.add(HEADER_NAME, HeaderValue.FIVE.subset(4));
|
||||
assertCsvValues(headers, HeaderValue.FIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setCharSequenceCsv() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.set(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setIterableCsv() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.set(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setObjectObjectsCsv() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.set(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setObjectIterableCsv() {
|
||||
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||
headers.set(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
private static CombinedHttpHeaders newCombinedHttpHeaders() {
|
||||
return new CombinedHttpHeaders(true);
|
||||
}
|
||||
|
||||
private static void assertCsvValues(final CombinedHttpHeaders headers, final HeaderValue headerValue) {
|
||||
assertTrue(contentEquals(headerValue.asCsv(), headers.get(HEADER_NAME)));
|
||||
assertTrue(contentEquals(headerValue.asCsv(), headers.getAll(HEADER_NAME).get(0)));
|
||||
}
|
||||
|
||||
private static void assertCsvValue(final CombinedHttpHeaders headers, final HeaderValue headerValue) {
|
||||
assertTrue(contentEquals(headerValue.toString(), headers.get(HEADER_NAME)));
|
||||
assertTrue(contentEquals(headerValue.toString(), headers.getAll(HEADER_NAME).get(0)));
|
||||
}
|
||||
|
||||
private static void addValues(final CombinedHttpHeaders headers, HeaderValue... headerValues) {
|
||||
for (HeaderValue v: headerValues) {
|
||||
headers.add(HEADER_NAME, v.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void addObjectValues(final CombinedHttpHeaders headers, HeaderValue... headerValues) {
|
||||
for (HeaderValue v: headerValues) {
|
||||
headers.add(HEADER_NAME, v.toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -15,15 +15,24 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.handler.codec.http.HttpHeadersTestUtils.HeaderValue;
|
||||
import io.netty.util.AsciiString;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.util.AsciiString.contentEquals;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DefaultHttpHeadersTest {
|
||||
private static final String HEADER_NAME = "testHeader";
|
||||
|
||||
@Test
|
||||
public void keysShouldBeCaseInsensitive() {
|
||||
@ -57,4 +66,113 @@ public class DefaultHttpHeadersTest {
|
||||
assertEquals(headers2, headers1);
|
||||
assertEquals(headers1.hashCode(), headers2.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveTransferEncodingIgnoreCase() {
|
||||
HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
|
||||
message.headers().set(HttpHeaderNames.TRANSFER_ENCODING, "Chunked");
|
||||
assertFalse(message.headers().isEmpty());
|
||||
HttpHeaderUtil.setTransferEncodingChunked(message, false);
|
||||
assertTrue(message.headers().isEmpty());
|
||||
}
|
||||
|
||||
// Test for https://github.com/netty/netty/issues/1690
|
||||
@Test
|
||||
public void testGetOperations() {
|
||||
HttpHeaders headers = new DefaultHttpHeaders();
|
||||
headers.add("Foo", "1");
|
||||
headers.add("Foo", "2");
|
||||
|
||||
assertEquals("1", headers.get("Foo"));
|
||||
|
||||
List<String> values = headers.getAll("Foo");
|
||||
assertEquals(2, values.size());
|
||||
assertEquals("1", values.get(0));
|
||||
assertEquals("2", values.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquansIgnoreCase() {
|
||||
assertThat(AsciiString.contentEqualsIgnoreCase(null, null), is(true));
|
||||
assertThat(AsciiString.contentEqualsIgnoreCase(null, "foo"), is(false));
|
||||
assertThat(AsciiString.contentEqualsIgnoreCase("bar", null), is(false));
|
||||
assertThat(AsciiString.contentEqualsIgnoreCase("FoO", "fOo"), is(true));
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void testSetNullHeaderValueValidate() {
|
||||
HttpHeaders headers = new DefaultHttpHeaders(true);
|
||||
headers.set("test", (CharSequence) null);
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void testSetNullHeaderValueNotValidate() {
|
||||
HttpHeaders headers = new DefaultHttpHeaders(false);
|
||||
headers.set("test", (CharSequence) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequences() {
|
||||
final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertDefaultValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIterable() {
|
||||
final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertDefaultValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addObjects() {
|
||||
final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertDefaultValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setCharSequences() {
|
||||
final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
|
||||
headers.set(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertDefaultValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setIterable() {
|
||||
final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
|
||||
headers.set(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertDefaultValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setObjectObjects() {
|
||||
final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
|
||||
headers.set(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertDefaultValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setObjectIterable() {
|
||||
final DefaultHttpHeaders headers = newDefaultDefaultHttpHeaders();
|
||||
headers.set(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertDefaultValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
private static void assertDefaultValues(final DefaultHttpHeaders headers, final HeaderValue headerValue) {
|
||||
assertTrue(contentEquals(headerValue.asList().get(0), headers.get(HEADER_NAME)));
|
||||
List<CharSequence> expected = headerValue.asList();
|
||||
List<String> actual = headers.getAll(HEADER_NAME);
|
||||
assertEquals(expected.size(), actual.size());
|
||||
Iterator<CharSequence> eItr = expected.iterator();
|
||||
Iterator<String> aItr = actual.iterator();
|
||||
while (eItr.hasNext()) {
|
||||
assertTrue(contentEquals(eItr.next(), aItr.next()));
|
||||
}
|
||||
}
|
||||
|
||||
private static DefaultHttpHeaders newDefaultDefaultHttpHeaders() {
|
||||
return new DefaultHttpHeaders(true);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class HttpHeaderUtilTest {
|
||||
|
||||
@Test
|
||||
public void testRemoveTransferEncodingIgnoreCase() {
|
||||
HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
|
||||
message.headers().set(HttpHeaderNames.TRANSFER_ENCODING, "Chunked");
|
||||
assertFalse(message.headers().isEmpty());
|
||||
HttpHeaderUtil.setTransferEncodingChunked(message, false);
|
||||
assertTrue(message.headers().isEmpty());
|
||||
}
|
||||
|
||||
// Test for https://github.com/netty/netty/issues/1690
|
||||
@Test
|
||||
public void testGetOperations() {
|
||||
HttpHeaders headers = new DefaultHttpHeaders();
|
||||
headers.add("Foo", "1");
|
||||
headers.add("Foo", "2");
|
||||
|
||||
assertEquals("1", headers.get("Foo"));
|
||||
|
||||
List<String> 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));
|
||||
}
|
||||
}
|
@ -52,10 +52,10 @@ public class HttpHeadersTest {
|
||||
|
||||
@Test
|
||||
public void testEquansIgnoreCase() {
|
||||
assertThat(AsciiString.equalsIgnoreCase(null, null), is(true));
|
||||
assertThat(AsciiString.equalsIgnoreCase(null, "foo"), is(false));
|
||||
assertThat(AsciiString.equalsIgnoreCase("bar", null), is(false));
|
||||
assertThat(AsciiString.equalsIgnoreCase("FoO", "fOo"), is(true));
|
||||
assertThat(AsciiString.contentEqualsIgnoreCase(null, null), is(true));
|
||||
assertThat(AsciiString.contentEqualsIgnoreCase(null, "foo"), is(false));
|
||||
assertThat(AsciiString.contentEqualsIgnoreCase("bar", null), is(false));
|
||||
assertThat(AsciiString.contentEqualsIgnoreCase("FoO", "fOo"), is(true));
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.netty.util.internal.StringUtil.COMMA;
|
||||
import static io.netty.util.internal.StringUtil.DOUBLE_QUOTE;
|
||||
|
||||
class HttpHeadersTestUtils {
|
||||
public enum HeaderValue {
|
||||
UNKNOWN("Unknown", 0),
|
||||
ONE("One", 1),
|
||||
TWO("Two", 2),
|
||||
THREE("Three", 3),
|
||||
FOUR("Four", 4),
|
||||
FIVE("Five", 5),
|
||||
SIX_QUOTED("Six,", 6),
|
||||
SEVEN_QUOTED("Seven; , GMT", 7),
|
||||
EIGHT("Eight", 8);
|
||||
|
||||
private final int nr;
|
||||
private final String value;
|
||||
private List<CharSequence> array;
|
||||
|
||||
HeaderValue(final String value, final int nr) {
|
||||
this.nr = nr;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public List<CharSequence> asList() {
|
||||
if (array == null) {
|
||||
List<CharSequence> list = new ArrayList<CharSequence>(nr);
|
||||
for (int i = 1; i <= nr; i++) {
|
||||
list.add(of(i).toString());
|
||||
}
|
||||
array = list;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public List<CharSequence> subset(int from) {
|
||||
assert from > 0;
|
||||
--from;
|
||||
final int size = nr - from;
|
||||
final int end = from + size;
|
||||
List<CharSequence> list = new ArrayList<CharSequence>(size);
|
||||
List<CharSequence> fullList = asList();
|
||||
for (int i = from; i < end; ++i) {
|
||||
list.add(fullList.get(i));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public String subsetAsCsvString(final int from) {
|
||||
final List<CharSequence> subset = subset(from);
|
||||
return asCsv(subset);
|
||||
}
|
||||
|
||||
public String asCsv(final List<CharSequence> arr) {
|
||||
if (arr == null || arr.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder(arr.size() * 10);
|
||||
final int end = arr.size() - 1;
|
||||
for (int i = 0; i < end; ++i) {
|
||||
quoted(sb, arr.get(i)).append(COMMA);
|
||||
}
|
||||
quoted(sb, arr.get(end));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public CharSequence asCsv() {
|
||||
return asCsv(asList());
|
||||
}
|
||||
|
||||
private static StringBuilder quoted(final StringBuilder sb, final CharSequence value) {
|
||||
if (contains(value, COMMA) && !contains(value, DOUBLE_QUOTE)) {
|
||||
return sb.append(DOUBLE_QUOTE).append(value).append(DOUBLE_QUOTE);
|
||||
}
|
||||
return sb.append(value);
|
||||
}
|
||||
|
||||
private static boolean contains(CharSequence value, char c) {
|
||||
for (int i = 0; i < value.length(); ++i) {
|
||||
if (value.charAt(i) == c) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final Map<Integer, HeaderValue> MAP;
|
||||
|
||||
static {
|
||||
final Map<Integer, HeaderValue> map = new HashMap<Integer, HeaderValue>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -84,7 +84,7 @@ public class SpdySessionHandlerTest {
|
||||
assertEquals(last, spdyHeadersFrame.isLast());
|
||||
for (CharSequence name: headers.names()) {
|
||||
List<CharSequence> expectedValues = headers.getAll(name);
|
||||
List<CharSequence> receivedValues = new ArrayList<CharSequence>(spdyHeadersFrame.headers().getAll(name));
|
||||
List<CharSequence> receivedValues = spdyHeadersFrame.headers().getAll(name);
|
||||
assertTrue(receivedValues.containsAll(expectedValues));
|
||||
receivedValues.removeAll(expectedValues);
|
||||
assertTrue(receivedValues.isEmpty());
|
||||
|
@ -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<ByteString> implements Http2Headers {
|
||||
private HeaderEntry<ByteString> firstNonPseudo = head;
|
||||
|
||||
public DefaultHttp2Headers() {
|
||||
super(new TreeMap<ByteString, Object>(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<? extends ByteString> 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<? extends ByteString> headers) {
|
||||
super.set(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setAll(BinaryHeaders headers) {
|
||||
public Http2Headers setAll(Headers<? extends ByteString> 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<ByteString> newHeaderEntry(int h, ByteString name, ByteString value,
|
||||
HeaderEntry<ByteString> 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<ByteString> {
|
||||
protected Http2HeaderEntry(int hash, ByteString key, ByteString value, HeaderEntry<ByteString> next) {
|
||||
super(hash, key);
|
||||
this.value = value;
|
||||
this.next = next;
|
||||
|
||||
private static class Http2HeaderNameComparator implements Comparator<ByteString>, Serializable {
|
||||
|
||||
public static final Http2HeaderNameComparator INSTANCE = new Http2HeaderNameComparator();
|
||||
private static final long serialVersionUID = 1109871697664666478L;
|
||||
|
||||
@Override
|
||||
public int compare(ByteString one, ByteString two) {
|
||||
// Reserved header names come first.
|
||||
final boolean isPseudoHeader1 = !one.isEmpty() && one.byteAt(0) == ':';
|
||||
final boolean isPseudoHeader2 = !two.isEmpty() && two.byteAt(0) == ':';
|
||||
if (isPseudoHeader1 != isPseudoHeader2) {
|
||||
return isPseudoHeader1 ? -1 : 1;
|
||||
}
|
||||
final int delta = one.hashCode() - two.hashCode();
|
||||
if (delta == 0) {
|
||||
// If the hash code matches it's very likely for the two strings to be equal
|
||||
// and thus we optimistically compare them with the much faster equals method.
|
||||
if (one.equals(two)) {
|
||||
return 0;
|
||||
} else {
|
||||
return ByteString.DEFAULT_COMPARATOR.compare(one, two);
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ByteString> 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<? extends ByteString> 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<? extends ByteString> headers) {
|
||||
super.set(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers setAll(BinaryHeaders headers) {
|
||||
public Http2Headers setAll(Headers<? extends ByteString> headers) {
|
||||
super.setAll(headers);
|
||||
return this;
|
||||
}
|
||||
|
@ -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<ByteString> {
|
||||
|
||||
/**
|
||||
* 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<? extends ByteString> 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<? extends ByteString> headers);
|
||||
|
||||
@Override
|
||||
Http2Headers setAll(BinaryHeaders headers);
|
||||
Http2Headers setAll(Headers<? extends ByteString> headers);
|
||||
|
||||
@Override
|
||||
Http2Headers clear();
|
||||
|
@ -275,7 +275,7 @@ public final class HttpUtil {
|
||||
out.path(new AsciiString(request.uri()));
|
||||
out.method(new AsciiString(request.method().toString()));
|
||||
|
||||
String value = inHeaders.get(HttpHeaderNames.HOST);
|
||||
String value = inHeaders.getAsString(HttpHeaderNames.HOST);
|
||||
if (value != null) {
|
||||
URI hostUri = URI.create(value);
|
||||
// The authority MUST NOT include the deprecated "userinfo" subcomponent
|
||||
@ -324,8 +324,8 @@ public final class HttpUtil {
|
||||
AsciiString aValue = AsciiString.of(entry.getValue());
|
||||
// https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-8.1.2.2
|
||||
// makes a special exception for TE
|
||||
if (!aName.equalsIgnoreCase(HttpHeaderNames.TE) ||
|
||||
aValue.equalsIgnoreCase(HttpHeaderValues.TRAILERS)) {
|
||||
if (!aName.contentEqualsIgnoreCase(HttpHeaderNames.TE) ||
|
||||
aValue.contentEqualsIgnoreCase(HttpHeaderValues.TRAILERS)) {
|
||||
out.add(aName, aValue);
|
||||
}
|
||||
}
|
||||
@ -334,7 +334,7 @@ public final class HttpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* A visitor which translates HTTP/2 headers to HTTP/1 headers
|
||||
* Utility which translates HTTP/2 headers to HTTP/1 headers.
|
||||
*/
|
||||
private static final class Http2ToHttpHeaderTranslator {
|
||||
/**
|
||||
|
@ -14,12 +14,6 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
||||
import io.netty.handler.codec.http.FullHttpMessage;
|
||||
@ -27,7 +21,12 @@ import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.collection.IntObjectHashMap;
|
||||
import io.netty.util.collection.IntObjectMap;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
||||
|
||||
/**
|
||||
* Translate header/data/priority HTTP/2 frame events into HTTP events. Just as {@link InboundHttp2ToHttpAdapter}
|
||||
@ -136,14 +135,10 @@ public final class InboundHttp2ToHttpPriorityAdapter extends InboundHttp2ToHttpA
|
||||
* @param http2Headers The target HTTP/2 headers
|
||||
*/
|
||||
private static void addHttpHeadersToHttp2Headers(HttpHeaders httpHeaders, final Http2Headers http2Headers) {
|
||||
try {
|
||||
Iterator<Entry<CharSequence, CharSequence>> iter = httpHeaders.iteratorCharSequence();
|
||||
while (iter.hasNext()) {
|
||||
Entry<CharSequence, CharSequence> entry = iter.next();
|
||||
http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue()));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
PlatformDependent.throwException(ex);
|
||||
Iterator<Entry<CharSequence, CharSequence>> iter = httpHeaders.iteratorCharSequence();
|
||||
while (iter.hasNext()) {
|
||||
Entry<CharSequence, CharSequence> entry = iter.next();
|
||||
http2Headers.add(AsciiString.of(entry.getKey()), AsciiString.of(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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}.
|
||||
*/
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
@ -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<Entry<ByteString, ByteString>> iter = headers.iterator();
|
||||
List<ByteString> names = new ArrayList<ByteString>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
names.add(iter.next().getKey());
|
||||
}
|
||||
assertTrue(names.containsAll(asList(bs(":method"), bs(":status"), bs(":path"), bs(":scheme"),
|
||||
bs(":authority"))));
|
||||
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<ByteString> nonPseudoHeaders = new HashSet<ByteString>(headers.size());
|
||||
for (Entry<ByteString, ByteString> 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<ByteString, ByteString> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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<CharSequence> implements StompHeaders {
|
||||
public DefaultStompHeaders() {
|
||||
super(new TreeMap<CharSequence, Object>(), 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<? extends CharSequence> 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<? extends CharSequence> headers) {
|
||||
super.set(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StompHeaders setAll(TextHeaders headers) {
|
||||
public StompHeaders setAll(Headers<? extends CharSequence> 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<String> getAllAsString(CharSequence name) {
|
||||
return HeadersUtils.getAllAsString(this, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<String, String>> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<CharSequence> {
|
||||
|
||||
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<? extends CharSequence> 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<? extends CharSequence> headers);
|
||||
|
||||
@Override
|
||||
StompHeaders setAll(TextHeaders headers);
|
||||
StompHeaders setAll(Headers<? extends CharSequence> 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<String> getAllAsString(CharSequence name);
|
||||
|
||||
/**
|
||||
* {@link #iterator()} that converts each {@link Entry}'s key and value to a {@link String}.
|
||||
*/
|
||||
Iterator<Entry<String, String>> iteratorAsString();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a header with the {@code name} and {@code value} exists, {@code false} otherwise.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
@ -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}.
|
||||
|
@ -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 =
|
||||
|
@ -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<ByteString> {
|
||||
|
||||
@Override
|
||||
BinaryHeaders add(ByteString name, ByteString value);
|
||||
|
||||
@Override
|
||||
BinaryHeaders add(ByteString name, Iterable<? extends ByteString> 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<? extends ByteString> 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();
|
||||
}
|
@ -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<ByteString> {
|
||||
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);
|
||||
}
|
||||
}
|
@ -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<CharSequence> {
|
||||
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());
|
||||
}
|
||||
}
|
@ -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<UnconvertedType, ConvertedType> extends Headers<UnconvertedType> {
|
||||
|
||||
/**
|
||||
* Interface to do conversions to and from the two generic type parameters
|
||||
*/
|
||||
interface TypeConverter<UnconvertedType, ConvertedType> {
|
||||
/**
|
||||
* 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<ConvertedType> 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<ConvertedType> 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<Entry<ConvertedType, ConvertedType>> iteratorConverted();
|
||||
|
||||
/**
|
||||
* Invokes {@link Headers#names()} and does a conversion on the results
|
||||
*
|
||||
* @return The values corresponding to {@code name} and then converted
|
||||
*/
|
||||
Set<ConvertedType> namesAndConvert(Comparator<ConvertedType> comparator);
|
||||
}
|
@ -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<ByteString> implements BinaryHeaders {
|
||||
|
||||
private static final NameValidator<ByteString> NO_NAME_VALIDATOR = DefaultHeaders.NoNameValidator.instance();
|
||||
|
||||
public DefaultBinaryHeaders() {
|
||||
this(new TreeMap<ByteString, Object>(ByteString.DEFAULT_COMPARATOR));
|
||||
}
|
||||
|
||||
public DefaultBinaryHeaders(Map<ByteString, Object> map) {
|
||||
this(map, NO_NAME_VALIDATOR);
|
||||
}
|
||||
|
||||
public DefaultBinaryHeaders(Map<ByteString, Object> map, NameValidator<ByteString> nameValidator) {
|
||||
super(map, nameValidator, ByteStringConverter.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryHeaders add(ByteString name, ByteString value) {
|
||||
super.add(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryHeaders add(ByteString name, Iterable<? extends ByteString> 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<? extends ByteString> 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<ByteString> {
|
||||
|
||||
public static final ByteStringConverter INSTANCE = new ByteStringConverter();
|
||||
private static final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8;
|
||||
|
||||
private ByteStringConverter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertObject(Object value) {
|
||||
if (value instanceof ByteString) {
|
||||
return (ByteString) value;
|
||||
}
|
||||
if (value instanceof CharSequence) {
|
||||
return new ByteString((CharSequence) value, DEFAULT_CHARSET);
|
||||
}
|
||||
return new ByteString(value.toString(), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertInt(int value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertLong(long value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertDouble(double value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertChar(char value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertBoolean(boolean value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertFloat(float value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertToInt(ByteString value) {
|
||||
return value.parseAsciiInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToLong(ByteString value) {
|
||||
return value.parseAsciiLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertTimeMillis(long value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToTimeMillis(ByteString value) {
|
||||
try {
|
||||
return HeaderDateFormat.get().parse(value.toString());
|
||||
} catch (ParseException e) {
|
||||
PlatformDependent.throwException(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double convertToDouble(ByteString value) {
|
||||
return value.parseAsciiDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char convertToChar(ByteString value) {
|
||||
return value.parseChar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean convertToBoolean(ByteString value) {
|
||||
return value.byteAt(0) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float convertToFloat(ByteString value) {
|
||||
return value.parseAsciiFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertShort(short value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short convertToShort(ByteString value) {
|
||||
return value.parseAsciiShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteString convertByte(byte value) {
|
||||
return new ByteString(String.valueOf(value), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte convertToByte(ByteString value) {
|
||||
return value.byteAt(0);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<UnconvertedType, ConvertedType> extends DefaultHeaders<UnconvertedType>
|
||||
implements ConvertibleHeaders<UnconvertedType, ConvertedType> {
|
||||
|
||||
private final TypeConverter<UnconvertedType, ConvertedType> typeConverter;
|
||||
|
||||
public DefaultConvertibleHeaders(Map<UnconvertedType, Object> map,
|
||||
NameValidator<UnconvertedType> nameValidator,
|
||||
ValueConverter<UnconvertedType> valueConverter,
|
||||
TypeConverter<UnconvertedType, ConvertedType> 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<ConvertedType> getAllAndConvert(UnconvertedType name) {
|
||||
List<UnconvertedType> all = getAll(name);
|
||||
List<ConvertedType> allConverted = new ArrayList<ConvertedType>(all.size());
|
||||
for (int i = 0; i < all.size(); ++i) {
|
||||
allConverted.add(typeConverter.toConvertedType(all.get(i)));
|
||||
}
|
||||
return allConverted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConvertedType> getAllAndRemoveAndConvert(UnconvertedType name) {
|
||||
List<UnconvertedType> all = getAllAndRemove(name);
|
||||
List<ConvertedType> allConverted = new ArrayList<ConvertedType>(all.size());
|
||||
for (int i = 0; i < all.size(); ++i) {
|
||||
allConverted.add(typeConverter.toConvertedType(all.get(i)));
|
||||
}
|
||||
return allConverted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<ConvertedType, ConvertedType>> iteratorConverted() {
|
||||
return new ConvertedIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConvertedType> namesAndConvert(Comparator<ConvertedType> comparator) {
|
||||
Set<UnconvertedType> names = names();
|
||||
Set<ConvertedType> namesConverted = new TreeSet<ConvertedType>(comparator);
|
||||
for (UnconvertedType unconverted : names) {
|
||||
namesConverted.add(typeConverter.toConvertedType(unconverted));
|
||||
}
|
||||
return namesConverted;
|
||||
}
|
||||
|
||||
private final class ConvertedIterator implements Iterator<Entry<ConvertedType, ConvertedType>> {
|
||||
private final Iterator<Entry<UnconvertedType, UnconvertedType>> iter = iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iter.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<ConvertedType, ConvertedType> next() {
|
||||
Entry<UnconvertedType, UnconvertedType> next = iter.next();
|
||||
|
||||
return new ConvertedEntry(next);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private final class ConvertedEntry implements Entry<ConvertedType, ConvertedType> {
|
||||
private final Entry<UnconvertedType, UnconvertedType> entry;
|
||||
private ConvertedType name;
|
||||
private ConvertedType value;
|
||||
|
||||
ConvertedEntry(Entry<UnconvertedType, UnconvertedType> 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();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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<CharSequence, String> implements TextHeaders {
|
||||
|
||||
public static final NameValidator<CharSequence> NO_NAME_VALIDATOR = NoNameValidator.instance();
|
||||
|
||||
private final ValuesComposer valuesComposer;
|
||||
|
||||
public DefaultTextHeaders() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public DefaultTextHeaders(boolean singleHeaderFields) {
|
||||
this(new TreeMap<CharSequence, Object>(CHARSEQUENCE_CASE_INSENSITIVE_ORDER), NO_NAME_VALIDATOR,
|
||||
CharSequenceConverter.INSTANCE, singleHeaderFields);
|
||||
}
|
||||
|
||||
public DefaultTextHeaders(Map<CharSequence, Object> map,
|
||||
NameValidator<CharSequence> nameValidator,
|
||||
ValueConverter<CharSequence> valueConverter,
|
||||
boolean singleHeaderFields) {
|
||||
super(map, nameValidator, valueConverter, CharSequenceToStringConverter.INSTANCE);
|
||||
valuesComposer = singleHeaderFields ? new SingleHeaderValuesComposer() : new MultipleFieldsValueComposer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
|
||||
return contains(name, value,
|
||||
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<? extends CharSequence> 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<? extends CharSequence> 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<? extends CharSequence> 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<? extends CharSequence> 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<? extends CharSequence> 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<? extends CharSequence> 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 <a href="https://tools.ietf.org/html/rfc7230#section-3.2.2">3.2.2 Field Order</a>
|
||||
* of RFC-7230 for details.
|
||||
*/
|
||||
private final class SingleHeaderValuesComposer implements ValuesComposer {
|
||||
|
||||
private final ValueConverter<CharSequence> valueConverter = valueConverter();
|
||||
private CsvValueEscaper<Object> objectEscaper;
|
||||
private CsvValueEscaper<CharSequence> charSequenceEscaper;
|
||||
|
||||
private CsvValueEscaper<Object> objectEscaper() {
|
||||
if (objectEscaper == null) {
|
||||
objectEscaper = new CsvValueEscaper<Object>() {
|
||||
@Override
|
||||
public CharSequence escape(Object value) {
|
||||
return StringUtil.escapeCsv(valueConverter.convertObject(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
return objectEscaper;
|
||||
}
|
||||
|
||||
private CsvValueEscaper<CharSequence> charSequenceEscaper() {
|
||||
if (charSequenceEscaper == null) {
|
||||
charSequenceEscaper = new CsvValueEscaper<CharSequence>() {
|
||||
@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<? extends CharSequence> 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<? extends CharSequence> 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 <T> CharSequence commaSeparate(CsvValueEscaper<T> 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 <T> CharSequence commaSeparate(CsvValueEscaper<T> escaper, Iterable<? extends T> values) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Iterator<? extends T> 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 <T> The type that a concrete implementation handles
|
||||
*/
|
||||
private interface CsvValueEscaper<T> {
|
||||
/**
|
||||
* 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<CharSequence, String> {
|
||||
|
||||
private static final CharSequenceToStringConverter INSTANCE = new CharSequenceToStringConverter();
|
||||
|
||||
@Override
|
||||
public String toConvertedType(CharSequence value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toUnconvertedType(String value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CharSequenceConverter implements ValueConverter<CharSequence> {
|
||||
|
||||
public static final CharSequenceConverter INSTANCE = new CharSequenceConverter();
|
||||
|
||||
@Override
|
||||
public CharSequence convertObject(Object value) {
|
||||
if (value instanceof CharSequence) {
|
||||
return (CharSequence) value;
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertInt(int value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertLong(long value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertDouble(double value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertChar(char value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertBoolean(boolean value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertFloat(float value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean convertToBoolean(CharSequence value) {
|
||||
return Boolean.parseBoolean(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertByte(byte value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte convertToByte(CharSequence value) {
|
||||
return Byte.valueOf(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public char convertToChar(CharSequence value) {
|
||||
if (value.length() == 0) {
|
||||
throw new IllegalArgumentException("'value' is empty.");
|
||||
}
|
||||
return value.charAt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertShort(short value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short convertToShort(CharSequence value) {
|
||||
return Short.valueOf(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertToInt(CharSequence value) {
|
||||
return Integer.parseInt(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToLong(CharSequence value) {
|
||||
return Long.parseLong(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertTimeMillis(long value) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long convertToTimeMillis(CharSequence value) {
|
||||
try {
|
||||
return HeaderDateFormat.get().parse(value.toString());
|
||||
} catch (ParseException e) {
|
||||
PlatformDependent.throwException(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float convertToFloat(CharSequence value) {
|
||||
return Float.valueOf(value.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double convertToDouble(CharSequence value) {
|
||||
return Double.valueOf(value.toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -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<ByteString> 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<? extends ByteString> 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<? extends ByteString> 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;
|
||||
}
|
||||
}
|
@ -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<UnconvertedType, ConvertedType> extends
|
||||
EmptyHeaders<UnconvertedType> implements ConvertibleHeaders<UnconvertedType, ConvertedType> {
|
||||
|
||||
@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<ConvertedType> getAllAndConvert(UnconvertedType name) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConvertedType> getAllAndRemoveAndConvert(UnconvertedType name) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<ConvertedType, ConvertedType>> iteratorConverted() {
|
||||
List<Entry<ConvertedType, ConvertedType>> empty = Collections.emptyList();
|
||||
return empty.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConvertedType> namesAndConvert(Comparator<ConvertedType> comparator) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
@ -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<T> implements Headers<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(T name, T value, Comparator<? super T> valueComparator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
|
@ -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<CharSequence, String> 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<? extends CharSequence> 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<? extends CharSequence> 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;
|
||||
}
|
||||
}
|
@ -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<T> extends Iterable<Entry<T, T>> {
|
||||
|
||||
/**
|
||||
* Converts to/from a generic object to the type of the headers.
|
||||
*/
|
||||
interface ValueConverter<T> {
|
||||
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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<T> extends Iterable<Entry<T, T>> {
|
||||
* The {@link Object#equals(Object)} method is used to test for equality of {@code value}.
|
||||
* </p>
|
||||
* @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<T> extends Iterable<Entry<T, T>> {
|
||||
*/
|
||||
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<? super T> valueComparator);
|
||||
|
||||
/**
|
||||
* Returns the number of headers in this object.
|
||||
*/
|
||||
@ -687,8 +669,7 @@ public interface Headers<T> extends Iterable<Entry<T, T>> {
|
||||
Headers<T> 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<T> extends Iterable<Entry<T, T>> {
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
282
codec/src/main/java/io/netty/handler/codec/HeadersUtils.java
Normal file
282
codec/src/main/java/io/netty/handler/codec/HeadersUtils.java
Normal file
@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright 2015 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
/**
|
||||
* Provides utility methods related to {@link Headers}.
|
||||
*/
|
||||
public final class HeadersUtils {
|
||||
|
||||
private HeadersUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Headers#get(Object)} and convert each element of {@link List} to a {@link String}.
|
||||
* @param name the name of the header to retrieve
|
||||
* @return a {@link List} of header values or an empty {@link List} if no values are found.
|
||||
*/
|
||||
public static <T> List<String> getAllAsString(Headers<T> headers, T name) {
|
||||
final List<T> allNames = headers.getAll(name);
|
||||
return new AbstractList<String>() {
|
||||
@Override
|
||||
public String get(int index) {
|
||||
T value = allNames.get(index);
|
||||
return value != null ? value.toString() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return allNames.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Headers#get(Object)} and convert the result to a {@link String}.
|
||||
* @param headers the headers to get the {@code name} from
|
||||
* @param name the name of the header to retrieve
|
||||
* @return the first header value if the header is found. {@code null} if there's no such entry.
|
||||
*/
|
||||
public static <T> String getAsString(Headers<T> headers, T name) {
|
||||
T orig = headers.get(name);
|
||||
return orig != null ? orig.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Headers#iterator()} which converts each {@link Entry}'s key and value to a {@link String}.
|
||||
*/
|
||||
public static Iterator<Entry<String, String>> iteratorAsString(
|
||||
Iterable<Entry<CharSequence, CharSequence>> headers) {
|
||||
return new StringEntryIterator(headers.iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Headers#names()} and convert each element of {@link Set} to a {@link String}.
|
||||
* @param headers the headers to get the names from
|
||||
* @return a {@link Set} of header values or an empty {@link Set} if no values are found.
|
||||
*/
|
||||
public static Set<String> namesAsString(Headers<CharSequence> headers) {
|
||||
return new CharSequenceDelegatingStringSet(headers.names());
|
||||
}
|
||||
|
||||
private static final class StringEntryIterator implements Iterator<Entry<String, String>> {
|
||||
private final Iterator<Entry<CharSequence, CharSequence>> iter;
|
||||
|
||||
public StringEntryIterator(Iterator<Entry<CharSequence, CharSequence>> iter) {
|
||||
this.iter = iter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iter.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<String, String> next() {
|
||||
return new StringEntry(iter.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StringEntry implements Entry<String, String> {
|
||||
private final Entry<CharSequence, CharSequence> entry;
|
||||
private String name;
|
||||
private String value;
|
||||
|
||||
StringEntry(Entry<CharSequence, CharSequence> entry) {
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
if (name == null) {
|
||||
name = entry.getKey().toString();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
if (value == null && entry.getValue() != null) {
|
||||
value = entry.getValue().toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setValue(String value) {
|
||||
String old = getValue();
|
||||
entry.setValue(value);
|
||||
return old;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return entry.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StringIterator<T> implements Iterator<String> {
|
||||
private final Iterator<T> iter;
|
||||
|
||||
public StringIterator(Iterator<T> iter) {
|
||||
this.iter = iter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iter.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
T next = iter.next();
|
||||
return next != null ? next.toString() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CharSequenceDelegatingStringSet extends DelegatingStringSet<CharSequence> {
|
||||
public CharSequenceDelegatingStringSet(Set<CharSequence> allNames) {
|
||||
super(allNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(String e) {
|
||||
return allNames.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends String> c) {
|
||||
return allNames.addAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class DelegatingStringSet<T> implements Set<String> {
|
||||
protected final Set<T> allNames;
|
||||
|
||||
public DelegatingStringSet(Set<T> allNames) {
|
||||
this.allNames = checkNotNull(allNames, "allNames");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return allNames.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return allNames.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return allNames.contains(o.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new StringIterator<T>(allNames.iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
Object[] arr = new String[size()];
|
||||
fillArray(arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <X> X[] toArray(X[] a) {
|
||||
if (a == null || a.length < size()) {
|
||||
X[] arr = (X[]) new Object[size()];
|
||||
fillArray(arr);
|
||||
return arr;
|
||||
}
|
||||
fillArray(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
private void fillArray(Object[] arr) {
|
||||
Iterator<T> itr = allNames.iterator();
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
arr[i] = itr.next();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return allNames.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
for (Object o : c) {
|
||||
if (!contains(o)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
boolean modified = false;
|
||||
for (Object o : c) {
|
||||
if (remove(o)) {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
boolean modified = false;
|
||||
Iterator<String> it = iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!c.contains(it.next())) {
|
||||
it.remove();
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
allNames.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<CharSequence, String> {
|
||||
|
||||
/**
|
||||
* 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<? extends CharSequence> 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<? extends CharSequence> 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();
|
||||
}
|
@ -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> {
|
||||
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);
|
||||
}
|
@ -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<ByteString> newInstance() {
|
||||
return new DefaultHeaders<ByteString>(ByteStringValueConverter.INSTANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addShouldIncreaseAndRemoveShouldDecreaseTheSize() {
|
||||
DefaultBinaryHeaders headers = new DefaultBinaryHeaders();
|
||||
Headers<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> 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<ByteString> headers1 = newInstance();
|
||||
Headers<ByteString> 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<ByteString> headers1 = newInstance();
|
||||
headers1.add(bs("name1"), bs("value1"));
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
Headers<ByteString> headers2 = newInstance();
|
||||
headers1.add(bs("name1"), bs("value2"));
|
||||
assertNotEquals(headers1, headers2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subsetOfHeadersShouldNotBeEquivalent() {
|
||||
DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
|
||||
Headers<ByteString> headers1 = newInstance();
|
||||
headers1.add(bs("name1"), bs("value1"));
|
||||
headers1.add(bs("name2"), bs("value2"));
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
Headers<ByteString> headers2 = newInstance();
|
||||
headers1.add(bs("name1"), bs("value1"));
|
||||
assertNotEquals(headers1, headers2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWithDifferentNamesAndValuesShouldNotBeEquivalent() {
|
||||
DefaultBinaryHeaders h1 = new DefaultBinaryHeaders();
|
||||
Headers<ByteString> h1 = newInstance();
|
||||
h1.set(bs("name1"), bs("value1"));
|
||||
DefaultBinaryHeaders h2 = new DefaultBinaryHeaders();
|
||||
Headers<ByteString> 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<Map.Entry<ByteString, ByteString>> iterator = new DefaultBinaryHeaders().iterator();
|
||||
Iterator<Map.Entry<ByteString, ByteString>> iterator = newInstance().iterator();
|
||||
assertFalse(iterator.hasNext());
|
||||
iterator.next();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorShouldReturnAllNameValuePairs() {
|
||||
DefaultBinaryHeaders headers1 = new DefaultBinaryHeaders();
|
||||
Headers<ByteString> 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<ByteString> headers2 = newInstance();
|
||||
for (Entry<ByteString, ByteString> 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<ByteString> 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<ByteString> 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<ByteString> headers1 = newInstance();
|
||||
headers1.add(bs("name"), bs("value"));
|
||||
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
Headers<ByteString> 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<ByteString> headers1 = newInstance();
|
||||
headers1.add(bs("name"), bs("value"));
|
||||
headers1.add(bs("name1"), bs("value1"));
|
||||
|
||||
DefaultBinaryHeaders headers2 = new DefaultBinaryHeaders();
|
||||
Headers<ByteString> headers2 = newInstance();
|
||||
headers2.add(bs("name"), bs("newvalue"));
|
||||
headers2.add(bs("name2"), bs("value2"));
|
||||
|
||||
DefaultBinaryHeaders expected = new DefaultBinaryHeaders();
|
||||
Headers<ByteString> expected = newInstance();
|
||||
expected.add(bs("name"), bs("newvalue"));
|
||||
expected.add(bs("name1"), bs("value1"));
|
||||
expected.add(bs("name2"), bs("value2"));
|
@ -1,360 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License, version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package io.netty.handler.codec;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.netty.util.internal.StringUtil.COMMA;
|
||||
import static io.netty.util.internal.StringUtil.DOUBLE_QUOTE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class DefaultTextHeadersTest {
|
||||
|
||||
private static final String HEADER_NAME = "testHeader";
|
||||
|
||||
@Test
|
||||
public void addCharSequences() {
|
||||
final TextHeaders headers = newDefaultTextHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asArray());
|
||||
assertDefaultValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequencesCsv() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asArray());
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequencesCsvWithExistingHeader() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asArray());
|
||||
headers.add(HEADER_NAME, HeaderValue.FIVE.subset(4));
|
||||
assertCsvValues(headers, HeaderValue.FIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequencesCsvWithValueContainingComma() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.SIX_QUOTED.subset(4));
|
||||
assertEquals(HeaderValue.SIX_QUOTED.subsetAsCsvString(4), headers.getAndConvert(HEADER_NAME));
|
||||
assertEquals(HeaderValue.SIX_QUOTED.subsetAsCsvString(4), headers.getAllAndConvert(HEADER_NAME).get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequencesCsvWithValueContainingCommas() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.EIGHT.subset(6));
|
||||
assertEquals(HeaderValue.EIGHT.subsetAsCsvString(6), headers.getAndConvert(HEADER_NAME));
|
||||
assertEquals(HeaderValue.EIGHT.subsetAsCsvString(6), headers.getAllAndConvert(HEADER_NAME).get(0));
|
||||
}
|
||||
|
||||
@Test (expected = NullPointerException.class)
|
||||
public void addCharSequencesCsvNullValue() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
final String value = null;
|
||||
headers.add(HEADER_NAME, value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequencesCsvMultipleTimes() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
headers.add(HEADER_NAME, "value");
|
||||
}
|
||||
assertEquals("value,value,value,value,value", headers.getAndConvert(HEADER_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequenceCsv() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
addValues(headers, HeaderValue.ONE, HeaderValue.TWO, HeaderValue.THREE);
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addCharSequenceCsvSingleValue() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
addValues(headers, HeaderValue.ONE);
|
||||
assertCsvValue(headers, HeaderValue.ONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIterable() {
|
||||
final TextHeaders headers = newDefaultTextHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertDefaultValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIterableCsv() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asList());
|
||||
assertCsvValues(headers, HeaderValue.THREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIterableCsvWithExistingHeader() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.THREE.asArray());
|
||||
headers.add(HEADER_NAME, HeaderValue.FIVE.subset(4));
|
||||
assertCsvValues(headers, HeaderValue.FIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIterableCsvSingleValue() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
headers.add(HEADER_NAME, HeaderValue.ONE.asList());
|
||||
assertCsvValue(headers, HeaderValue.ONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addIterableCsvEmtpy() {
|
||||
final TextHeaders headers = newCsvTextHeaders();
|
||||
headers.add(HEADER_NAME, Collections.<CharSequence>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<CharSequence> asList() {
|
||||
return Arrays.<CharSequence>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<Integer, HeaderValue> MAP;
|
||||
|
||||
static {
|
||||
final Map<Integer, HeaderValue> map = new HashMap<Integer, HeaderValue>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<AsciiString> CASE_INSENSITIVE_ORDER = new Comparator<AsciiString>() {
|
||||
public static final HashingStrategy<CharSequence> CASE_INSENSITIVE_HASHER =
|
||||
new HashingStrategy<CharSequence>() {
|
||||
@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<AsciiString> CASE_SENSITIVE_ORDER = new Comparator<AsciiString>() {
|
||||
public static final HashingStrategy<CharSequence> CASE_SENSITIVE_HASHER =
|
||||
new HashingStrategy<CharSequence>() {
|
||||
@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> CHARSEQUENCE_CASE_INSENSITIVE_ORDER = new Comparator<CharSequence>() {
|
||||
|
||||
@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> CHARSEQUENCE_CASE_SENSITIVE_ORDER = new Comparator<CharSequence>() {
|
||||
@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.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@ -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<ByteString> DEFAULT_COMPARATOR = new Comparator<ByteString>() {
|
||||
@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;
|
||||
|
75
common/src/main/java/io/netty/util/HashingStrategy.java
Normal file
75
common/src/main/java/io/netty/util/HashingStrategy.java
Normal file
@ -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<T> {
|
||||
/**
|
||||
* Generate a hash code for {@code obj}.
|
||||
* <p>
|
||||
* This method must obey the same relationship that {@link java.lang.Object#hashCode()} has with
|
||||
* {@link java.lang.Object#equals(Object)}:
|
||||
* <ul>
|
||||
* <li>Calling this method multiple times with the same {@code obj} should return the same result</li>
|
||||
* <li>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</li>
|
||||
* <li>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 <strong>not</strong> have to
|
||||
* return different results results. However this property is desirable.</li>
|
||||
* <li>if {@code obj} is {@code null} then this method return {@code 0}</li>
|
||||
* </ul>
|
||||
*/
|
||||
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:
|
||||
* <ul>
|
||||
* <li><i>reflexive</i> - {@code equals(a, a)} should return true</li>
|
||||
* <li><i>symmetric</i> - {@code equals(a, b)} returns {@code true} iff {@code equals(b, a)} returns
|
||||
* {@code true}</li>
|
||||
* <li><i>transitive</i> - 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}</li>
|
||||
* <li><i>consistent</i> - {@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</li>
|
||||
* <li>if {@code a} and {@code b} are both {@code null} then this method returns {@code true}</li>
|
||||
* <li>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}</li>
|
||||
* </ul>
|
||||
*/
|
||||
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));
|
||||
}
|
||||
};
|
||||
}
|
@ -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];
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -264,7 +264,7 @@ public final class HttpUploadClient {
|
||||
*/
|
||||
private static void formpostmultipart(
|
||||
Bootstrap bootstrap, String host, int port, URI uriFile, HttpDataFactory factory,
|
||||
List<Entry<String, String>> headers, List<InterfaceHttpData> bodylist) throws Exception {
|
||||
Iterable<Entry<String, String>> headers, List<InterfaceHttpData> bodylist) throws Exception {
|
||||
// XXX /formpostmultipart
|
||||
// Start the connection attempt.
|
||||
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
|
||||
|
@ -21,7 +21,6 @@ import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
|
@ -62,7 +62,7 @@ public class StompClientHandler extends SimpleChannelInboundHandler<StompFrame>
|
||||
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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -83,6 +83,14 @@ public class HeadersBenchmark extends AbstractMicrobenchmark {
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void httpRemove(Blackhole bh) {
|
||||
for (AsciiString name : httpNames) {
|
||||
bh.consume(httpHeaders.remove(name));
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void httpGet(Blackhole bh) {
|
||||
@ -110,6 +118,14 @@ public class HeadersBenchmark extends AbstractMicrobenchmark {
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void http2Remove(Blackhole bh) {
|
||||
for (ByteString name : http2Names) {
|
||||
bh.consume(http2Headers.remove(name));
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
public void http2Get(Blackhole bh) {
|
||||
|
Loading…
Reference in New Issue
Block a user