Introduce TextHeaders and AsciiString

Motivation:

We have quite a bit of code duplication between HTTP/1, HTTP/2, SPDY,
and STOMP codec, because they all have a notion of 'headers', which is a
multimap of string names and values.

Modifications:

- Add TextHeaders and its default implementation
- Add AsciiString to replace HttpHeaderEntity
  - Borrowed some portion from Apache Harmony's java.lang.String.
- Reimplement HttpHeaders, SpdyHeaders, and StompHeaders using
  TextHeaders
- Add AsciiHeadersEncoder to reuse the encoding a TextHeaders
  - Used a dedicated encoder for HTTP headers for better performance
    though
- Remove shortcut methods in SpdyHeaders
- Replace SpdyHeaders.getStatus() with HttpResponseStatus.parseLine()

Result:

- Removed quite a bit of code duplication in the header implementations.
- Slightly better performance thanks to improved header validation and
  hash code calculation
This commit is contained in:
Trustin Lee 2014-06-05 18:31:04 +09:00
parent 2a2a21ec59
commit 681d460938
29 changed files with 3677 additions and 1412 deletions

View File

@ -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/

View File

@ -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<String> getAll(final String name) {
return getAll((CharSequence) name);
public List<String> getAll(String name) {
return headers.getAll(name);
}
@Override
public List<String> getAll(final CharSequence name) {
if (name == null) {
throw new NullPointerException("name");
}
LinkedList<String> values = new LinkedList<String>();
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<String> getAll(CharSequence name) {
return headers.getAll(name);
}
@Override
public List<Map.Entry<String, String>> entries() {
List<Map.Entry<String, String>> all =
new LinkedList<Map.Entry<String, String>>();
HeaderEntry e = head.after;
while (e != head) {
all.add(e);
e = e.after;
}
return all;
return headers.entries();
}
@Override
public Iterator<Map.Entry<String, String>> 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<String> names() {
Set<String> names = new LinkedHashSet<String>();
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<Map.Entry<String, String>> {
private HeaderEntry current = head;
static class NonValidatingTextHeaders extends DefaultTextHeaders {
@Override
public boolean hasNext() {
return current.after != head;
}
@Override
public Entry<String, String> 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<String, String> {
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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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<Map.Entry<String, String>>
}
}
/**
* 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<Map.Entry<String, String>>
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<Map.Entry<String, String>>
}
}
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<Map.Entry<String, String>>
}
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<Map.Entry<String, String>>
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<Map.Entry<String, String>>
/**
* @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<String> 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<Map.Entry<String, String>>
/**
* Returns {@code true} if a header with the name and value exists.
*
* @param name the headername
* @param value the value
* @param 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);
}
}

View File

@ -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 : '?';
}
}

View File

@ -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<HttpResponseStatus> {
return new HttpResponseStatus(code, reasonPhrase + " (" + code + ')');
}
/**
* Parses the specified HTTP status line into a {@link HttpResponseStatus}. The expected formats of the line are:
* <ul>
* <li>{@code statusCode} (e.g. 200)</li>
* <li>{@code statusCode} {@code reasonPhrase} (e.g. 404 Not Found)</li>
* </ul>
*
* @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;

View File

@ -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<String> getAll(final String name) {
if (name == null) {
throw new NullPointerException("name");
}
LinkedList<String> values = new LinkedList<String>();
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<Map.Entry<String, String>> entries() {
List<Map.Entry<String, String>> all =
new LinkedList<Map.Entry<String, String>>();
HeaderEntry e = head.after;
while (e != head) {
all.add(e);
e = e.after;
}
return all;
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
return new HeaderIterator();
}
@Override
public boolean contains(String name) {
return get(name) != null;
}
@Override
public Set<String> names() {
Set<String> names = new TreeSet<String>();
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<Map.Entry<String, String>> {
private HeaderEntry current = head;
@Override
public boolean hasNext() {
return current.after != head;
}
@Override
public Entry<String, String> 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<String, String> {
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;
}
}
}

View File

@ -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");
}

View File

@ -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<Map.Entry<String, String>> {
public static final SpdyHeaders EMPTY_HEADERS = new SpdyHeaders() {
@Override
public List<String> getAll(String name) {
return Collections.emptyList();
}
@Override
public List<Map.Entry<String, String>> entries() {
return Collections.emptyList();
}
@Override
public boolean contains(String name) {
return false;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public Set<String> 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<Map.Entry<String, String>> 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<Map.Entry<String, String>> 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<String> 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<Map.Entry<String, String>> 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<String> 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);
}

View File

@ -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<SpdyFrame> {
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<SpdyFrame> {
// 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<SpdyFrame> {
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<SpdyFrame> {
// 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<SpdyFrame> {
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<SpdyFrame> {
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<String, String> e: responseFrame.headers()) {

View File

@ -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<HttpObject> {
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<HttpObject> {
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<String, String> 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<HttpObject> {
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<String, String> entry: httpResponse.headers()) {

View File

@ -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<String> 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

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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<String, List<String>> headers = new LinkedHashMap<String, List<String>>();
public boolean has(String key) {
List<String> values = headers.get(key);
return values != null && !values.isEmpty();
}
public String get(String key) {
List<String> values = headers.get(key);
if (values != null && !values.isEmpty()) {
return values.get(0);
} else {
return null;
}
}
public void add(String key, String value) {
List<String> values = headers.get(key);
if (values == null) {
values = new ArrayList<String>();
headers.put(key, values);
}
values.add(value);
}
@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
public void set(String key, String value) {
headers.put(key, Arrays.asList(value));
}
public List<String> getAll(String key) {
List<String> values = headers.get(key);
if (values != null) {
return new ArrayList<String>(values);
} else {
return new ArrayList<String>();
}
}
public Set<String> 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<String> 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);
}

View File

@ -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

View File

@ -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<State> {
}
} 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<State> {
}
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;
}

View File

@ -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<StompSubframe>
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<String> 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;
}
}

View File

@ -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";

View File

@ -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 : '?';
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<CharSequence, CharSequence> 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<CharSequence> getAllUnconverted(CharSequence name) {
if (name == null) {
throw new NullPointerException("name");
}
List<CharSequence> values = new ArrayList<CharSequence>(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<String> getAll(CharSequence name) {
if (name == null) {
throw new NullPointerException("name");
}
List<String> values = new ArrayList<String>(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<Map.Entry<String, String>> entries() {
int cnt = 0;
int size = size();
@SuppressWarnings("unchecked")
Map.Entry<String, String>[] 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<Map.Entry<CharSequence, CharSequence>> unconvertedEntries() {
int cnt = 0;
int size = size();
@SuppressWarnings("unchecked")
Map.Entry<CharSequence, CharSequence>[] 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<Entry<String, String>> iterator() {
return new StringHeaderIterator();
}
@Override
public Iterator<Entry<CharSequence, CharSequence>> 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<CharSequence> unconvertedNames() {
Set<CharSequence> names = new LinkedHashSet<CharSequence>(size());
HeaderEntry e = head.after;
while (e != head) {
names.add(e.getKey());
e = e.after;
}
return names;
}
@Override
public Set<String> names() {
Set<String> names = new LinkedHashSet<String>(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<CharSequence, CharSequence> {
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<String, String> {
private final Entry<CharSequence, CharSequence> entry;
private String name;
private String value;
StringHeaderEntry(Entry<CharSequence, CharSequence> entry) {
this.entry = entry;
}
@Override
public String getKey() {
if (name == null) {
name = entry.getKey().toString();
}
return name;
}
@Override
public String getValue() {
if (value == null) {
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<Map.Entry<CharSequence, CharSequence>> {
private HeaderEntry current = head;
@Override
public boolean hasNext() {
return current.after != head;
}
@Override
public Entry<CharSequence, CharSequence> 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<Map.Entry<String, String>> {
private HeaderEntry current = head;
@Override
public boolean hasNext() {
return current.after != head;
}
@Override
public Entry<String, String> 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:
* <ul>
* <li>Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with
* valid generation</li>
* <li>Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification</li>
* <li>Sun Nov 6 08:49:37 1994: obsolete specification</li>
* </ul>
*/
static final class HttpHeaderDateFormat {
private static final ParsePosition parsePos = new ParsePosition(0);
private static final ThreadLocal<HttpHeaderDateFormat> dateFormatThreadLocal =
new ThreadLocal<HttpHeaderDateFormat>() {
@Override
protected HttpHeaderDateFormat initialValue() {
return new HttpHeaderDateFormat();
}
};
static HttpHeaderDateFormat get() {
return dateFormatThreadLocal.get();
}
/**
* Standard date format:
* <pre>Sun, 06 Nov 1994 08:49:37 GMT -> E, d MMM yyyy HH:mm:ss z</pre>
*/
private final DateFormat dateFormat1 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
/**
* First obsolete format:
* <pre>Sunday, 06-Nov-94 08:49:37 GMT -> E, d-MMM-y HH:mm:ss z</pre>
*/
private final DateFormat dateFormat2 = new SimpleDateFormat("E, dd-MMM-yy HH:mm:ss z", Locale.ENGLISH);
/**
* Second obsolete format
* <pre>Sun Nov 6 08:49:37 1994 -> EEE, MMM d HH:mm:ss yyyy</pre>
*/
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();
}
}
}

View File

@ -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<String> getAll(CharSequence name) {
return Collections.emptyList();
}
@Override
public List<CharSequence> getAllUnconverted(CharSequence name) {
return Collections.emptyList();
}
@Override
public List<Entry<String, String>> entries() {
return Collections.emptyList();
}
@Override
public List<Entry<CharSequence, CharSequence>> 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<String> names() {
return Collections.emptySet();
}
@Override
public Set<CharSequence> 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<Entry<String, String>> iterator() {
return entries().iterator();
}
@Override
public Iterator<Entry<CharSequence, CharSequence>> unconvertedIterator() {
return unconvertedEntries().iterator();
}
@Override
public TextHeaders forEachEntry(TextHeaderProcessor processor) {
return this;
}
}

View File

@ -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;
}

View File

@ -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<Map.Entry<String, String>> {
/**
* Returns the value of a header with the specified name. If there are
* more than one values for the specified name, the first value is returned.
*
* @param name The name of the header to search
* @return The first header value or {@code 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<String> 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<CharSequence> 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<Entry<String, String>> 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<Entry<CharSequence, CharSequence>> 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<String> 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<CharSequence> 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 <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
*
* @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:
* <pre>
* for (Object v: values) {
* if (v == null) {
* break;
* }
* headers.add(name, v);
* }
* </pre>
*
* @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
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
*
* @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:
* <pre>
* headers.remove(name);
* for (Object v: values) {
* if (v == null) {
* break;
* }
* headers.add(name, v);
* }
* </pre>
*
* @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<Entry<String, String>> iterator();
Iterator<Entry<CharSequence, CharSequence>> unconvertedIterator();
TextHeaders forEachEntry(TextHeaderProcessor processor);
}

View File

@ -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];

View File

@ -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);
}
}

177
license/LICENSE.harmony.txt Normal file
View File

@ -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