diff --git a/NOTICE.txt b/NOTICE.txt index ef5371b9cb..6e6fb8b678 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -64,6 +64,14 @@ Bloch of Google, Inc: * LICENSE: * license/LICENSE.deque.txt (Public Domain) +This product contains a modified portion of 'Apache Harmony', an open source +Java SE, which can be obtained at: + + * LICENSE: + * license/LICENSE.harmony.txt (Apache License 2.0) + * HOMEPAGE: + * http://archive.apache.org/dist/harmony/ + This product contains a modified version of Roland Kuhn's ASL2 AbstractNodeQueue, which is based on Dmitriy Vyukov's non-intrusive MPSC queue. It can be obtained at: @@ -137,3 +145,4 @@ can be obtained at: * license/LICENSE.log4j.txt (Apache License 2.0) * HOMEPAGE: * http://logging.apache.org/log4j/ + diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java index aa90256eaa..a690180d3e 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java @@ -16,53 +16,37 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.AsciiString; +import io.netty.handler.codec.DefaultTextHeaders; +import io.netty.handler.codec.TextHeaders; -import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; import java.util.Set; public class DefaultHttpHeaders extends HttpHeaders { - private static final int BUCKET_SIZE = 17; - - private static int index(int hash) { - return hash % BUCKET_SIZE; - } - - private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE]; - private final HeaderEntry head = new HeaderEntry(); - protected final boolean validate; + private final TextHeaders headers; public DefaultHttpHeaders() { this(true); } public DefaultHttpHeaders(boolean validate) { - this.validate = validate; - head.before = head.after = head; + headers = validate? new ValidatingTextHeaders() : new NonValidatingTextHeaders(); } - void validateHeaderName0(CharSequence headerName) { - validateHeaderName(headerName); + DefaultHttpHeaders(TextHeaders headers) { + this.headers = headers; } @Override public HttpHeaders add(HttpHeaders headers) { if (headers instanceof DefaultHttpHeaders) { - DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) headers; - HeaderEntry e = defaultHttpHeaders.head.after; - while (e != defaultHttpHeaders.head) { - add(e.key, e.value); - e = e.after; - } + this.headers.add(((DefaultHttpHeaders) headers).headers); return this; } else { return super.add(headers); @@ -72,13 +56,7 @@ public class DefaultHttpHeaders extends HttpHeaders { @Override public HttpHeaders set(HttpHeaders headers) { if (headers instanceof DefaultHttpHeaders) { - clear(); - DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) headers; - HeaderEntry e = defaultHttpHeaders.head.after; - while (e != defaultHttpHeaders.head) { - add(e.key, e.value); - e = e.after; - } + this.headers.set(((DefaultHttpHeaders) headers).headers); return this; } else { return super.set(headers); @@ -86,413 +64,329 @@ public class DefaultHttpHeaders extends HttpHeaders { } @Override - public HttpHeaders add(final String name, final Object value) { - return add((CharSequence) name, value); + public HttpHeaders add(String name, Object value) { + headers.add(name, value); + return this; } @Override - public HttpHeaders add(final CharSequence name, final Object value) { - CharSequence strVal; - if (validate) { - validateHeaderName0(name); - strVal = toCharSequence(value); - validateHeaderValue(strVal); - } else { - strVal = toCharSequence(value); - } - int h = hash(name); - int i = index(h); - add0(h, i, name, strVal); + public HttpHeaders add(CharSequence name, Object value) { + headers.add(name, value); return this; } @Override public HttpHeaders add(String name, Iterable values) { - return add((CharSequence) name, values); + headers.add(name, values); + return this; } @Override public HttpHeaders add(CharSequence name, Iterable values) { - if (validate) { - validateHeaderName0(name); - } - int h = hash(name); - int i = index(h); - for (Object v: values) { - CharSequence vstr = toCharSequence(v); - if (validate) { - validateHeaderValue(vstr); - } - add0(h, i, name, vstr); - } - return this; - } - - private void add0(int h, int i, final CharSequence name, final CharSequence value) { - // Update the hash table. - HeaderEntry e = entries[i]; - HeaderEntry newEntry; - entries[i] = newEntry = new HeaderEntry(h, name, value); - newEntry.next = e; - - // Update the linked list. - newEntry.addBefore(head); - } - - @Override - public HttpHeaders remove(final String name) { - return remove((CharSequence) name); - } - - @Override - public HttpHeaders remove(final CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - int h = hash(name); - int i = index(h); - remove0(h, i, name); - return this; - } - - private void remove0(int h, int i, CharSequence name) { - HeaderEntry e = entries[i]; - if (e == null) { - return; - } - - for (;;) { - if (e.hash == h && equalsIgnoreCase(name, e.key)) { - e.remove(); - HeaderEntry next = e.next; - if (next != null) { - entries[i] = next; - e = next; - } else { - entries[i] = null; - return; - } - } else { - break; - } - } - - for (;;) { - HeaderEntry next = e.next; - if (next == null) { - break; - } - if (next.hash == h && equalsIgnoreCase(name, next.key)) { - e.next = next.next; - next.remove(); - } else { - e = next; - } - } - } - - @Override - public HttpHeaders set(final String name, final Object value) { - return set((CharSequence) name, value); - } - - @Override - public HttpHeaders set(final CharSequence name, final Object value) { - CharSequence strVal; - if (validate) { - validateHeaderName0(name); - strVal = toCharSequence(value); - validateHeaderValue(strVal); - } else { - strVal = toCharSequence(value); - } - int h = hash(name); - int i = index(h); - remove0(h, i, name); - add0(h, i, name, strVal); + headers.add(name, values); return this; } @Override - public HttpHeaders set(final String name, final Iterable values) { - return set((CharSequence) name, values); + public HttpHeaders remove(String name) { + headers.remove(name); + return this; } @Override - public HttpHeaders set(final CharSequence name, final Iterable values) { - if (values == null) { - throw new NullPointerException("values"); - } - if (validate) { - validateHeaderName0(name); - } + public HttpHeaders remove(CharSequence name) { + headers.remove(name); + return this; + } - int h = hash(name); - int i = index(h); + @Override + public HttpHeaders set(String name, Object value) { + headers.set(name, value); + return this; + } - remove0(h, i, name); - for (Object v: values) { - if (v == null) { - break; - } - CharSequence strVal = toCharSequence(v); - if (validate) { - validateHeaderValue(strVal); - } - add0(h, i, name, strVal); - } + @Override + public HttpHeaders set(CharSequence name, Object value) { + headers.set(name, value); + return this; + } + @Override + public HttpHeaders set(String name, Iterable values) { + headers.set(name, values); + return this; + } + + @Override + public HttpHeaders set(CharSequence name, Iterable values) { + headers.set(name, values); return this; } @Override public HttpHeaders clear() { - Arrays.fill(entries, null); - head.before = head.after = head; + headers.clear(); return this; } @Override - public String get(final String name) { - return get((CharSequence) name); + public String get(String name) { + return headers.get(name); } @Override - public String get(final CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - - int h = hash(name); - int i = index(h); - HeaderEntry e = entries[i]; - CharSequence value = null; - // loop until the first header was found - while (e != null) { - if (e.hash == h && equalsIgnoreCase(name, e.key)) { - value = e.value; - } - - e = e.next; - } - if (value == null) { - return null; - } - return value.toString(); + public String get(CharSequence name) { + return headers.get(name); } @Override - public List getAll(final String name) { - return getAll((CharSequence) name); + public List getAll(String name) { + return headers.getAll(name); } @Override - public List getAll(final CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - - LinkedList values = new LinkedList(); - - int h = hash(name); - int i = index(h); - HeaderEntry e = entries[i]; - while (e != null) { - if (e.hash == h && equalsIgnoreCase(name, e.key)) { - values.addFirst(e.getValue()); - } - e = e.next; - } - return values; + public List getAll(CharSequence name) { + return headers.getAll(name); } @Override public List> entries() { - List> all = - new LinkedList>(); - - HeaderEntry e = head.after; - while (e != head) { - all.add(e); - e = e.after; - } - return all; + return headers.entries(); } @Override public Iterator> iterator() { - return new HeaderIterator(); + return headers.iterator(); } @Override public boolean contains(String name) { - return get(name) != null; + return headers.contains(name); } @Override public boolean contains(CharSequence name) { - return get(name) != null; + return headers.contains(name); } @Override public boolean isEmpty() { - return head == head.after; + return headers.isEmpty(); } @Override - public boolean contains(String name, String value, boolean ignoreCaseValue) { - return contains((CharSequence) name, (CharSequence) value, ignoreCaseValue); + public boolean contains(String name, String value, boolean ignoreCase) { + return headers.contains(name, value, ignoreCase); } @Override - public boolean contains(CharSequence name, CharSequence value, boolean ignoreCaseValue) { - if (name == null) { - throw new NullPointerException("name"); - } - - int h = hash(name); - int i = index(h); - HeaderEntry e = entries[i]; - while (e != null) { - if (e.hash == h && equalsIgnoreCase(name, e.key)) { - if (ignoreCaseValue) { - if (equalsIgnoreCase(e.value, value)) { - return true; - } - } else { - if (e.value.equals(value)) { - return true; - } - } - } - e = e.next; - } - return false; + public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { + return headers.contains(name, value, ignoreCase); } @Override public Set names() { - Set names = new LinkedHashSet(); - HeaderEntry e = head.after; - while (e != head) { - names.add(e.getKey()); - e = e.after; - } - return names; + return headers.names(); } void encode(ByteBuf buf) { - HeaderEntry e = head.after; - while (e != head) { - e.encode(buf); - e = e.after; - } + headers.forEachEntry(new HttpHeadersEncoder(buf)); } - private static CharSequence toCharSequence(Object value) { - if (value == null) { - return null; - } - if (value instanceof CharSequence) { - return (CharSequence) value; - } - 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 final class HeaderIterator implements Iterator> { - - private HeaderEntry current = head; - + static class NonValidatingTextHeaders extends DefaultTextHeaders { @Override - public boolean hasNext() { - return current.after != head; - } - - @Override - public Entry next() { - current = current.after; - - if (current == head) { - throw new NoSuchElementException(); - } - - return current; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - private final class HeaderEntry implements Map.Entry { - final int hash; - final CharSequence key; - CharSequence value; - HeaderEntry next; - HeaderEntry before, after; - - HeaderEntry(int hash, CharSequence key, CharSequence value) { - this.hash = hash; - this.key = key; - this.value = value; - } - - HeaderEntry() { - hash = -1; - key = null; - value = null; - } - - void remove() { - before.after = after; - after.before = before; - } - - void addBefore(HeaderEntry e) { - after = e; - before = e.before; - before.after = this; - after.before = this; - } - - @Override - public String getKey() { - return key.toString(); - } - - @Override - public String getValue() { - return value.toString(); - } - - @Override - public String setValue(String value) { + protected CharSequence convertValue(Object value) { if (value == null) { throw new NullPointerException("value"); } - validateHeaderValue(value); - CharSequence oldValue = this.value; - this.value = value; - return oldValue.toString(); + + CharSequence seq; + if (value instanceof CharSequence) { + seq = (CharSequence) value; + } else if (value instanceof Number) { + seq = value.toString(); + } else if (value instanceof Date) { + seq = HttpHeaderDateFormat.get().format((Date) value); + } else if (value instanceof Calendar) { + seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime()); + } else { + seq = value.toString(); + } + + return seq; + } + } + + static class ValidatingTextHeaders extends NonValidatingTextHeaders { + private static final int HIGHEST_INVALID_NAME_CHAR_MASK = ~63; + private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15; + + /** + * A look-up table used for checking if a character in a header name is prohibited. + */ + private static final byte[] LOOKUP_TABLE = new byte[~HIGHEST_INVALID_NAME_CHAR_MASK + 1]; + + static { + LOOKUP_TABLE['\t'] = -1; + LOOKUP_TABLE['\n'] = -1; + LOOKUP_TABLE[0x0b] = -1; + LOOKUP_TABLE['\f'] = -1; + LOOKUP_TABLE[' '] = -1; + LOOKUP_TABLE[','] = -1; + LOOKUP_TABLE[':'] = -1; + LOOKUP_TABLE[';'] = -1; + LOOKUP_TABLE['='] = -1; } @Override - public String toString() { - return key.toString() + '=' + value.toString(); + protected CharSequence convertName(CharSequence name) { + name = super.convertName(name); + if (name instanceof AsciiString) { + validateName((AsciiString) name); + } else { + validateName(name); + } + + return name; } - void encode(ByteBuf buf) { - HttpHeaders.encode(key, value, buf); + private static void validateName(AsciiString name) { + // Go through each characters in the name + final int start = name.arrayOffset(); + final int end = start + name.length(); + final byte[] array = name.array(); + for (int index = start; index < end; index ++) { + byte b = array[index]; + + // Check to see if the character is not an ASCII character + if (b < 0) { + throw new IllegalArgumentException( + "a header name cannot contain non-ASCII characters: " + name); + } + + // Check for prohibited characters. + validateNameChar(name, b); + } + } + + private static void validateName(CharSequence name) { + // Go through each characters 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. + validateNameChar(name, character); + } + } + + private static void validateNameChar(CharSequence name, int character) { + 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); + } + } + + @Override + protected CharSequence convertValue(Object value) { + CharSequence seq = super.convertValue(value); + if (value instanceof AsciiString) { + validateValue((AsciiString) seq); + } else { + validateValue(seq); + } + + return seq; + } + + private static void validateValue(AsciiString seq) { + int state = 0; + // Start looping through each of the character + final int start = seq.arrayOffset(); + final int end = start + seq.length(); + final byte[] array = seq.array(); + for (int index = start; index < end; index ++) { + state = validateValueChar(seq, state, (char) (array[index] & 0xFF)); + } + + if (state != 0) { + throw new IllegalArgumentException( + "a header value must not end with '\\r' or '\\n':" + seq); + } + } + + private static void validateValue(CharSequence seq) { + int state = 0; + // Start looping through each of the character + for (int index = 0; index < seq.length(); index ++) { + state = validateValueChar(seq, state, seq.charAt(index)); + } + + if (state != 0) { + throw new IllegalArgumentException( + "a header value must not end with '\\r' or '\\n':" + seq); + } + } + + private static int validateValueChar(CharSequence seq, int state, char character) { + /* + * State: + * + * 0: Previous character was neither CR nor LF + * 1: The previous character was CR + * 2: The previous character was LF + */ + if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) { + // Check the absolutely prohibited characters. + switch (character) { + case 0x0b: // Vertical tab + throw new IllegalArgumentException( + "a header value contains a prohibited character '\\v': " + seq); + case '\f': + throw new IllegalArgumentException( + "a header value contains a prohibited character '\\f': " + seq); + } + } + + // Check the CRLF (HT | SP) pattern + switch (state) { + case 0: + switch (character) { + case '\r': + state = 1; + break; + case '\n': + state = 2; + break; + } + break; + case 1: + switch (character) { + case '\n': + state = 2; + break; + default: + throw new IllegalArgumentException( + "only '\\n' is allowed after '\\r': " + seq); + } + break; + case 2: + switch (character) { + case '\t': case ' ': + state = 0; + break; + default: + throw new IllegalArgumentException( + "only ' ' and '\\t' are allowed after '\\n': " + seq); + } + } + return state; } } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java index 1fff4a3ab2..523c98321f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java @@ -17,6 +17,8 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.DefaultHttpHeaders.NonValidatingTextHeaders; +import io.netty.handler.codec.http.DefaultHttpHeaders.ValidatingTextHeaders; import io.netty.util.internal.StringUtil; import java.util.Map; @@ -39,7 +41,8 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt public DefaultLastHttpContent(ByteBuf content, boolean validateHeaders) { super(content); - trailingHeaders = new TrailingHeaders(validateHeaders); + trailingHeaders = new DefaultHttpHeaders( + validateHeaders? new ValidatingTrailingTextHeaders() : new NonValidatingTextHeaders()); this.validateHeaders = validateHeaders; } @@ -106,20 +109,17 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt } } - private static final class TrailingHeaders extends DefaultHttpHeaders { - TrailingHeaders(boolean validate) { - super(validate); - } - + private static final class ValidatingTrailingTextHeaders extends ValidatingTextHeaders { @Override - void validateHeaderName0(CharSequence name) { - super.validateHeaderName0(name); - if (equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) || - equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) || - equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) { + protected CharSequence convertName(CharSequence name) { + name = super.convertName(name); + if (HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) || + HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) || + HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) { throw new IllegalArgumentException( "prohibited trailing header: " + name); } + return name; } } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderEntity.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderEntity.java deleted file mode 100644 index c667782bc9..0000000000 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderEntity.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package io.netty.handler.codec.http; - -import io.netty.buffer.ByteBuf; -import io.netty.util.CharsetUtil; - -final class HttpHeaderEntity implements CharSequence { - - private final String name; - private final int hash; - private final byte[] bytes; - - public HttpHeaderEntity(String name) { - this.name = name; - hash = HttpHeaders.hash(name); - bytes = name.getBytes(CharsetUtil.US_ASCII); - } - - int hash() { - return hash; - } - - @Override - public int length() { - return bytes.length; - } - - @Override - public char charAt(int index) { - return (char) bytes[index]; - } - - @Override - public CharSequence subSequence(int start, int end) { - return new HttpHeaderEntity(name.substring(start, end)); - } - - @Override - public String toString() { - return name; - } - - void encode(ByteBuf buf) { - buf.writeBytes(bytes); - } -} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java index ebb6eb5ff6..1181f8cc42 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java @@ -16,6 +16,7 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.AsciiString; import java.text.ParseException; import java.util.Calendar; @@ -1146,115 +1147,6 @@ public abstract class HttpHeaders implements Iterable> } } - /** - * Validates the name of a header - * - * @param headerName The header name being validated - */ - static void validateHeaderName(CharSequence headerName) { - //Check to see if the name is null - if (headerName == null) { - throw new NullPointerException("Header names cannot be null"); - } - //Go through each of the characters in the name - for (int index = 0; index < headerName.length(); index ++) { - //Actually get the character - char character = headerName.charAt(index); - - //Check to see if the character is not an ASCII character - if (character > 127) { - throw new IllegalArgumentException( - "Header name cannot contain non-ASCII characters: " + headerName); - } - - //Check for prohibited characters. - switch (character) { - case '\t': case '\n': case 0x0b: case '\f': case '\r': - case ' ': case ',': case ':': case ';': case '=': - throw new IllegalArgumentException( - "Header name cannot contain the following prohibited characters: " + - "=,;: \\t\\r\\n\\v\\f: " + headerName); - } - } - } - - /** - * Validates the specified header value - * - * @param headerValue The value being validated - */ - static void validateHeaderValue(CharSequence headerValue) { - //Check to see if the value is null - if (headerValue == null) { - throw new NullPointerException("Header values cannot be null"); - } - - /* - * Set up the state of the validation - * - * States are as follows: - * - * 0: Previous character was neither CR nor LF - * 1: The previous character was CR - * 2: The previous character was LF - */ - int state = 0; - - //Start looping through each of the character - - for (int index = 0; index < headerValue.length(); index ++) { - char character = headerValue.charAt(index); - - //Check the absolutely prohibited characters. - switch (character) { - case 0x0b: // Vertical tab - throw new IllegalArgumentException( - "Header value contains a prohibited character '\\v': " + headerValue); - case '\f': - throw new IllegalArgumentException( - "Header value contains a prohibited character '\\f': " + headerValue); - } - - // Check the CRLF (HT | SP) pattern - switch (state) { - case 0: - switch (character) { - case '\r': - state = 1; - break; - case '\n': - state = 2; - break; - } - break; - case 1: - switch (character) { - case '\n': - state = 2; - break; - default: - throw new IllegalArgumentException( - "Only '\\n' is allowed after '\\r': " + headerValue); - } - break; - case 2: - switch (character) { - case '\t': case ' ': - state = 0; - break; - default: - throw new IllegalArgumentException( - "Only ' ' and '\\t' are allowed after '\\n': " + headerValue); - } - } - } - - if (state != 0) { - throw new IllegalArgumentException( - "Header value must not end with '\\r' or '\\n':" + headerValue); - } - } - /** * Checks to see if the transfer encoding in a specified {@link HttpMessage} is chunked * @@ -1329,28 +1221,6 @@ public abstract class HttpHeaders implements Iterable> return true; } - static int hash(CharSequence name) { - if (name instanceof HttpHeaderEntity) { - return ((HttpHeaderEntity) name).hash(); - } - int h = 0; - for (int i = name.length() - 1; i >= 0; i --) { - char c = name.charAt(i); - if (c >= 'A' && c <= 'Z') { - c += 32; - } - h = 31 * h + c; - } - - if (h > 0) { - return h; - } else if (h == Integer.MIN_VALUE) { - return Integer.MAX_VALUE; - } else { - return -h; - } - } - static void encode(HttpHeaders headers, ByteBuf buf) { if (headers instanceof DefaultHttpHeaders) { ((DefaultHttpHeaders) headers).encode(buf); @@ -1361,7 +1231,7 @@ public abstract class HttpHeaders implements Iterable> } } - static void encode(CharSequence key, CharSequence value, ByteBuf buf) { + private static void encode(CharSequence key, CharSequence value, ByteBuf buf) { encodeAscii(key, buf); buf.writeBytes(HEADER_SEPERATOR); encodeAscii(value, buf); @@ -1369,8 +1239,8 @@ public abstract class HttpHeaders implements Iterable> } public static void encodeAscii(CharSequence seq, ByteBuf buf) { - if (seq instanceof HttpHeaderEntity) { - ((HttpHeaderEntity) seq).encode(buf); + if (seq instanceof AsciiString) { + ((AsciiString) seq).copy(0, buf, seq.length()); } else { encodeAscii0(seq, buf); } @@ -1391,7 +1261,7 @@ public abstract class HttpHeaders implements Iterable> if (name == null) { throw new NullPointerException("name"); } - return new HttpHeaderEntity(name); + return new AsciiString(name); } protected HttpHeaders() { } @@ -1621,14 +1491,14 @@ public abstract class HttpHeaders implements Iterable> /** * @see {@link #contains(CharSequence, CharSequence, boolean)} */ - public boolean contains(String name, String value, boolean ignoreCaseValue) { + public boolean contains(String name, String value, boolean ignoreCase) { List values = getAll(name); if (values.isEmpty()) { return false; } for (String v: values) { - if (ignoreCaseValue) { + if (ignoreCase) { if (equalsIgnoreCase(v, value)) { return true; } @@ -1644,12 +1514,12 @@ public abstract class HttpHeaders implements Iterable> /** * Returns {@code true} if a header with the name and value exists. * - * @param name the headername - * @param value the value - * @param ignoreCaseValue {@code true} if case should be ignored - * @return contains {@code true} if it contains it {@code false} otherwise + * @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 */ - public boolean contains(CharSequence name, CharSequence value, boolean ignoreCaseValue) { - return contains(name.toString(), value.toString(), ignoreCaseValue); + public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { + return contains(name.toString(), value.toString(), ignoreCase); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java new file mode 100644 index 0000000000..fccd251509 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java @@ -0,0 +1,72 @@ +/* + * 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.http; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.AsciiString; +import io.netty.handler.codec.TextHeaderProcessor; + +final class HttpHeadersEncoder implements TextHeaderProcessor { + + private final ByteBuf buf; + + HttpHeadersEncoder(ByteBuf buf) { + this.buf = buf; + } + + @Override + public boolean process(CharSequence name, CharSequence value) throws Exception { + final ByteBuf buf = this.buf; + final int nameLen = name.length(); + final int valueLen = value.length(); + final int entryLen = nameLen + valueLen + 4; + int offset = buf.writerIndex(); + buf.ensureWritable(entryLen); + writeAscii(buf, offset, name, nameLen); + offset += nameLen; + buf.setByte(offset ++, ':'); + buf.setByte(offset ++, ' '); + writeAscii(buf, offset, value, valueLen); + offset += valueLen; + buf.setByte(offset ++, '\r'); + buf.setByte(offset ++, '\n'); + buf.writerIndex(offset); + return true; + } + + private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) { + if (value instanceof AsciiString) { + writeAsciiString(buf, offset, (AsciiString) value, valueLen); + } else { + writeCharSequence(buf, offset, value, valueLen); + } + } + + private static void writeAsciiString(ByteBuf buf, int offset, AsciiString value, int valueLen) { + value.copy(0, buf, offset, valueLen); + } + + private static void writeCharSequence(ByteBuf buf, int offset, CharSequence value, int valueLen) { + for (int i = 0; i < valueLen; i ++) { + buf.setByte(offset ++, c2b(value.charAt(i))); + } + } + + private static int c2b(char ch) { + return ch < 256? (byte) ch : '?'; + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java index f0278d9b7e..af623201c3 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java @@ -18,7 +18,7 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; import io.netty.util.CharsetUtil; -import static io.netty.handler.codec.http.HttpConstants.SP; +import static io.netty.handler.codec.http.HttpConstants.*; /** * The response code and its description of HTTP or its derived protocols, such as @@ -453,6 +453,36 @@ public class HttpResponseStatus implements Comparable { return new HttpResponseStatus(code, reasonPhrase + " (" + code + ')'); } + /** + * Parses the specified HTTP status line into a {@link HttpResponseStatus}. The expected formats of the line are: + *
    + *
  • {@code statusCode} (e.g. 200)
  • + *
  • {@code statusCode} {@code reasonPhrase} (e.g. 404 Not Found)
  • + *
+ * + * @throws IllegalArgumentException if the specified status line is malformed + */ + public static HttpResponseStatus parseLine(CharSequence line) { + String status = line.toString(); + try { + int space = status.indexOf(' '); + if (space == -1) { + return valueOf(Integer.parseInt(status)); + } else { + int code = Integer.parseInt(status.substring(0, space)); + String reasonPhrase = status.substring(space + 1); + HttpResponseStatus responseStatus = valueOf(code); + if (responseStatus.reasonPhrase().equals(reasonPhrase)) { + return responseStatus; + } else { + return new HttpResponseStatus(code, reasonPhrase); + } + } + } catch (Exception e) { + throw new IllegalArgumentException("malformed status line: " + status, e); + } + } + private final int code; private final String reasonPhrase; diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java index 6367041163..a00aab5579 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java @@ -15,365 +15,101 @@ */ package io.netty.handler.codec.spdy; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.TreeSet; +import io.netty.handler.codec.AsciiString; +import io.netty.handler.codec.DefaultTextHeaders; +import io.netty.handler.codec.TextHeaderProcessor; +import io.netty.handler.codec.TextHeaders; + +import java.util.Locale; -public class DefaultSpdyHeaders extends SpdyHeaders { - - private static final int BUCKET_SIZE = 17; - - private static int hash(String name) { - int h = 0; - for (int i = name.length() - 1; i >= 0; i --) { - char c = name.charAt(i); - if (c >= 'A' && c <= 'Z') { - c += 32; - } - h = 31 * h + c; - } - - if (h > 0) { - return h; - } else if (h == Integer.MIN_VALUE) { - return Integer.MAX_VALUE; +public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeaders { + @Override + protected CharSequence convertName(CharSequence name) { + name = super.convertName(name); + if (name instanceof AsciiString) { + name = ((AsciiString) name).toLowerCase(); } else { - return -h; + name = name.toString().toLowerCase(Locale.US); } - } - - private static boolean eq(String name1, String name2) { - int nameLen = name1.length(); - if (nameLen != name2.length()) { - return false; - } - - for (int i = nameLen - 1; i >= 0; i --) { - char c1 = name1.charAt(i); - char c2 = name2.charAt(i); - if (c1 != c2) { - if (c1 >= 'A' && c1 <= 'Z') { - c1 += 32; - } - if (c2 >= 'A' && c2 <= 'Z') { - c2 += 32; - } - if (c1 != c2) { - return false; - } - } - } - return true; - } - - private static int index(int hash) { - return hash % BUCKET_SIZE; - } - - private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE]; - private final HeaderEntry head = new HeaderEntry(-1, null, null); - - DefaultSpdyHeaders() { - head.before = head.after = head; + SpdyCodecUtil.validateHeaderName(name); + return name; } @Override - public SpdyHeaders add(final String name, final Object value) { - String lowerCaseName = name.toLowerCase(); - SpdyCodecUtil.validateHeaderName(lowerCaseName); - String strVal = toString(value); - SpdyCodecUtil.validateHeaderValue(strVal); - int h = hash(lowerCaseName); - int i = index(h); - add0(h, i, lowerCaseName, strVal); - return this; - } + protected CharSequence convertValue(Object value) { + if (value == null) { + throw new NullPointerException("value"); + } - private void add0(int h, int i, final String name, final String value) { - // Update the hash table. - HeaderEntry e = entries[i]; - HeaderEntry newEntry; - entries[i] = newEntry = new HeaderEntry(h, name, value); - newEntry.next = e; + CharSequence seq; + if (value instanceof CharSequence) { + seq = (CharSequence) value; + } else { + seq = value.toString(); + } - // Update the linked list. - newEntry.addBefore(head); + SpdyCodecUtil.validateHeaderValue(seq); + return seq; } @Override - public SpdyHeaders remove(final String name) { - if (name == null) { - throw new NullPointerException("name"); - } - String lowerCaseName = name.toLowerCase(); - int h = hash(lowerCaseName); - int i = index(h); - remove0(h, i, lowerCaseName); - return this; - } - - private void remove0(int h, int i, String name) { - HeaderEntry e = entries[i]; - if (e == null) { - return; - } - - for (;;) { - if (e.hash == h && eq(name, e.key)) { - e.remove(); - HeaderEntry next = e.next; - if (next != null) { - entries[i] = next; - e = next; - } else { - entries[i] = null; - return; - } - } else { - break; - } - } - - for (;;) { - HeaderEntry next = e.next; - if (next == null) { - break; - } - if (next.hash == h && eq(name, next.key)) { - e.next = next.next; - next.remove(); - } else { - e = next; - } - } - } - - @Override - public SpdyHeaders set(final String name, final Object value) { - String lowerCaseName = name.toLowerCase(); - SpdyCodecUtil.validateHeaderName(lowerCaseName); - String strVal = toString(value); - SpdyCodecUtil.validateHeaderValue(strVal); - int h = hash(lowerCaseName); - int i = index(h); - remove0(h, i, lowerCaseName); - add0(h, i, lowerCaseName, strVal); + public SpdyHeaders add(CharSequence name, Object value) { + super.add(name, value); return this; } @Override - public SpdyHeaders set(final String name, final Iterable values) { - if (values == null) { - throw new NullPointerException("values"); - } + public SpdyHeaders add(CharSequence name, Iterable values) { + super.add(name, values); + return this; + } - String lowerCaseName = name.toLowerCase(); - SpdyCodecUtil.validateHeaderName(lowerCaseName); + @Override + public SpdyHeaders add(CharSequence name, Object... values) { + super.add(name, values); + return this; + } - int h = hash(lowerCaseName); - int i = index(h); + @Override + public SpdyHeaders add(TextHeaders headers) { + super.add(headers); + return this; + } - remove0(h, i, lowerCaseName); - for (Object v: values) { - if (v == null) { - break; - } - String strVal = toString(v); - SpdyCodecUtil.validateHeaderValue(strVal); - add0(h, i, lowerCaseName, strVal); - } + @Override + public SpdyHeaders set(CharSequence name, Object value) { + super.set(name, value); + return this; + } + + @Override + public SpdyHeaders set(CharSequence name, Object... values) { + super.set(name, values); + return this; + } + + @Override + public SpdyHeaders set(CharSequence name, Iterable values) { + super.set(name, values); + return this; + } + + @Override + public SpdyHeaders set(TextHeaders headers) { + super.set(headers); return this; } @Override public SpdyHeaders clear() { - for (int i = 0; i < entries.length; i ++) { - entries[i] = null; - } - head.before = head.after = head; + super.clear(); return this; } @Override - public String get(final String name) { - if (name == null) { - throw new NullPointerException("name"); - } - - int h = hash(name); - int i = index(h); - HeaderEntry e = entries[i]; - while (e != null) { - if (e.hash == h && eq(name, e.key)) { - return e.value; - } - - e = e.next; - } - return null; - } - - @Override - public List getAll(final String name) { - if (name == null) { - throw new NullPointerException("name"); - } - - LinkedList values = new LinkedList(); - - int h = hash(name); - int i = index(h); - HeaderEntry e = entries[i]; - while (e != null) { - if (e.hash == h && eq(name, e.key)) { - values.addFirst(e.value); - } - e = e.next; - } - return values; - } - - @Override - public List> entries() { - List> all = - new LinkedList>(); - - HeaderEntry e = head.after; - while (e != head) { - all.add(e); - e = e.after; - } - return all; - } - - @Override - public Iterator> iterator() { - return new HeaderIterator(); - } - - @Override - public boolean contains(String name) { - return get(name) != null; - } - - @Override - public Set names() { - Set names = new TreeSet(); - - HeaderEntry e = head.after; - while (e != head) { - names.add(e.key); - e = e.after; - } - return names; - } - - @Override - public SpdyHeaders add(String name, Iterable values) { - SpdyCodecUtil.validateHeaderValue(name); - int h = hash(name); - int i = index(h); - for (Object v: values) { - String vstr = toString(v); - SpdyCodecUtil.validateHeaderValue(vstr); - add0(h, i, name, vstr); - } + public SpdyHeaders forEachEntry(TextHeaderProcessor processor) { + super.forEachEntry(processor); return this; } - - @Override - public boolean isEmpty() { - return head == head.after; - } - - private static String toString(Object value) { - if (value == null) { - return null; - } - return value.toString(); - } - - private final class HeaderIterator implements Iterator> { - - private HeaderEntry current = head; - - @Override - public boolean hasNext() { - return current.after != head; - } - - @Override - public Entry next() { - current = current.after; - - if (current == head) { - throw new NoSuchElementException(); - } - - return current; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - private static final class HeaderEntry implements Map.Entry { - final int hash; - final String key; - String value; - HeaderEntry next; - HeaderEntry before, after; - - HeaderEntry(int hash, String key, String value) { - this.hash = hash; - this.key = key; - this.value = value; - } - - void remove() { - before.after = after; - after.before = before; - } - - void addBefore(HeaderEntry e) { - after = e; - before = e.before; - before.after = this; - after.before = this; - } - - @Override - public String getKey() { - return key; - } - - @Override - public String getValue() { - return value; - } - - @Override - public String setValue(String value) { - if (value == null) { - throw new NullPointerException("value"); - } - SpdyCodecUtil.validateHeaderValue(value); - String oldValue = this.value; - this.value = value; - return oldValue; - } - - @Override - public String toString() { - return key + '=' + value; - } - } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java index b663a7ecfd..612ebdbcb2 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java @@ -285,11 +285,11 @@ final class SpdyCodecUtil { /** * Validate a SPDY header name. */ - static void validateHeaderName(String name) { + static void validateHeaderName(CharSequence name) { if (name == null) { throw new NullPointerException("name"); } - if (name.isEmpty()) { + if (name.length() == 0) { throw new IllegalArgumentException( "name cannot be length zero"); } @@ -315,7 +315,7 @@ final class SpdyCodecUtil { /** * Validate a SPDY header value. Does not validate max length. */ - static void validateHeaderValue(String value) { + static void validateHeaderValue(CharSequence value) { if (value == null) { throw new NullPointerException("value"); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java index aebaa08043..ec220db5b5 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java @@ -15,399 +15,75 @@ */ package io.netty.handler.codec.spdy; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpVersion; - -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; +import io.netty.handler.codec.AsciiString; +import io.netty.handler.codec.TextHeaderProcessor; +import io.netty.handler.codec.TextHeaders; /** * Provides the constants for the standard SPDY HTTP header names and commonly * used utility methods that access a {@link SpdyHeadersFrame}. */ -public abstract class SpdyHeaders implements Iterable> { - - public static final SpdyHeaders EMPTY_HEADERS = new SpdyHeaders() { - - @Override - public List getAll(String name) { - return Collections.emptyList(); - } - - @Override - public List> entries() { - return Collections.emptyList(); - } - - @Override - public boolean contains(String name) { - return false; - } - - @Override - public boolean isEmpty() { - return true; - } - - @Override - public Set names() { - return Collections.emptySet(); - } - - @Override - public SpdyHeaders add(String name, Object value) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public SpdyHeaders add(String name, Iterable values) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public SpdyHeaders set(String name, Object value) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public SpdyHeaders set(String name, Iterable values) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public SpdyHeaders remove(String name) { - throw new UnsupportedOperationException("read only"); - } - - @Override - public SpdyHeaders clear() { - throw new UnsupportedOperationException("read only"); - } - - @Override - public Iterator> iterator() { - return entries().iterator(); - } - - @Override - public String get(String name) { - return null; - } - }; +public interface SpdyHeaders extends TextHeaders { /** * SPDY HTTP header names */ - public static final class HttpNames { + final class HttpNames { /** * {@code ":host"} */ - public static final String HOST = ":host"; + public static final AsciiString HOST = new AsciiString(":host"); /** * {@code ":method"} */ - public static final String METHOD = ":method"; + public static final AsciiString METHOD = new AsciiString(":method"); /** * {@code ":path"} */ - public static final String PATH = ":path"; + public static final AsciiString PATH = new AsciiString(":path"); /** * {@code ":scheme"} */ - public static final String SCHEME = ":scheme"; + public static final AsciiString SCHEME = new AsciiString(":scheme"); /** * {@code ":status"} */ - public static final String STATUS = ":status"; + public static final AsciiString STATUS = new AsciiString(":status"); /** * {@code ":version"} */ - public static final String VERSION = ":version"; + public static final AsciiString VERSION = new AsciiString(":version"); private HttpNames() { } } - /** - * Returns the header value with the specified header name. If there are - * more than one header value for the specified header name, the first - * value is returned. - * - * @return the header value or {@code null} if there is no such header - */ - public static String getHeader(SpdyHeadersFrame frame, String name) { - return frame.headers().get(name); - } - - /** - * Returns the header value with the specified header name. If there are - * more than one header value for the specified header name, the first - * value is returned. - * - * @return the header value or the {@code defaultValue} if there is no such - * header - */ - public static String getHeader(SpdyHeadersFrame frame, String name, String defaultValue) { - String value = frame.headers().get(name); - if (value == null) { - return defaultValue; - } - return value; - } - - /** - * Sets a new header with the specified name and value. If there is an - * existing header with the same name, the existing header is removed. - */ - public static void setHeader(SpdyHeadersFrame frame, String name, Object value) { - frame.headers().set(name, value); - } - - /** - * Sets a new header with the specified name and values. If there is an - * existing header with the same name, the existing header is removed. - */ - public static void setHeader(SpdyHeadersFrame frame, String name, Iterable values) { - frame.headers().set(name, values); - } - - /** - * Adds a new header with the specified name and value. - */ - public static void addHeader(SpdyHeadersFrame frame, String name, Object value) { - frame.headers().add(name, value); - } - - /** - * Removes the SPDY host header. - */ - public static void removeHost(SpdyHeadersFrame frame) { - frame.headers().remove(HttpNames.HOST); - } - - /** - * Returns the SPDY host header. - */ - public static String getHost(SpdyHeadersFrame frame) { - return frame.headers().get(HttpNames.HOST); - } - - /** - * Set the SPDY host header. - */ - public static void setHost(SpdyHeadersFrame frame, String host) { - frame.headers().set(HttpNames.HOST, host); - } - - /** - * Removes the HTTP method header. - */ - public static void removeMethod(int spdyVersion, SpdyHeadersFrame frame) { - frame.headers().remove(HttpNames.METHOD); - } - - /** - * Returns the {@link HttpMethod} represented by the HTTP method header. - */ - public static HttpMethod getMethod(int spdyVersion, SpdyHeadersFrame frame) { - try { - return HttpMethod.valueOf(frame.headers().get(HttpNames.METHOD)); - } catch (Exception e) { - return null; - } - } - - /** - * Sets the HTTP method header. - */ - public static void setMethod(int spdyVersion, SpdyHeadersFrame frame, HttpMethod method) { - frame.headers().set(HttpNames.METHOD, method.name()); - } - - /** - * Removes the URL scheme header. - */ - public static void removeScheme(int spdyVersion, SpdyHeadersFrame frame) { - frame.headers().remove(HttpNames.SCHEME); - } - - /** - * Returns the value of the URL scheme header. - */ - public static String getScheme(int spdyVersion, SpdyHeadersFrame frame) { - return frame.headers().get(HttpNames.SCHEME); - } - - /** - * Sets the URL scheme header. - */ - public static void setScheme(int spdyVersion, SpdyHeadersFrame frame, String scheme) { - frame.headers().set(HttpNames.SCHEME, scheme); - } - - /** - * Removes the HTTP response status header. - */ - public static void removeStatus(int spdyVersion, SpdyHeadersFrame frame) { - frame.headers().remove(HttpNames.STATUS); - } - - /** - * Returns the {@link HttpResponseStatus} represented by the HTTP response status header. - */ - public static HttpResponseStatus getStatus(int spdyVersion, SpdyHeadersFrame frame) { - try { - String status = frame.headers().get(HttpNames.STATUS); - int space = status.indexOf(' '); - if (space == -1) { - return HttpResponseStatus.valueOf(Integer.parseInt(status)); - } else { - int code = Integer.parseInt(status.substring(0, space)); - String reasonPhrase = status.substring(space + 1); - HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code); - if (responseStatus.reasonPhrase().equals(reasonPhrase)) { - return responseStatus; - } else { - return new HttpResponseStatus(code, reasonPhrase); - } - } - } catch (Exception e) { - return null; - } - } - - /** - * Sets the HTTP response status header. - */ - public static void setStatus(int spdyVersion, SpdyHeadersFrame frame, HttpResponseStatus status) { - frame.headers().set(HttpNames.STATUS, status.toString()); - } - - /** - * Removes the URL path header. - */ - public static void removeUrl(int spdyVersion, SpdyHeadersFrame frame) { - frame.headers().remove(HttpNames.PATH); - } - - /** - * Returns the value of the URL path header. - */ - public static String getUrl(int spdyVersion, SpdyHeadersFrame frame) { - return frame.headers().get(HttpNames.PATH); - } - - /** - * Sets the URL path header. - */ - public static void setUrl(int spdyVersion, SpdyHeadersFrame frame, String path) { - frame.headers().set(HttpNames.PATH, path); - } - - /** - * Removes the HTTP version header. - */ - public static void removeVersion(int spdyVersion, SpdyHeadersFrame frame) { - frame.headers().remove(HttpNames.VERSION); - } - - /** - * Returns the {@link HttpVersion} represented by the HTTP version header. - */ - public static HttpVersion getVersion(int spdyVersion, SpdyHeadersFrame frame) { - try { - return HttpVersion.valueOf(frame.headers().get(HttpNames.VERSION)); - } catch (Exception e) { - return null; - } - } - - /** - * Sets the HTTP version header. - */ - public static void setVersion(int spdyVersion, SpdyHeadersFrame frame, HttpVersion httpVersion) { - frame.headers().set(HttpNames.VERSION, httpVersion.text()); - } + @Override + SpdyHeaders add(CharSequence name, Object value); @Override - public Iterator> iterator() { - return entries().iterator(); - } + SpdyHeaders add(CharSequence name, Iterable values); - /** - * Returns the header value with the specified header name. If there is - * more than one header value for the specified header name, the first - * value is returned. - * - * @return the header value or {@code null} if there is no such header - */ - public abstract String get(String name); + @Override + SpdyHeaders add(CharSequence name, Object... values); - /** - * Returns the header values with the specified header name. - * - * @return the {@link List} of header values. An empty list if there is no - * such header. - */ - public abstract List getAll(String name); + @Override + SpdyHeaders add(TextHeaders headers); - /** - * Returns all header names and values that this frame contains. - * - * @return the {@link List} of the header name-value pairs. An empty list - * if there is no header in this message. - */ - public abstract List> entries(); + @Override + SpdyHeaders set(CharSequence name, Object value); - /** - * Returns {@code true} if and only if there is a header with the specified - * header name. - */ - public abstract boolean contains(String name); + @Override + SpdyHeaders set(CharSequence name, Iterable values); - /** - * Returns the {@link Set} of all header names that this frame contains. - */ - public abstract Set names(); + @Override + SpdyHeaders set(CharSequence name, Object... values); - /** - * Adds a new header with the specified name and value. - */ - public abstract SpdyHeaders add(String name, Object value); + @Override + SpdyHeaders set(TextHeaders headers); - /** - * Adds a new header with the specified name and values. If there is an - * existing header with the same name, the existing header is removed. - */ - public abstract SpdyHeaders add(String name, Iterable values); + @Override + SpdyHeaders clear(); - /** - * Sets a new header with the specified name and value. If there is an - * existing header with the same name, the existing header is removed. - */ - public abstract SpdyHeaders set(String name, Object value); - - /** - * Sets a new header with the specified name and values. If there is an - * existing header with the same name, the existing header is removed. - */ - public abstract SpdyHeaders set(String name, Iterable values); - - /** - * Removes the header with the specified name. - */ - public abstract SpdyHeaders remove(String name); - - /** - * Removes all headers from this frame. - */ - public abstract SpdyHeaders clear(); - - /** - * Checks if no header exists. - */ - public abstract boolean isEmpty(); + @Override + SpdyHeaders forEachEntry(TextHeaderProcessor processor); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java index 6ad8cf2a95..623740fdc1 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java @@ -33,6 +33,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*; + /** * Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s, * and {@link SpdyDataFrame}s into {@link FullHttpRequest}s and {@link FullHttpResponse}s. @@ -111,7 +113,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { return; } - String URL = SpdyHeaders.getUrl(spdyVersion, spdySynStreamFrame); + String URL = spdySynStreamFrame.headers().get(PATH); // If a client receives a SYN_STREAM without a 'url' header // it must reply with a RST_STREAM with error code PROTOCOL_ERROR @@ -148,7 +150,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { // Response body will follow in a series of Data Frames putMessage(streamId, httpResponseWithEntity); } - } catch (Exception e) { + } catch (Exception ignored) { SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR); ctx.writeAndFlush(spdyRstStreamFrame); @@ -161,10 +163,9 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { if (spdySynStreamFrame.isTruncated()) { SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId); spdySynReplyFrame.setLast(true); - SpdyHeaders.setStatus(spdyVersion, - spdySynReplyFrame, - HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE); - SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0); + SpdyHeaders frameHeaders = spdySynReplyFrame.headers(); + frameHeaders.set(STATUS, HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE); + frameHeaders.set(VERSION, HttpVersion.HTTP_1_0); ctx.writeAndFlush(spdySynReplyFrame); return; } @@ -187,8 +188,9 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { // Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId); spdySynReplyFrame.setLast(true); - SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST); - SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0); + SpdyHeaders frameHeaders = spdySynReplyFrame.headers(); + frameHeaders.set(STATUS, HttpResponseStatus.BAD_REQUEST); + frameHeaders.set(VERSION, HttpVersion.HTTP_1_0); ctx.writeAndFlush(spdySynReplyFrame); } } @@ -291,22 +293,23 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { private static FullHttpRequest createHttpRequest(int spdyVersion, SpdyHeadersFrame requestFrame) throws Exception { // Create the first line of the request from the name/value pairs - HttpMethod method = SpdyHeaders.getMethod(spdyVersion, requestFrame); - String url = SpdyHeaders.getUrl(spdyVersion, requestFrame); - HttpVersion httpVersion = SpdyHeaders.getVersion(spdyVersion, requestFrame); - SpdyHeaders.removeMethod(spdyVersion, requestFrame); - SpdyHeaders.removeUrl(spdyVersion, requestFrame); - SpdyHeaders.removeVersion(spdyVersion, requestFrame); + SpdyHeaders headers = requestFrame.headers(); + HttpMethod method = HttpMethod.valueOf(headers.get(METHOD)); + String url = headers.get(PATH); + HttpVersion httpVersion = HttpVersion.valueOf(headers.get(VERSION)); + headers.remove(METHOD); + headers.remove(PATH); + headers.remove(VERSION); FullHttpRequest req = new DefaultFullHttpRequest(httpVersion, method, url); // Remove the scheme header - SpdyHeaders.removeScheme(spdyVersion, requestFrame); + headers.remove(SCHEME); if (spdyVersion >= 3) { // Replace the SPDY host header with the HTTP host header - String host = SpdyHeaders.getHost(requestFrame); - SpdyHeaders.removeHost(requestFrame); + String host = headers.get(HOST); + headers.remove(HOST); HttpHeaders.setHost(req, host); } @@ -323,13 +326,15 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { return req; } - private static FullHttpResponse createHttpResponse(int spdyVersion, SpdyHeadersFrame responseFrame) - throws Exception { + private static FullHttpResponse createHttpResponse( + int spdyVersion, SpdyHeadersFrame responseFrame) throws Exception { + // Create the first line of the response from the name/value pairs - HttpResponseStatus status = SpdyHeaders.getStatus(spdyVersion, responseFrame); - HttpVersion version = SpdyHeaders.getVersion(spdyVersion, responseFrame); - SpdyHeaders.removeStatus(spdyVersion, responseFrame); - SpdyHeaders.removeVersion(spdyVersion, responseFrame); + SpdyHeaders headers = responseFrame.headers(); + HttpResponseStatus status = HttpResponseStatus.parseLine(headers.get(STATUS)); + HttpVersion version = HttpVersion.valueOf(headers.get(VERSION)); + headers.remove(STATUS); + headers.remove(VERSION); FullHttpResponse res = new DefaultFullHttpResponse(version, status); for (Map.Entry e: responseFrame.headers()) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java index 111f9c1ccd..3914de0419 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java @@ -31,6 +31,8 @@ import io.netty.handler.codec.http.LastHttpContent; import java.util.List; import java.util.Map; +import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*; + /** * Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpContent}s * into {@link SpdySynStreamFrame}s and {@link SpdySynReplyFrame}s. @@ -227,17 +229,18 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { new DefaultSpdySynStreamFrame(streamID, associatedToStreamId, priority); // Unfold the first line of the message into name/value pairs + SpdyHeaders frameHeaders = spdySynStreamFrame.headers(); if (httpMessage instanceof FullHttpRequest) { HttpRequest httpRequest = (HttpRequest) httpMessage; - SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod()); - SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri()); - SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion()); + frameHeaders.set(METHOD, httpRequest.getMethod()); + frameHeaders.set(PATH, httpRequest.getUri()); + frameHeaders.set(VERSION, httpMessage.getProtocolVersion()); } if (httpMessage instanceof HttpResponse) { HttpResponse httpResponse = (HttpResponse) httpMessage; - SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus()); - SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL); - SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion()); + frameHeaders.set(STATUS, httpResponse.getStatus()); + frameHeaders.set(PATH, URL); + frameHeaders.set(VERSION, httpMessage.getProtocolVersion()); spdySynStreamFrame.setUnidirectional(true); } @@ -245,18 +248,18 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { if (spdyVersion >= 3) { String host = HttpHeaders.getHost(httpMessage); httpMessage.headers().remove(HttpHeaders.Names.HOST); - SpdyHeaders.setHost(spdySynStreamFrame, host); + frameHeaders.set(HOST, host); } // Set the SPDY scheme header if (scheme == null) { scheme = "https"; } - SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme); + frameHeaders.set(SCHEME, scheme); // Transfer the remaining HTTP headers for (Map.Entry entry: httpMessage.headers()) { - spdySynStreamFrame.headers().add(entry.getKey(), entry.getValue()); + frameHeaders.add(entry.getKey(), entry.getValue()); } currentStreamId = spdySynStreamFrame.getStreamId(); spdySynStreamFrame.setLast(isLast(httpMessage)); @@ -278,10 +281,10 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { httpResponse.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING); SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); - + SpdyHeaders frameHeaders = spdySynReplyFrame.headers(); // Unfold the first line of the response into name/value pairs - SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus()); - SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion()); + frameHeaders.set(STATUS, httpResponse.getStatus()); + frameHeaders.set(VERSION, httpResponse.getProtocolVersion()); // Transfer the remaining HTTP headers for (Map.Entry entry: httpResponse.headers()) { diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java index 5d98726905..559791e486 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpHeadersTest.java @@ -15,13 +15,12 @@ */ package io.netty.handler.codec.http; -import org.junit.Assert; import org.junit.Test; import java.util.List; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; public class HttpHeadersTest { @@ -29,8 +28,9 @@ public class HttpHeadersTest { public void testRemoveTransferEncodingIgnoreCase() { HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); message.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, "Chunked"); + assertFalse(message.headers().isEmpty()); HttpHeaders.removeTransferEncodingChunked(message); - Assert.assertTrue(message.headers().isEmpty()); + assertTrue(message.headers().isEmpty()); } // Test for https://github.com/netty/netty/issues/1690 @@ -40,12 +40,12 @@ public class HttpHeadersTest { headers.add("Foo", "1"); headers.add("Foo", "2"); - Assert.assertEquals("1", headers.get("Foo")); + assertEquals("1", headers.get("Foo")); List values = headers.getAll("Foo"); - Assert.assertEquals(2, values.size()); - Assert.assertEquals("1", values.get(0)); - Assert.assertEquals("2", values.get(1)); + assertEquals(2, values.size()); + assertEquals("1", values.get(0)); + assertEquals("2", values.get(1)); } @Test diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java new file mode 100644 index 0000000000..7df231a82f --- /dev/null +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java @@ -0,0 +1,84 @@ +/* + * 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.stomp; + +import io.netty.handler.codec.DefaultTextHeaders; +import io.netty.handler.codec.TextHeaderProcessor; +import io.netty.handler.codec.TextHeaders; + +public class DefaultStompHeaders extends DefaultTextHeaders implements StompHeaders { + + @Override + public StompHeaders add(CharSequence name, Object value) { + super.add(name, value); + return this; + } + + @Override + public StompHeaders add(CharSequence name, Iterable values) { + super.add(name, values); + return this; + } + + @Override + public StompHeaders add(CharSequence name, Object... values) { + super.add(name, values); + return this; + } + + @Override + public StompHeaders add(TextHeaders headers) { + super.add(headers); + return this; + } + + @Override + public StompHeaders set(CharSequence name, Object value) { + super.set(name, value); + return this; + } + + @Override + public StompHeaders set(CharSequence name, Object... values) { + super.set(name, values); + return this; + } + + @Override + public StompHeaders set(CharSequence name, Iterable values) { + super.set(name, values); + return this; + } + + @Override + public StompHeaders set(TextHeaders headers) { + super.set(headers); + return this; + } + + @Override + public StompHeaders clear() { + super.clear(); + return this; + } + + @Override + public StompHeaders forEachEntry(TextHeaderProcessor processor) { + super.forEachEntry(processor); + return this; + } +} diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeadersSubframe.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeadersSubframe.java index 2f4dc990cb..ca7db33d13 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeadersSubframe.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeadersSubframe.java @@ -24,7 +24,7 @@ public class DefaultStompHeadersSubframe implements StompHeadersSubframe { protected final StompCommand command; protected DecoderResult decoderResult = DecoderResult.SUCCESS; - protected final StompHeaders headers = new StompHeaders(); + protected final StompHeaders headers = new DefaultStompHeaders(); public DefaultStompHeadersSubframe(StompCommand command) { if (command == null) { diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java index c2413050a5..b929bb67dc 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java @@ -15,93 +15,63 @@ */ package io.netty.handler.codec.stomp; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import io.netty.handler.codec.AsciiString; +import io.netty.handler.codec.TextHeaderProcessor; +import io.netty.handler.codec.TextHeaders; /** - * Provides the constants for the standard STOMP header names and values and - * commonly used utility methods that accesses an {@link StompHeadersSubframe}. + * 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 class StompHeaders { +public interface StompHeaders extends TextHeaders { - public static final String ACCEPT_VERSION = "accept-version"; - public static final String HOST = "host"; - public static final String LOGIN = "login"; - public static final String PASSCODE = "passcode"; - public static final String HEART_BEAT = "heart-beat"; - public static final String VERSION = "version"; - public static final String SESSION = "session"; - public static final String SERVER = "server"; - public static final String DESTINATION = "destination"; - public static final String ID = "id"; - public static final String ACK = "ack"; - public static final String TRANSACTION = "transaction"; - public static final String RECEIPT = "receipt"; - public static final String MESSAGE_ID = "message-id"; - public static final String SUBSCRIPTION = "subscription"; - public static final String RECEIPT_ID = "receipt-id"; - public static final String MESSAGE = "message"; - public static final String CONTENT_LENGTH = "content-length"; - public static final String CONTENT_TYPE = "content-type"; - - private final Map> headers = new LinkedHashMap>(); - - public boolean has(String key) { - List values = headers.get(key); - return values != null && !values.isEmpty(); - } - - public String get(String key) { - List values = headers.get(key); - if (values != null && !values.isEmpty()) { - return values.get(0); - } else { - return null; - } - } - - public void add(String key, String value) { - List values = headers.get(key); - if (values == null) { - values = new ArrayList(); - headers.put(key, values); - } - values.add(value); - } - - @SuppressWarnings("ArraysAsListWithZeroOrOneArgument") - public void set(String key, String value) { - headers.put(key, Arrays.asList(value)); - } - - public List getAll(String key) { - List values = headers.get(key); - if (values != null) { - return new ArrayList(values); - } else { - return new ArrayList(); - } - } - - public Set keySet() { - return headers.keySet(); - } + AsciiString ACCEPT_VERSION = new AsciiString("accept-version"); + AsciiString HOST = new AsciiString("host"); + AsciiString LOGIN = new AsciiString("login"); + AsciiString PASSCODE = new AsciiString("passcode"); + AsciiString HEART_BEAT = new AsciiString("heart-beat"); + AsciiString VERSION = new AsciiString("version"); + AsciiString SESSION = new AsciiString("session"); + AsciiString SERVER = new AsciiString("server"); + AsciiString DESTINATION = new AsciiString("destination"); + AsciiString ID = new AsciiString("id"); + AsciiString ACK = new AsciiString("ack"); + AsciiString TRANSACTION = new AsciiString("transaction"); + AsciiString RECEIPT = new AsciiString("receipt"); + AsciiString MESSAGE_ID = new AsciiString("message-id"); + AsciiString SUBSCRIPTION = new AsciiString("subscription"); + AsciiString RECEIPT_ID = new AsciiString("receipt-id"); + AsciiString MESSAGE = new AsciiString("message"); + AsciiString CONTENT_LENGTH = new AsciiString("content-length"); + AsciiString CONTENT_TYPE = new AsciiString("content-type"); @Override - public String toString() { - return "StompHeaders{" + - headers + - '}'; - } + StompHeaders add(CharSequence name, Object value); - public void set(StompHeaders headers) { - for (String key: headers.keySet()) { - List values = headers.getAll(key); - this.headers.put(key, values); - } - } + @Override + StompHeaders add(CharSequence name, Iterable values); + + @Override + StompHeaders add(CharSequence name, Object... values); + + @Override + StompHeaders add(TextHeaders headers); + + @Override + StompHeaders set(CharSequence name, Object value); + + @Override + StompHeaders set(CharSequence name, Iterable values); + + @Override + StompHeaders set(CharSequence name, Object... values); + + @Override + StompHeaders set(TextHeaders headers); + + @Override + StompHeaders clear(); + + @Override + StompHeaders forEachEntry(TextHeaderProcessor processor); } diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeAggregator.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeAggregator.java index 0c93342a27..11977da621 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeAggregator.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeAggregator.java @@ -18,6 +18,7 @@ package io.netty.handler.codec.stomp; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.AsciiString; import io.netty.handler.codec.MessageAggregator; import io.netty.handler.codec.TooLongFrameException; @@ -64,12 +65,17 @@ public class StompSubframeAggregator @Override protected boolean hasContentLength(StompHeadersSubframe start) throws Exception { - return start.headers().has(StompHeaders.CONTENT_LENGTH); + return start.headers().contains(StompHeaders.CONTENT_LENGTH); } @Override protected long contentLength(StompHeadersSubframe start) throws Exception { - return Long.parseLong(start.headers().get(StompHeaders.CONTENT_LENGTH)); + CharSequence value = start.headers().get(StompHeaders.CONTENT_LENGTH); + if (value instanceof AsciiString) { + return ((AsciiString) value).parseLong(); + } + + return Long.parseLong(value.toString()); } @Override diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java index a8376fd5f9..35dc176b71 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java @@ -18,6 +18,7 @@ package io.netty.handler.codec.stomp; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.AsciiString; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.DecoderResult; import io.netty.handler.codec.ReplayingDecoder; @@ -199,7 +200,7 @@ public class StompSubframeDecoder extends ReplayingDecoder { } } else { long contentLength = -1; - if (headers.has(StompHeaders.CONTENT_LENGTH)) { + if (headers.contains(StompHeaders.CONTENT_LENGTH)) { contentLength = getContentLength(headers, 0); } else { int globalIndex = indexOf(buffer, buffer.readerIndex(), @@ -219,10 +220,14 @@ public class StompSubframeDecoder extends ReplayingDecoder { } private static long getContentLength(StompHeaders headers, long defaultValue) { - String contentLength = headers.get(StompHeaders.CONTENT_LENGTH); + CharSequence contentLength = headers.get(StompHeaders.CONTENT_LENGTH); if (contentLength != null) { try { - return Long.parseLong(contentLength); + if (contentLength instanceof AsciiString) { + return ((AsciiString) contentLength).parseLong(); + } else { + return Long.parseLong(contentLength.toString()); + } } catch (NumberFormatException ignored) { return defaultValue; } diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java index ac8f286c91..4da3739967 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java @@ -17,6 +17,9 @@ package io.netty.handler.codec.stomp; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.AsciiHeadersEncoder; +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; @@ -61,18 +64,9 @@ public class StompSubframeEncoder extends MessageToMessageEncoder ByteBuf buf = ctx.alloc().buffer(); buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII)); - buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF); - - StompHeaders headers = frame.headers(); - for (String k: headers.keySet()) { - List values = headers.getAll(k); - for (String v: values) { - buf.writeBytes(k.getBytes(CharsetUtil.US_ASCII)). - writeByte(StompConstants.COLON).writeBytes(v.getBytes(CharsetUtil.US_ASCII)); - buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF); - } - } - buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF); + buf.writeByte(StompConstants.LF); + frame.headers().forEachEntry(new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF)); + buf.writeByte(StompConstants.LF); return buf; } } diff --git a/codec-stomp/src/test/java/io/netty/handler/codec/stomp/StompTestConstants.java b/codec-stomp/src/test/java/io/netty/handler/codec/stomp/StompTestConstants.java index b9246c1d0d..a427b944cc 100644 --- a/codec-stomp/src/test/java/io/netty/handler/codec/stomp/StompTestConstants.java +++ b/codec-stomp/src/test/java/io/netty/handler/codec/stomp/StompTestConstants.java @@ -17,29 +17,29 @@ package io.netty.handler.codec.stomp; public final class StompTestConstants { public static final String CONNECT_FRAME = - "CONNECT\r\n" + - "host:stomp.github.org\r\n" + - "accept-version:1.1,1.2\r\n" + - "\r\n" + + "CONNECT\n" + + "host:stomp.github.org\n" + + "accept-version:1.1,1.2\n" + + '\n' + '\0'; public static final String CONNECTED_FRAME = - "CONNECTED\r\n" + + "CONNECTED\n" + "version:1.2\n" + - "\r\n" + + '\n' + "\0\n"; public static final String SEND_FRAME_1 = - "SEND\r\n" + + "SEND\n" + "destination:/queue/a\n" + "content-type:text/plain\n" + - "\r\n" + + '\n' + "hello, queue a!" + "\0\n"; public static final String SEND_FRAME_2 = - "SEND\r\n" + + "SEND\n" + "destination:/queue/a\n" + "content-type:text/plain\n" + "content-length:17\n" + - "\r\n" + + '\n' + "hello, queue a!!!" + "\0\n"; diff --git a/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java b/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java new file mode 100644 index 0000000000..d1ae85cdbc --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java @@ -0,0 +1,139 @@ +/* + * 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.buffer.ByteBuf; + +public final class AsciiHeadersEncoder implements TextHeaderProcessor { + + /** + * The separator characters to insert between a header name and a header value. + */ + public enum SeparatorType { + /** + * {@code ':'} + */ + COLON, + /** + * {@code ': '} + */ + COLON_SPACE, + } + + /** + * The newline characters to insert between header entries. + */ + public enum NewlineType { + /** + * {@code '\n'} + */ + LF, + /** + * {@code '\r\n'} + */ + CRLF + } + + private final ByteBuf buf; + private final SeparatorType separatorType; + private final NewlineType newlineType; + + public AsciiHeadersEncoder(ByteBuf buf) { + this(buf, SeparatorType.COLON_SPACE, NewlineType.CRLF); + } + + public AsciiHeadersEncoder(ByteBuf buf, SeparatorType separatorType, NewlineType newlineType) { + if (buf == null) { + throw new NullPointerException("buf"); + } + if (separatorType == null) { + throw new NullPointerException("separatorType"); + } + if (newlineType == null) { + throw new NullPointerException("newlineType"); + } + + this.buf = buf; + this.separatorType = separatorType; + this.newlineType = newlineType; + } + + @Override + public boolean process(CharSequence name, CharSequence value) throws Exception { + final ByteBuf buf = this.buf; + final int nameLen = name.length(); + final int valueLen = value.length(); + final int entryLen = nameLen + valueLen + 4; + int offset = buf.writerIndex(); + buf.ensureWritable(entryLen); + writeAscii(buf, offset, name, nameLen); + offset += nameLen; + + switch (separatorType) { + case COLON: + buf.setByte(offset ++, ':'); + break; + case COLON_SPACE: + buf.setByte(offset ++, ':'); + buf.setByte(offset ++, ' '); + break; + default: + throw new Error(); + } + + writeAscii(buf, offset, value, valueLen); + offset += valueLen; + + switch (newlineType) { + case LF: + buf.setByte(offset ++, '\n'); + break; + case CRLF: + buf.setByte(offset ++, '\r'); + buf.setByte(offset ++, '\n'); + break; + default: + throw new Error(); + } + + buf.writerIndex(offset); + return true; + } + + private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) { + if (value instanceof AsciiString) { + writeAsciiString(buf, offset, (AsciiString) value, valueLen); + } else { + writeCharSequence(buf, offset, value, valueLen); + } + } + + private static void writeAsciiString(ByteBuf buf, int offset, AsciiString value, int valueLen) { + value.copy(0, buf, offset, valueLen); + } + + private static void writeCharSequence(ByteBuf buf, int offset, CharSequence value, int valueLen) { + for (int i = 0; i < valueLen; i ++) { + buf.setByte(offset ++, c2b(value.charAt(i))); + } + } + + private static int c2b(char ch) { + return ch < 256? (byte) ch : '?'; + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/AsciiString.java b/codec/src/main/java/io/netty/handler/codec/AsciiString.java new file mode 100644 index 0000000000..a2a99eed28 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/AsciiString.java @@ -0,0 +1,1411 @@ +/* + * 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.buffer.ByteBuf; +import io.netty.util.internal.EmptyArrays; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * 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 reduced memory footprint and faster data transfer from/to byte-based data structures such as a byte array and + * {@link ByteBuf}. It is often used in conjunction with {@link TextHeaders}. + */ +public final class AsciiString implements CharSequence, Comparable { + + public static final AsciiString EMPTY_STRING = new AsciiString(""); + + /** XXX: Make sure that this method and {@link #hashCode()} uses the same hashing algorithm */ + static int hashCode(CharSequence value) { + if (value instanceof AsciiString) { + return value.hashCode(); + } + + int hash = 0; + final int end = value.length(); + for (int i = 0; i < end; i ++) { + hash = hash * 31 ^ value.charAt(i) & 31; + } + + return hash; + } + + private final byte[] value; + private String string; + private int hash; + + public AsciiString(byte[] value) { + this(value, true); + } + + public AsciiString(byte[] value, boolean copy) { + checkNull(value); + if (copy) { + this.value = value.clone(); + } else { + this.value = value; + } + } + + public AsciiString(byte[] value, int start, int length) { + this(value, start, length, true); + } + + public AsciiString(byte[] value, int start, int length, boolean copy) { + checkNull(value); + if (start < 0 || start > value.length - length) { + throw new IndexOutOfBoundsException("expected: " + + "0 <= start(" + start + ") <= start + length(" + length + ") <= " + + "value.length(" + value.length + ')'); + } + + if (copy || start != 0 || length != value.length) { + this.value = Arrays.copyOfRange(value, start, start + length); + } else { + this.value = value; + } + } + + public AsciiString(char[] value) { + this(checkNull(value), 0, value.length); + } + + public AsciiString(char[] value, int start, int length) { + checkNull(value); + if (start < 0 || start > value.length - length) { + throw new IndexOutOfBoundsException("expected: " + + "0 <= start(" + start + ") <= start + length(" + length + ") <= " + + "value.length(" + value.length + ')'); + } + + this.value = new byte[length]; + for (int i = 0, j = start; i < length; i ++, j ++) { + this.value[i] = c2b(value[j]); + } + } + + public AsciiString(CharSequence value) { + this(checkNull(value), 0, value.length()); + } + + public AsciiString(CharSequence value, int start, int length) { + if (value == null) { + throw new NullPointerException("value"); + } + + if (start < 0 || length < 0 || length > value.length() - start) { + throw new IndexOutOfBoundsException("expected: " + + "0 <= start(" + start + ") <= start + length(" + length + ") <= " + + "value.length(" + value.length() + ')'); + } + + this.value = new byte[length]; + for (int i = 0; i < length; i++) { + this.value[i] = c2b(value.charAt(start + i)); + } + } + + public AsciiString(ByteBuffer value) { + this(checkNull(value), value.position(), value.remaining()); + } + + public AsciiString(ByteBuffer value, int start, int length) { + if (value == null) { + throw new NullPointerException("value"); + } + + if (start < 0 || length > value.capacity() - start) { + throw new IndexOutOfBoundsException("expected: " + + "0 <= start(" + start + ") <= start + length(" + length + ") <= " + + "value.capacity(" + value.capacity() + ')'); + } + + if (value.hasArray()) { + int baseOffset = value.arrayOffset() + start; + this.value = Arrays.copyOfRange(value.array(), baseOffset, baseOffset + length); + } else { + this.value = new byte[length]; + int oldPos = value.position(); + value.get(this.value, 0, this.value.length); + value.position(oldPos); + } + } + + private static T checkNull(T value) { + if (value == null) { + throw new NullPointerException("value"); + } + return value; + } + + @Override + public int length() { + return value.length; + } + + @Override + public char charAt(int index) { + return (char) (byteAt(index) & 0xFF); + } + + public byte byteAt(int index) { + return value[index]; + } + + public byte[] array() { + return value; + } + + public int arrayOffset() { + return 0; + } + + private static byte c2b(char c) { + if (c > 255) { + return '?'; + } + return (byte) c; + } + + 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; + } + + /** + * Copies a range of characters into a new string. + * + * @param start + * the offset of the first character. + * @return a new string containing the characters from start to the end of + * the string. + * @throws IndexOutOfBoundsException + * if {@code start < 0} or {@code start > length()}. + */ + public AsciiString subSequence(int start) { + return subSequence(start, length()); + } + + @Override + public AsciiString subSequence(int start, int end) { + if (start < 0 || start > end || end > length()) { + throw new IndexOutOfBoundsException( + "expected: 0 <= start(" + start + ") <= end (" + end + ") <= length(" + length() + ')'); + } + + final byte[] value = this.value; + if (start == 0 && end == value.length) { + return this; + } + + if (end == start) { + return EMPTY_STRING; + } + + return new AsciiString(value, start, end - start, false); + } + + @Override + public int hashCode() { + int hash = this.hash; + final byte[] value = this.value; + if (hash != 0 || value.length == 0) { + return hash; + } + + for (byte b: value) { + hash = hash * 31 ^ b & 31; + } + + return this.hash = hash; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof AsciiString)) { + return false; + } + + if (this == obj) { + return true; + } + + AsciiString that = (AsciiString) obj; + int thisHash = hashCode(); + int thatHash = that.hashCode(); + if (thisHash != thatHash || length() != that.length()) { + return false; + } + + byte[] thisValue = value; + byte[] thatValue = that.value; + int end = thisValue.length; + for (int i = 0, j = 0; i < end; i ++, j ++) { + if (thisValue[i] != thatValue[j]) { + return false; + } + } + + return true; + } + + @Override + @SuppressWarnings("deprecation") + public String toString() { + String string = this.string; + if (string != null) { + return string; + } + + final byte[] value = this.value; + return this.string = new String(value, 0, 0, value.length); + } + + @SuppressWarnings("deprecation") + public String toString(int start, int end) { + final byte[] value = this.value; + if (start == 0 && end == value.length) { + return toString(); + } + + int length = end - start; + if (length == 0) { + return ""; + } + + return new String(value, 0, start, length); + } + + /** + * Compares the specified string to this string using the ASCII values of + * the characters. 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 a + * 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}. + */ + @Override + public int compareTo(CharSequence string) { + if (this == string) { + return 0; + } + + int result; + int length1 = length(); + int length2 = string.length(); + int minLength = Math.min(length1, length2); + byte[] value = this.value; + for (int i = 0, j = 0; j < minLength; i ++, j ++) { + result = (value[i] & 0xFF) - string.charAt(j); + if (result != 0) { + return result; + } + } + + 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) { + if (this == string) { + return 0; + } + + int result; + int length1 = length(); + int length2 = string.length(); + int minLength = Math.min(length1, length2); + byte[] thisValue = value; + if (string instanceof AsciiString) { + AsciiString that = (AsciiString) string; + byte[] thatValue = that.value; + for (int i = 0; i < minLength; i ++) { + byte v1 = thisValue[i]; + byte v2 = thatValue[i]; + if (v1 == v2) { + continue; + } + int c1 = toLowerCase(v1) & 0xFF; + int c2 = toLowerCase(v2) & 0xFF; + result = c1 - c2; + if (result != 0) { + return result; + } + } + } else { + for (int i = 0; i < minLength; i ++) { + int c1 = toLowerCase(thisValue[i]) & 0xFF; + int c2 = toLowerCase(string.charAt(i)); + result = c1 - c2; + if (result != 0) { + return result; + } + } + } + + return length1 - length2; + } + + /** + * Concatenates this string and the specified string. + * + * @param string + * the string to concatenate + * @return a new string which is the concatenation of this string and the + * specified string. + */ + public AsciiString concat(CharSequence string) { + int thisLen = length(); + int thatLen = string.length(); + if (thatLen == 0) { + return this; + } + + if (string instanceof AsciiString) { + AsciiString that = (AsciiString) string; + if (isEmpty()) { + return that; + } + + byte[] newValue = Arrays.copyOf(value, thisLen + thatLen); + System.arraycopy(that.value, 0, newValue, thisLen, thatLen); + + return new AsciiString(newValue, false); + } + + if (isEmpty()) { + return new AsciiString(string); + } + + int newLen = thisLen + thatLen; + byte[] newValue = Arrays.copyOf(value, newLen); + for (int i = thisLen, j = 0; i < newLen; i ++, j ++) { + newValue[i] = c2b(string.charAt(j)); + } + + return new AsciiString(newValue, false); + } + + /** + * Compares the specified string to this string to determine if the + * specified string is a suffix. + * + * @param suffix + * the suffix to look for. + * @return {@code true} if the specified string is a suffix of this string, + * {@code false} otherwise. + * @throws NullPointerException + * if {@code suffix} is {@code null}. + */ + public boolean endsWith(CharSequence suffix) { + int suffixLen = suffix.length(); + return regionMatches(length() - suffixLen, suffix, 0, suffixLen); + } + + /** + * Compares the specified string to this string ignoring the case of the + * characters and returns true if they are equal. + * + * @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) { + return true; + } + + if (string == null) { + return false; + } + + final byte[] value = this.value; + final int thisLen = value.length; + final int thatLen = string.length(); + if (thisLen != thatLen) { + return false; + } + + for (int i = 0; i < thisLen; i ++) { + char c1 = (char) (value[i] & 0xFF); + char c2 = string.charAt(i); + if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) { + return false; + } + } + return true; + } + + /** + * Converts this string to a byte array using the ASCII encoding. + * + * @return the byte array encoding of this string. + */ + public byte[] toByteArray() { + return toByteArray(0, length()); + } + + /** + * Converts this string to a byte array using the ASCII encoding. + * + * @return the byte array encoding of this string. + */ + public byte[] toByteArray(int start, int end) { + return Arrays.copyOfRange(value, start, end); + } + + /** + * Copies the characters in this string to a character array. + * + * @return a character array containing the characters of this string. + */ + public char[] toCharArray() { + return toCharArray(0, length()); + } + + /** + * Copies the characters in this string to a character array. + * + * @return a character array containing the characters of this string. + */ + public char[] toCharArray(int start, int end) { + int length = end - start; + if (length == 0) { + return EmptyArrays.EMPTY_CHARS; + } + + final byte[] value = this.value; + final char[] buffer = new char[length]; + for (int i = 0, j = start; i < length; i ++, j ++) { + buffer[i] = (char) (value[j] & 0xFF); + } + return buffer; + } + + /** + * Copies the content of this string to a {@link ByteBuf} using {@link ByteBuf#writeBytes(byte[], int, int)}. + * + * @param srcIdx + * the starting offset of characters to copy. + * @param dst + * the destination byte array. + * @param dstIdx + * the starting offset in the destination byte array. + * @param length + * the number of characters to copy. + */ + public void copy(int srcIdx, ByteBuf dst, int dstIdx, int length) { + if (dst == null) { + throw new NullPointerException("dst"); + } + + final byte[] value = this.value; + final int thisLen = value.length; + + if (srcIdx < 0 || length > thisLen - srcIdx) { + throw new IndexOutOfBoundsException("expected: " + + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + length + ") <= srcLen(" + thisLen + ')'); + } + + dst.setBytes(dstIdx, value, srcIdx, length); + } + + /** + * Copies the content of this string to a {@link ByteBuf} using {@link ByteBuf#writeBytes(byte[], int, int)}. + * + * @param srcIdx + * the starting offset of characters to copy. + * @param dst + * the destination byte array. + * @param length + * the number of characters to copy. + */ + public void copy(int srcIdx, ByteBuf dst, int length) { + if (dst == null) { + throw new NullPointerException("dst"); + } + + final byte[] value = this.value; + final int thisLen = value.length; + + if (srcIdx < 0 || length > thisLen - srcIdx) { + throw new IndexOutOfBoundsException("expected: " + + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + length + ") <= srcLen(" + thisLen + ')'); + } + + dst.writeBytes(value, srcIdx, length); + } + + /** + * Copies the content of this string to a byte array. + * + * @param srcIdx + * the starting offset of characters to copy. + * @param dst + * the destination byte array. + * @param dstIdx + * the starting offset in the destination byte array. + * @param length + * the number of characters to copy. + */ + public void copy(int srcIdx, byte[] dst, int dstIdx, int length) { + if (dst == null) { + throw new NullPointerException("dst"); + } + + final byte[] value = this.value; + final int thisLen = value.length; + + if (srcIdx < 0 || length > thisLen - srcIdx) { + throw new IndexOutOfBoundsException("expected: " + + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + length + ") <= srcLen(" + thisLen + ')'); + } + + System.arraycopy(value, srcIdx, dst, dstIdx, length); + } + + /** + * Copied the content of this string to a character array. + * + * @param srcIdx + * the starting offset of characters to copy. + * @param dst + * the destination character array. + * @param dstIdx + * the starting offset in the destination byte array. + * @param length + * the number of characters to copy. + */ + public void copy(int srcIdx, char[] dst, int dstIdx, int length) { + if (dst == null) { + throw new NullPointerException("dst"); + } + + final byte[] value = this.value; + final int thisLen = value.length; + + if (srcIdx < 0 || length > thisLen - srcIdx) { + throw new IndexOutOfBoundsException("expected: " + + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + length + ") <= srcLen(" + thisLen + ')'); + } + + final int dstEnd = dstIdx + length; + for (int i = srcIdx, j = dstIdx; j < dstEnd; i ++, j ++) { + dst[j] = (char) (value[i] & 0xFF); + } + } + + /** + * Searches in this string for the first index of the specified character. + * The search for the character starts at the beginning and moves towards + * the end of this string. + * + * @param c + * the character to find. + * @return the index in this string of the specified character, -1 if the + * character isn't found. + */ + public int indexOf(int c) { + return indexOf(c, 0); + } + + /** + * Searches in this string for the index of the specified character. The + * search for the character starts at the specified offset and moves towards + * the end of this string. + * + * @param c + * the character to find. + * @param start + * the starting offset. + * @return the index in this string of the specified character, -1 if the + * character isn't found. + */ + public int indexOf(int c, int start) { + final byte[] value = this.value; + final int length = value.length; + if (start < length) { + if (start < 0) { + start = 0; + } + + for (int i = start; i < length; i ++) { + if ((value[i] & 0xFF) == c) { + return i; + } + } + } + return -1; + } + + /** + * Searches in this string for the first index of the specified string. The + * search for the string starts at the beginning and moves towards the end + * of this string. + * + * @param string + * the string to find. + * @return the index of the first character of the specified string in this + * string, -1 if the specified string is not a substring. + * @throws NullPointerException + * if {@code string} is {@code null}. + */ + public int indexOf(CharSequence string) { + return indexOf(string, 0); + } + + /** + * Searches in this string for the index of the specified string. The search + * for the string starts at the specified offset and moves towards the end + * of this string. + * + * @param subString + * the string to find. + * @param start + * the starting offset. + * @return the index of the first character of the specified string in this + * string, -1 if the specified string is not a substring. + * @throws NullPointerException + * if {@code subString} is {@code null}. + */ + public int indexOf(CharSequence subString, int start) { + if (start < 0) { + start = 0; + } + + final byte[] value = this.value; + final int thisLen = value.length; + + int subCount = subString.length(); + if (subCount <= 0) { + return start < thisLen ? start : thisLen; + } + if (subCount > thisLen - start) { + return -1; + } + + char firstChar = subString.charAt(0); + for (;;) { + int i = indexOf(firstChar, start); + if (i == -1 || subCount + i > thisLen) { + return -1; // handles subCount > count || start >= count + } + int o1 = i, o2 = 0; + while (++o2 < subCount && (value[++o1] & 0xFF) == subString.charAt(o2)) { + // Intentionally empty + } + if (o2 == subCount) { + return i; + } + start = i + 1; + } + } + + /** + * Searches in this string for the last index of the specified character. + * The search for the character starts at the end and moves towards the + * beginning of this string. + * + * @param c + * the character to find. + * @return the index in this string of the specified character, -1 if the + * character isn't found. + */ + public int lastIndexOf(int c) { + return lastIndexOf(c, length() - 1); + } + + /** + * Searches in this string for the index of the specified character. The + * search for the character starts at the specified offset and moves towards + * the beginning of this string. + * + * @param c + * the character to find. + * @param start + * the starting offset. + * @return the index in this string of the specified character, -1 if the + * character isn't found. + */ + public int lastIndexOf(int c, int start) { + if (start >= 0) { + final byte[] value = this.value; + final int length = value.length; + if (start >= length) { + start = length - 1; + } + for (int i = start; i >= 0; -- i) { + if ((value[i] & 0xFF) == c) { + return i; + } + } + } + return -1; + } + + /** + * Searches in this string for the last index of the specified string. The + * search for the string starts at the end and moves towards the beginning + * of this string. + * + * @param string + * the string to find. + * @return the index of the first character of the specified string in this + * string, -1 if the specified string is not a substring. + * @throws NullPointerException + * if {@code string} is {@code null}. + */ + public int lastIndexOf(CharSequence string) { + // Use count instead of count - 1 so lastIndexOf("") answers count + return lastIndexOf(string, length()); + } + + /** + * Searches in this string for the index of the specified string. The search + * for the string starts at the specified offset and moves towards the + * beginning of this string. + * + * @param subString + * the string to find. + * @param start + * the starting offset. + * @return the index of the first character of the specified string in this + * string , -1 if the specified string is not a substring. + * @throws NullPointerException + * if {@code subString} is {@code null}. + */ + public int lastIndexOf(CharSequence subString, int start) { + final byte[] value = this.value; + final int thisLen = value.length; + final int subCount = subString.length(); + + if (subCount > thisLen || start < 0) { + return -1; + } + + if (subCount <= 0) { + return start < thisLen ? start : thisLen; + } + + start = Math.min(start, thisLen - subCount); + + // count and subCount are both >= 1 + char firstChar = subString.charAt(0); + for (;;) { + int i = lastIndexOf(firstChar, start); + if (i == -1) { + return -1; + } + int o1 = i, o2 = 0; + while (++o2 < subCount && (value[++o1] & 0xFF) == subString.charAt(o2)) { + // Intentionally empty + } + if (o2 == subCount) { + return i; + } + start = i - 1; + } + } + + /** + * Answers if the size of this String is zero. + * + * @return true if the size of this String is zero, false otherwise + */ + public boolean isEmpty() { + return value.length == 0; + } + + /** + * Compares the specified string to this string and compares the specified + * range of characters to determine if they are the same. + * + * @param thisStart + * the starting offset in this string. + * @param string + * the string to compare. + * @param start + * the starting offset in the specified string. + * @param length + * the number of characters to compare. + * @return {@code true} if the ranges of characters are equal, {@code false} + * otherwise + * @throws NullPointerException + * if {@code string} is {@code null}. + */ + public boolean regionMatches(int thisStart, CharSequence string, int start, int length) { + if (string == null) { + throw new NullPointerException("string"); + } + + if (start < 0 || string.length() - start < length) { + return false; + } + + final byte[] value = this.value; + final int thisLen = value.length; + if (thisStart < 0 || thisLen - thisStart < length) { + return false; + } + + if (length <= 0) { + return true; + } + + final int thisEnd = thisStart + length; + for (int i = thisStart, j = start; i < thisEnd; i ++, j ++) { + if ((value[i] & 0xFF) != string.charAt(j)) { + return false; + } + } + return true; + } + + /** + * Compares the specified string to this string and compares the specified + * range of characters to determine if they are the same. When ignoreCase is + * true, the case of the characters is ignored during the comparison. + * + * @param ignoreCase + * specifies if case should be ignored. + * @param thisStart + * the starting offset in this string. + * @param string + * the string to compare. + * @param start + * the starting offset in the specified string. + * @param length + * the number of characters to compare. + * @return {@code true} if the ranges of characters are equal, {@code false} + * otherwise. + * @throws NullPointerException + * if {@code string} is {@code null}. + */ + public boolean regionMatches(boolean ignoreCase, int thisStart, + CharSequence string, int start, int length) { + if (!ignoreCase) { + return regionMatches(thisStart, string, start, length); + } + + if (string == null) { + throw new NullPointerException("string"); + } + + final byte[] value = this.value; + final int thisLen = value.length; + if (thisStart < 0 || length > thisLen - thisStart) { + return false; + } + if (start < 0 || length > string.length() - start) { + return false; + } + + int thisEnd = thisStart + length; + while (thisStart < thisEnd) { + char c1 = (char) (value[thisStart++] & 0xFF); + char c2 = string.charAt(start++); + if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) { + return false; + } + } + return true; + } + + /** + * Copies this string replacing occurrences of the specified character with + * another character. + * + * @param oldChar + * the character to replace. + * @param newChar + * the replacement character. + * @return a new string with occurrences of oldChar replaced by newChar. + */ + public AsciiString replace(char oldChar, char newChar) { + int index = indexOf(oldChar, 0); + if (index == -1) { + return this; + } + + final byte[] value = this.value; + final int count = value.length; + byte[] buffer = new byte[count]; + for (int i = 0, j = 0; i < value.length; i ++, j ++) { + byte b = value[i]; + if ((char) (b & 0xFF) == oldChar) { + b = (byte) newChar; + } + buffer[j] = b; + } + + return new AsciiString(buffer, false); + } + + /** + * Compares the specified string to this string to determine if the + * specified string is a prefix. + * + * @param prefix + * the string to look for. + * @return {@code true} if the specified string is a prefix of this string, + * {@code false} otherwise + * @throws NullPointerException + * if {@code prefix} is {@code null}. + */ + public boolean startsWith(CharSequence prefix) { + return startsWith(prefix, 0); + } + + /** + * Compares the specified string to this string, starting at the specified + * offset, to determine if the specified string is a prefix. + * + * @param prefix + * the string to look for. + * @param start + * the starting offset. + * @return {@code true} if the specified string occurs in this string at the + * specified offset, {@code false} otherwise. + * @throws NullPointerException + * if {@code prefix} is {@code null}. + */ + public boolean startsWith(CharSequence prefix, int start) { + return regionMatches(start, prefix, 0, prefix.length()); + } + + /** + * Converts the characters in this string to lowercase, using the default + * Locale. + * + * @return a new string containing the lowercase characters equivalent to + * the characters in this string. + */ + public AsciiString toLowerCase() { + boolean lowercased = true; + final byte[] value = this.value; + + for (byte b: value) { + if (b >= 'A' && b <= 'Z') { + lowercased = false; + break; + } + } + + // Check if this string does not contain any uppercase characters. + if (lowercased) { + return this; + } + + final int length = value.length; + final byte[] newValue = new byte[length]; + for (int i = 0, j = 0; i < length; i ++, j ++) { + newValue[i] = toLowerCase(value[j]); + } + + return new AsciiString(newValue, false); + } + + /** + * Converts the characters in this string to uppercase, using the default + * Locale. + * + * @return a new string containing the uppercase characters equivalent to + * the characters in this string. + */ + public AsciiString toUpperCase() { + final byte[] value = this.value; + boolean uppercased = true; + for (byte b: value) { + if (b >= 'a' && b <= 'z') { + uppercased = false; + break; + } + } + + // Check if this string does not contain any lowercase characters. + if (uppercased) { + return this; + } + + final int length = value.length; + final byte[] newValue = new byte[length]; + for (int i = 0, j = 0; i < length; i ++, j ++) { + newValue[i] = toUpperCase(value[j]); + } + + return new AsciiString(newValue, false); + } + + /** + * Copies this string removing white space characters from the beginning and + * end of the string. + * + * @return a new string with characters {@code <= \\u0020} removed from + * the beginning and the end. + */ + public AsciiString trim() { + final byte[] value = this.value; + int start = 0, last = value.length; + int end = last; + while (start <= end && value[start] <= ' ') { + start ++; + } + while (end >= start && value[end] <= ' ') { + end --; + } + if (start == 0 && end == last) { + return this; + } + return new AsciiString(value, start, end - start + 1, false); + } + + /** + * Compares a {@code CharSequence} to this {@code String} to determine if + * their contents are equal. + * + * @param cs + * 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(); + } + + int length1 = length(); + int length2 = cs.length(); + if (length1 != length2) { + return false; + } + + if (length1 == 0 && length2 == 0) { + return true; // since both are empty strings + } + + return regionMatches(0, cs, 0, length2); + } + + /** + * Determines whether this string matches a given regular expression. + * + * @param expr + * the regular expression to be matched. + * @return {@code true} if the expression matches, otherwise {@code false}. + * @throws PatternSyntaxException + * if the syntax of the supplied regular expression is not + * valid. + * @throws NullPointerException + * if {@code expr} is {@code null}. + */ + public boolean matches(String expr) { + return Pattern.matches(expr, this); + } + + /** + * Splits this string using the supplied regular expression {@code expr}. + * The parameter {@code max} controls the behavior how many times the + * pattern is applied to the string. + * + * @param expr + * the regular expression used to divide the string. + * @param max + * the number of entries in the resulting array. + * @return an array of Strings created by separating the string along + * matches of the regular expression. + * @throws NullPointerException + * if {@code expr} is {@code null}. + * @throws PatternSyntaxException + * if the syntax of the supplied regular expression is not + * valid. + * @see Pattern#split(CharSequence, int) + */ + public AsciiString[] split(String expr, int max) { + 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]); + } + return res; + } + + /** + * Splits the specified {@link String} with the specified delimiter.. + */ + public AsciiString[] split(char delim) { + final List res = new ArrayList(); + + int start = 0; + final byte[] value = this.value; + final int length = value.length; + for (int i = start; i < length; i ++) { + if (charAt(i) == delim) { + if (start == i) { + res.add(EMPTY_STRING); + } else { + res.add(new AsciiString(value, start, i - start, false)); + } + start = i + 1; + } + } + + if (start == 0) { // If no delimiter was found in the value + res.add(this); + } else { + if (start != length) { + // Add the last element if it's not empty. + res.add(new AsciiString(value, start, length - start, false)); + } else { + // Truncate trailing empty elements. + for (int i = res.size() - 1; i >= 0; i --) { + if (res.get(i).isEmpty()) { + res.remove(i); + } else { + break; + } + } + } + } + + return res.toArray(new AsciiString[res.size()]); + } + + /** + * Determines if this {@code String} contains the sequence of characters in + * the {@code CharSequence} passed. + * + * @param cs + * the character sequence to search for. + * @return {@code true} if the sequence of characters are contained in this + * string, otherwise {@code false}. + */ + public boolean contains(CharSequence cs) { + if (cs == null) { + throw new NullPointerException(); + } + return indexOf(cs) >= 0; + } + + public int parseInt() { + return parseInt(0, length(), 10); + } + + public int parseInt(int radix) { + return parseInt(0, length(), radix); + } + + public int parseInt(int start, int end) { + return parseInt(start, end, 10); + } + + public int parseInt(int start, int end, int radix) { + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new NumberFormatException(); + } + + if (start == end) { + throw new NumberFormatException(); + } + + int i = start; + boolean negative = charAt(i) == '-'; + if (negative && ++ i == end) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + + return parseInt(i, end, radix, negative); + } + + private int parseInt(int start, int end, int radix, boolean negative) { + final byte[] value = this.value; + int max = Integer.MIN_VALUE / radix; + int result = 0; + int offset = start; + while (offset < end) { + int digit = Character.digit((char) (value[offset ++] & 0xFF), radix); + if (digit == -1) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + if (max > result) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + int next = result * radix - digit; + if (next > result) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + result = next; + } + if (!negative) { + result = -result; + if (result < 0) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + } + return result; + } + + public long parseLong() { + return parseLong(0, length(), 10); + } + + public long parseLong(int radix) { + return parseLong(0, length(), radix); + } + + public long parseLong(int start, int end) { + return parseLong(start, end, 10); + } + + public long parseLong(int start, int end, int radix) { + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new NumberFormatException(); + } + + if (start == end) { + throw new NumberFormatException(); + } + + int i = start; + boolean negative = charAt(i) == '-'; + if (negative && ++ i == end) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + + return parseLong(i, end, radix, negative); + } + + private long parseLong(int start, int end, int radix, boolean negative) { + final byte[] value = this.value; + long max = Long.MIN_VALUE / radix; + long result = 0; + int offset = start; + while (offset < end) { + int digit = Character.digit((char) (value[offset ++] & 0xFF), radix); + if (digit == -1) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + if (max > result) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + long next = result * radix - digit; + if (next > result) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + result = next; + } + if (!negative) { + result = -result; + if (result < 0) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + } + return result; + } + + public short parseShort() { + return parseShort(0, length(), 10); + } + + public short parseShort(int radix) { + return parseShort(0, length(), radix); + } + + public short parseShort(int start, int end) { + return parseShort(start, end, 10); + } + + public short parseShort(int start, int end, int radix) { + int intValue = parseInt(start, end, radix); + short result = (short) intValue; + if (result != intValue) { + throw new NumberFormatException(subSequence(start, end).toString()); + } + return result; + } + + public float parseFloat() { + return parseFloat(0, length()); + } + + public float parseFloat(int start, int end) { + return Float.parseFloat(toString(start, end)); + } + + public double parseDouble() { + return parseDouble(0, length()); + } + + public double parseDouble(int start, int end) { + return Double.parseDouble(toString(start, end)); + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java new file mode 100644 index 0000000000..fbe9a815e0 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java @@ -0,0 +1,803 @@ +/* + * 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.internal.PlatformDependent; + +import java.text.DateFormat; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.TimeZone; + +public class DefaultTextHeaders implements TextHeaders { + + private static final int BUCKET_SIZE = 17; + + private static int index(int hash) { + return Math.abs(hash % BUCKET_SIZE); + } + + @SuppressWarnings("unchecked") + private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE]; + private final HeaderEntry head = new HeaderEntry(this); + private final boolean ignoreCase; + int size; + + public DefaultTextHeaders() { + this(true); + } + + public DefaultTextHeaders(boolean ignoreCase) { + head.before = head.after = head; + this.ignoreCase = ignoreCase; + } + + protected int hashCode(CharSequence name) { + return AsciiString.hashCode(name); + } + + protected CharSequence convertName(CharSequence name) { + if (name == null) { + throw new NullPointerException("name"); + } + return name; + } + + @SuppressWarnings("unchecked") + protected CharSequence convertValue(Object value) { + if (value == null) { + throw new NullPointerException("value"); + } + if (value instanceof CharSequence) { + return (CharSequence) value; + } + return value.toString(); + } + + protected boolean nameEquals(CharSequence a, CharSequence b) { + return equals(a, b, ignoreCase); + } + + protected boolean valueEquals(CharSequence a, CharSequence b, boolean ignoreCase) { + return equals(a, b, ignoreCase); + } + + private static boolean equals(CharSequence a, CharSequence b, boolean ignoreCase) { + if (a == b) { + return true; + } + + if (a instanceof AsciiString) { + AsciiString aa = (AsciiString) a; + if (ignoreCase) { + return aa.equalsIgnoreCase(b); + } else { + return aa.equals(b); + } + } + + if (b instanceof AsciiString) { + AsciiString ab = (AsciiString) b; + if (ignoreCase) { + return ab.equalsIgnoreCase(a); + } else { + return ab.equals(a); + } + } + + if (ignoreCase) { + return a.toString().equalsIgnoreCase(b.toString()); + } else { + return a.equals(b); + } + } + + @Override + public TextHeaders add(CharSequence name, Object value) { + name = convertName(name); + CharSequence convertedVal = convertValue(value); + int h = hashCode(name); + int i = index(h); + add0(h, i, name, convertedVal); + return this; + } + + @Override + public TextHeaders add(CharSequence name, Iterable values) { + name = convertName(name); + if (values == null) { + throw new NullPointerException("values"); + } + + int h = hashCode(name); + int i = index(h); + for (Object v: values) { + if (v == null) { + break; + } + CharSequence convertedVal = convertValue(v); + add0(h, i, name, convertedVal); + } + return this; + } + + @Override + public TextHeaders add(CharSequence name, Object... values) { + name = convertName(name); + if (values == null) { + throw new NullPointerException("values"); + } + + int h = hashCode(name); + int i = index(h); + for (Object v: values) { + if (v == null) { + break; + } + CharSequence convertedVal = convertValue(v); + add0(h, i, name, convertedVal); + } + return this; + } + + private void add0(int h, int i, CharSequence name, CharSequence value) { + // Update the hash table. + HeaderEntry e = entries[i]; + HeaderEntry newEntry; + entries[i] = newEntry = new HeaderEntry(this, h, name, value); + newEntry.next = e; + + // Update the linked list. + newEntry.addBefore(head); + } + + @Override + public TextHeaders add(TextHeaders headers) { + if (headers == null) { + throw new NullPointerException("headers"); + } + + add0(headers); + return this; + } + + private void add0(TextHeaders headers) { + if (headers.isEmpty()) { + return; + } + + if (headers instanceof DefaultTextHeaders) { + @SuppressWarnings("unchecked") + DefaultTextHeaders m = (DefaultTextHeaders) headers; + HeaderEntry e = m.head.after; + while (e != m.head) { + CharSequence name = e.name; + name = convertName(name); + add(name, convertValue(e.value)); + e = e.after; + } + } else { + for (Entry e: headers.unconvertedEntries()) { + add(e.getKey(), e.getValue()); + } + } + } + + @Override + public boolean remove(CharSequence name) { + if (name == null) { + throw new NullPointerException("name"); + } + int h = hashCode(name); + int i = index(h); + return remove0(h, i, name); + } + + private boolean remove0(int h, int i, CharSequence name) { + HeaderEntry e = entries[i]; + if (e == null) { + return false; + } + + boolean removed = false; + for (;;) { + if (e.hash == h && nameEquals(e.name, name)) { + e.remove(); + HeaderEntry next = e.next; + if (next != null) { + entries[i] = next; + e = next; + } else { + entries[i] = null; + return true; + } + removed = true; + } else { + break; + } + } + + for (;;) { + HeaderEntry next = e.next; + if (next == null) { + break; + } + if (next.hash == h && nameEquals(next.name, name)) { + e.next = next.next; + next.remove(); + removed = true; + } else { + e = next; + } + } + + return removed; + } + + @Override + public TextHeaders set(CharSequence name, Object value) { + name = convertName(name); + CharSequence convertedVal = convertValue(value); + int h = hashCode(name); + int i = index(h); + remove0(h, i, name); + add0(h, i, name, convertedVal); + return this; + } + + @Override + public TextHeaders set(CharSequence name, Iterable values) { + name = convertName(name); + if (values == null) { + throw new NullPointerException("values"); + } + + int h = hashCode(name); + int i = index(h); + + remove0(h, i, name); + for (Object v: values) { + if (v == null) { + break; + } + CharSequence convertedVal = convertValue(v); + add0(h, i, name, convertedVal); + } + + return this; + } + + @Override + public TextHeaders set(CharSequence name, Object... values) { + name = convertName(name); + if (values == null) { + throw new NullPointerException("values"); + } + + int h = hashCode(name); + int i = index(h); + + remove0(h, i, name); + for (Object v: values) { + if (v == null) { + break; + } + CharSequence convertedVal = convertValue(v); + add0(h, i, name, convertedVal); + } + + return this; + } + + @Override + public TextHeaders set(TextHeaders headers) { + if (headers == null) { + throw new NullPointerException("headers"); + } + + clear(); + add0(headers); + return this; + } + + @Override + public TextHeaders clear() { + Arrays.fill(entries, null); + head.before = head.after = head; + size = 0; + return this; + } + + @Override + public CharSequence getUnconverted(CharSequence name) { + if (name == null) { + throw new NullPointerException("name"); + } + + int h = hashCode(name); + int i = index(h); + HeaderEntry e = entries[i]; + CharSequence value = null; + // loop until the first header was found + while (e != null) { + if (e.hash == h && nameEquals(e.name, name)) { + value = e.value; + } + + e = e.next; + } + if (value != null) { + return value; + } + return null; + } + + @Override + public String get(CharSequence name) { + CharSequence v = getUnconverted(name); + if (v == null) { + return null; + } + return v.toString(); + } + + @Override + public String get(CharSequence name, String defaultValue) { + CharSequence v = getUnconverted(name); + if (v == null) { + return defaultValue; + } + return v.toString(); + } + + @Override + public int getInt(CharSequence name, int defaultValue) { + CharSequence v = getUnconverted(name); + if (v == null) { + return defaultValue; + } + + try { + if (v instanceof AsciiString) { + return ((AsciiString) v).parseInt(); + } else { + return Integer.parseInt(v.toString()); + } + } catch (NumberFormatException ignored) { + return defaultValue; + } + } + + @Override + public long getLong(CharSequence name, long defaultValue) { + CharSequence v = getUnconverted(name); + if (v == null) { + return defaultValue; + } + + try { + if (v instanceof AsciiString) { + return ((AsciiString) v).parseLong(); + } else { + return Long.parseLong(v.toString()); + } + } catch (NumberFormatException ignored) { + return defaultValue; + } + } + + @Override + public long getTimeMillis(CharSequence name, long defaultValue) { + CharSequence v = getUnconverted(name); + if (v == null) { + return defaultValue; + } + + return HttpHeaderDateFormat.get().parse(v.toString(), defaultValue); + } + + @Override + public List getAllUnconverted(CharSequence name) { + if (name == null) { + throw new NullPointerException("name"); + } + + List values = new ArrayList(4); + int h = hashCode(name); + int i = index(h); + HeaderEntry e = entries[i]; + while (e != null) { + if (e.hash == h && nameEquals(e.name, name)) { + values.add(e.getValue()); + } + e = e.next; + } + + Collections.reverse(values); + return values; + } + + @Override + public List getAll(CharSequence name) { + if (name == null) { + throw new NullPointerException("name"); + } + + List values = new ArrayList(4); + int h = hashCode(name); + int i = index(h); + HeaderEntry e = entries[i]; + while (e != null) { + if (e.hash == h && nameEquals(e.name, name)) { + values.add(e.getValue().toString()); + } + e = e.next; + } + + Collections.reverse(values); + return values; + } + + @Override + public List> entries() { + int cnt = 0; + int size = size(); + @SuppressWarnings("unchecked") + Map.Entry[] all = new Map.Entry[size]; + + HeaderEntry e = head.after; + while (e != head) { + all[cnt ++] = new StringHeaderEntry(e); + e = e.after; + } + + assert size == cnt; + return Arrays.asList(all); + } + + @Override + public List> unconvertedEntries() { + int cnt = 0; + int size = size(); + @SuppressWarnings("unchecked") + Map.Entry[] all = new Map.Entry[size]; + + HeaderEntry e = head.after; + while (e != head) { + all[cnt ++] = e; + e = e.after; + } + + assert size == cnt; + return Arrays.asList(all); + } + + @Override + public Iterator> iterator() { + return new StringHeaderIterator(); + } + + @Override + public Iterator> unconvertedIterator() { + return new HeaderIterator(); + } + + @Override + public boolean contains(CharSequence name) { + return getUnconverted(name) != null; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return head == head.after; + } + + @Override + public boolean contains(CharSequence name, Object value) { + return contains(name, value, false); + } + + @Override + public boolean contains(CharSequence name, Object value, boolean ignoreCase) { + if (name == null) { + throw new NullPointerException("name"); + } + + int h = hashCode(name); + int i = index(h); + CharSequence convertedVal = convertValue(value); + HeaderEntry e = entries[i]; + while (e != null) { + if (e.hash == h && nameEquals(e.name, name)) { + if (valueEquals(e.value, convertedVal, ignoreCase)) { + return true; + } + } + e = e.next; + } + return false; + } + + @Override + public Set unconvertedNames() { + Set names = new LinkedHashSet(size()); + HeaderEntry e = head.after; + while (e != head) { + names.add(e.getKey()); + e = e.after; + } + return names; + } + + @Override + public Set names() { + Set names = new LinkedHashSet(size()); + HeaderEntry e = head.after; + while (e != head) { + names.add(e.getKey().toString()); + e = e.after; + } + return names; + } + + @Override + public TextHeaders forEachEntry(TextHeaderProcessor processor) { + HeaderEntry e = head.after; + try { + while (e != head) { + if (!processor.process(e.getKey(), e.getValue())) { + break; + } + e = e.after; + } + } catch (Exception ex) { + PlatformDependent.throwException(ex); + } + return this; + } + + private static final class HeaderEntry implements Map.Entry { + private final DefaultTextHeaders parent; + final int hash; + final CharSequence name; + CharSequence value; + HeaderEntry next; + HeaderEntry before, after; + + HeaderEntry(DefaultTextHeaders parent, int hash, CharSequence name, CharSequence value) { + this.parent = parent; + this.hash = hash; + this.name = name; + this.value = value; + } + + HeaderEntry(DefaultTextHeaders parent) { + this.parent = parent; + hash = -1; + name = null; + value = null; + } + + void remove() { + before.after = after; + after.before = before; + parent.size --; + } + + void addBefore(HeaderEntry e) { + after = e; + before = e.before; + before.after = this; + after.before = this; + parent.size ++; + } + + @Override + public CharSequence getKey() { + return name; + } + + @Override + public CharSequence getValue() { + return value; + } + + @Override + public CharSequence setValue(CharSequence value) { + if (value == null) { + throw new NullPointerException("value"); + } + value = parent.convertValue(value); + CharSequence oldValue = this.value; + this.value = value; + return oldValue; + } + + @Override + public String toString() { + return name.toString() + '=' + value.toString(); + } + } + + private static final class StringHeaderEntry implements Entry { + private final Entry entry; + private String name; + private String value; + + StringHeaderEntry(Entry entry) { + this.entry = entry; + } + + @Override + public String getKey() { + if (name == null) { + name = entry.getKey().toString(); + } + return name; + } + + @Override + public String getValue() { + if (value == null) { + value = entry.getValue().toString(); + } + return value; + } + + @Override + public String setValue(String value) { + return entry.setValue(value).toString(); + } + + @Override + public String toString() { + return entry.toString(); + } + } + + private final class HeaderIterator implements Iterator> { + + private HeaderEntry current = head; + + @Override + public boolean hasNext() { + return current.after != head; + } + + @Override + public Entry next() { + current = current.after; + + if (current == head) { + throw new NoSuchElementException(); + } + + return current; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private final class StringHeaderIterator implements Iterator> { + + private HeaderEntry current = head; + + @Override + public boolean hasNext() { + return current.after != head; + } + + @Override + public Entry next() { + current = current.after; + + if (current == head) { + throw new NoSuchElementException(); + } + + return new StringHeaderEntry(current); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + /** + * This DateFormat decodes 3 formats of {@link java.util.Date}, but only encodes the one, + * the first: + *
    + *
  • Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with + * valid generation
  • + *
  • Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification
  • + *
  • Sun Nov 6 08:49:37 1994: obsolete specification
  • + *
+ */ + static final class HttpHeaderDateFormat { + + private static final ParsePosition parsePos = new ParsePosition(0); + private static final ThreadLocal dateFormatThreadLocal = + new ThreadLocal() { + @Override + protected HttpHeaderDateFormat initialValue() { + return new HttpHeaderDateFormat(); + } + }; + + static HttpHeaderDateFormat get() { + return dateFormatThreadLocal.get(); + } + + /** + * Standard date format: + *
Sun, 06 Nov 1994 08:49:37 GMT -> E, d MMM yyyy HH:mm:ss z
+ */ + private final DateFormat dateFormat1 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH); + /** + * First obsolete format: + *
Sunday, 06-Nov-94 08:49:37 GMT -> E, d-MMM-y HH:mm:ss z
+ */ + private final DateFormat dateFormat2 = new SimpleDateFormat("E, dd-MMM-yy HH:mm:ss z", Locale.ENGLISH); + /** + * Second obsolete format + *
Sun Nov 6 08:49:37 1994 -> EEE, MMM d HH:mm:ss yyyy
+ */ + private final DateFormat dateFormat3 = new SimpleDateFormat("E MMM d HH:mm:ss yyyy", Locale.ENGLISH); + + private HttpHeaderDateFormat() { + TimeZone tz = TimeZone.getTimeZone("GMT"); + dateFormat1.setTimeZone(tz); + dateFormat2.setTimeZone(tz); + dateFormat3.setTimeZone(tz); + } + + long parse(String text, long defaultValue) { + Date date = dateFormat1.parse(text, parsePos); + if (date == null) { + date = dateFormat2.parse(text, parsePos); + } + if (date == null) { + date = dateFormat3.parse(text, parsePos); + } + if (date == null) { + return defaultValue; + } + return date.getTime(); + } + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java new file mode 100644 index 0000000000..21eff3162c --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java @@ -0,0 +1,178 @@ +/* + * 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.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +public class EmptyTextHeaders implements TextHeaders { + + protected EmptyTextHeaders() { } + + @Override + public String get(CharSequence name) { + return null; + } + + @Override + public String get(CharSequence name, String defaultValue) { + return defaultValue; + } + + @Override + public int getInt(CharSequence name, int defaultValue) { + return defaultValue; + } + + @Override + public long getLong(CharSequence name, long defaultValue) { + return defaultValue; + } + + @Override + public long getTimeMillis(CharSequence name, long defaultValue) { + return defaultValue; + } + + @Override + public CharSequence getUnconverted(CharSequence name) { + return null; + } + + @Override + public List getAll(CharSequence name) { + return Collections.emptyList(); + } + + @Override + public List getAllUnconverted(CharSequence name) { + return Collections.emptyList(); + } + + @Override + public List> entries() { + return Collections.emptyList(); + } + + @Override + public List> unconvertedEntries() { + return Collections.emptyList(); + } + + @Override + public boolean contains(CharSequence name) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public Set names() { + return Collections.emptySet(); + } + + @Override + public Set unconvertedNames() { + return Collections.emptySet(); + } + + @Override + public TextHeaders add(CharSequence name, Object value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public TextHeaders add(CharSequence name, Iterable values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public TextHeaders add(CharSequence name, Object... values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public TextHeaders add(TextHeaders headers) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public TextHeaders set(CharSequence name, Object value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public TextHeaders set(CharSequence name, Iterable values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public TextHeaders set(CharSequence name, Object... values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public TextHeaders set(TextHeaders headers) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public boolean remove(CharSequence name) { + return false; + } + + @Override + public TextHeaders clear() { + return this; + } + + @Override + public boolean contains(CharSequence name, Object value) { + return false; + } + + @Override + public boolean contains(CharSequence name, Object value, boolean ignoreCase) { + return false; + } + + @Override + public Iterator> iterator() { + return entries().iterator(); + } + + @Override + public Iterator> unconvertedIterator() { + return unconvertedEntries().iterator(); + } + + @Override + public TextHeaders forEachEntry(TextHeaderProcessor processor) { + return this; + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/TextHeaderProcessor.java b/codec/src/main/java/io/netty/handler/codec/TextHeaderProcessor.java new file mode 100644 index 0000000000..43aa9df7a6 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/TextHeaderProcessor.java @@ -0,0 +1,21 @@ +/* + * 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 interface TextHeaderProcessor { + boolean process(CharSequence name, CharSequence value) throws Exception; +} diff --git a/codec/src/main/java/io/netty/handler/codec/TextHeaders.java b/codec/src/main/java/io/netty/handler/codec/TextHeaders.java new file mode 100644 index 0000000000..a84f069bc2 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/TextHeaders.java @@ -0,0 +1,236 @@ +/* + * 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.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * 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(CharSequence)}, + * 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 AsciiString}. + */ +public interface TextHeaders extends Iterable> { + /** + * Returns the value of a header with the specified name. If there are + * more than one values for the specified name, the first value is returned. + * + * @param name The name of the header to search + * @return The first header value or {@code null} if there is no such header + */ + String get(CharSequence name); + + String get(CharSequence name, String defaultValue); + int getInt(CharSequence name, int defaultValue); + long getLong(CharSequence name, long defaultValue); + long getTimeMillis(CharSequence name, long defaultValue); + + /** + * 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 + */ + CharSequence getUnconverted(CharSequence name); + + /** + * 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 + */ + List getAll(CharSequence name); + + /** + * 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 + */ + List getAllUnconverted(CharSequence name); + + /** + * Returns a new {@link List} that contains all headers in this object. Note that modifying the + * returned {@link List} will not affect the state of this object. If you intend to enumerate over the header + * entries only, use {@link #iterator()} instead, which has much less overhead. + */ + List> entries(); + + /** + * Returns a new {@link List} that contains all headers in this object. Note that modifying the + * returned {@link List} will not affect the state of this object. If you intend to enumerate over the header + * entries only, use {@link #iterator()} instead, which has much less overhead. + */ + List> unconvertedEntries(); + + /** + * Checks to see if there is a header with the specified name + * + * @param name The name of the header to search for + * @return True if at least one header is found + */ + boolean contains(CharSequence name); + + int size(); + + /** + * Checks if no header exists. + */ + boolean isEmpty(); + + /** + * Returns a new {@link Set} that contains the names of all headers in this object. Note that modifying the + * returned {@link Set} will not affect the state of this object. If you intend to enumerate over the header + * entries only, use {@link #iterator()} instead, which has much less overhead. + */ + Set names(); + + /** + * 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. + */ + Set unconvertedNames(); + + /** + * Adds a new header with the specified name and value. + * + * If the specified value is not a {@link String}, it is converted + * into a {@link String} by {@link Object#toString()}, except in the cases + * of {@link java.util.Date} and {@link java.util.Calendar}, which are formatted to the date + * format defined in RFC2616. + * + * @param name The name of the header being added + * @param value The value of the header being added + * + * @return {@code this} + */ + TextHeaders add(CharSequence name, Object value); + + /** + * Adds a new header with the specified name and values. + * + * This getMethod can be represented approximately as the following code: + *
+     * for (Object v: values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     headers.add(name, v);
+     * }
+     * 
+ * + * @param name The name of the headepublic abstract rs being set + * @param values The values of the headers being set + * @return {@code this} + */ + TextHeaders add(CharSequence name, Iterable values); + + TextHeaders add(CharSequence name, Object... values); + + /** + * Adds all header entries of the specified {@code headers}. + * + * @return {@code this} + */ + TextHeaders add(TextHeaders headers); + + /** + * Sets a header with the specified name and value. + * + * If there is an existing header with the same name, it is removed. + * If the specified value is not a {@link String}, it is converted into a + * {@link String} by {@link Object#toString()}, except for {@link java.util.Date} + * and {@link java.util.Calendar}, which are formatted to the date format defined in + * RFC2616. + * + * @param name The name of the header being set + * @param value The value of the header being set + * @return {@code this} + */ + TextHeaders set(CharSequence name, Object value); + + /** + * Sets a header with the specified name and values. + * + * If there is an existing header with the same name, it is removed. + * This getMethod can be represented approximately as the following code: + *
+     * headers.remove(name);
+     * for (Object v: values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     headers.add(name, v);
+     * }
+     * 
+ * + * @param name The name of the headers being set + * @param values The values of the headers being set + * @return {@code this} + */ + TextHeaders set(CharSequence name, Iterable values); + + TextHeaders set(CharSequence name, Object... values); + + /** + * Cleans the current header entries and copies all header entries of the specified {@code headers}. + * + * @return {@code this} + */ + TextHeaders set(TextHeaders headers); + + /** + * Removes the header with the specified name. + * + * @param name The name of the header to remove + * @return {@code true} if and only if at least one entry has been removed + */ + boolean remove(CharSequence name); + + /** + * Removes all headers. + * + * @return {@code this} + */ + TextHeaders clear(); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the headername + * @param value the value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean contains(CharSequence name, Object value); + + boolean contains(CharSequence name, Object value, boolean ignoreCase); + + @Override + Iterator> iterator(); + + Iterator> unconvertedIterator(); + + TextHeaders forEachEntry(TextHeaderProcessor processor); +} diff --git a/common/src/main/java/io/netty/util/internal/EmptyArrays.java b/common/src/main/java/io/netty/util/internal/EmptyArrays.java index d31fa02f13..308710c3b0 100644 --- a/common/src/main/java/io/netty/util/internal/EmptyArrays.java +++ b/common/src/main/java/io/netty/util/internal/EmptyArrays.java @@ -22,6 +22,7 @@ import java.security.cert.X509Certificate; public final class EmptyArrays { public static final byte[] EMPTY_BYTES = new byte[0]; + public static final char[] EMPTY_CHARS = new char[0]; public static final boolean[] EMPTY_BOOLEANS = new boolean[0]; public static final double[] EMPTY_DOUBLES = new double[0]; public static final float[] EMPTY_FLOATS = new float[0]; diff --git a/example/src/main/java/io/netty/example/http/helloworld/HttpHelloWorldServerHandler.java b/example/src/main/java/io/netty/example/http/helloworld/HttpHelloWorldServerHandler.java index 51ce9afcd8..9351721c66 100644 --- a/example/src/main/java/io/netty/example/http/helloworld/HttpHelloWorldServerHandler.java +++ b/example/src/main/java/io/netty/example/http/helloworld/HttpHelloWorldServerHandler.java @@ -19,11 +19,11 @@ import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.AsciiString; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpRequest; -import static io.netty.handler.codec.http.HttpHeaders.Names.*; import static io.netty.handler.codec.http.HttpHeaders.*; import static io.netty.handler.codec.http.HttpResponseStatus.*; import static io.netty.handler.codec.http.HttpVersion.*; @@ -31,6 +31,11 @@ import static io.netty.handler.codec.http.HttpVersion.*; public class HttpHelloWorldServerHandler extends ChannelInboundHandlerAdapter { private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' }; + private static final AsciiString CONTENT_TYPE = new AsciiString("Content-Type"); + private static final AsciiString CONTENT_LENGTH = new AsciiString("Content-Length"); + private static final AsciiString CONNECTION = new AsciiString("Connection"); + private static final AsciiString KEEP_ALIVE = new AsciiString("keep-alive"); + @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); @@ -52,7 +57,7 @@ public class HttpHelloWorldServerHandler extends ChannelInboundHandlerAdapter { if (!keepAlive) { ctx.write(response).addListener(ChannelFutureListener.CLOSE); } else { - response.headers().set(CONNECTION, Values.KEEP_ALIVE); + response.headers().set(CONNECTION, KEEP_ALIVE); ctx.write(response); } } diff --git a/license/LICENSE.harmony.txt b/license/LICENSE.harmony.txt new file mode 100644 index 0000000000..66a27ec5ff --- /dev/null +++ b/license/LICENSE.harmony.txt @@ -0,0 +1,177 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +