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 - Remove shortcut methods in SpdyHttpHeaders - 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:
parent
7d37af5dfb
commit
de2872f7f7
@ -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/
|
||||
|
||||
|
@ -15,30 +15,36 @@
|
||||
*/
|
||||
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.TextHeaderProcessor;
|
||||
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 {
|
||||
public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeaders {
|
||||
|
||||
private static final int BUCKET_SIZE = 17;
|
||||
private static final int HIGHEST_INVALID_NAME_CHAR_MASK = ~63;
|
||||
private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15;
|
||||
|
||||
private static int index(int hash) {
|
||||
return hash % BUCKET_SIZE;
|
||||
/**
|
||||
* A look-up table used for checking if a character in a header name is prohibited.
|
||||
*/
|
||||
private static final byte[] LOOKUP_TABLE = new byte[~HIGHEST_INVALID_NAME_CHAR_MASK + 1];
|
||||
|
||||
static {
|
||||
LOOKUP_TABLE['\t'] = -1;
|
||||
LOOKUP_TABLE['\n'] = -1;
|
||||
LOOKUP_TABLE[0x0b] = -1;
|
||||
LOOKUP_TABLE['\f'] = -1;
|
||||
LOOKUP_TABLE[' '] = -1;
|
||||
LOOKUP_TABLE[','] = -1;
|
||||
LOOKUP_TABLE[':'] = -1;
|
||||
LOOKUP_TABLE[';'] = -1;
|
||||
LOOKUP_TABLE['='] = -1;
|
||||
}
|
||||
|
||||
private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];
|
||||
private final HeaderEntry head = new HeaderEntry();
|
||||
protected final boolean validate;
|
||||
|
||||
public DefaultHttpHeaders() {
|
||||
@ -47,407 +53,236 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
||||
|
||||
public DefaultHttpHeaders(boolean validate) {
|
||||
this.validate = validate;
|
||||
head.before = head.after = head;
|
||||
}
|
||||
|
||||
void validateHeaderName0(CharSequence headerName) {
|
||||
validateHeaderName(headerName);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
return this;
|
||||
} else {
|
||||
return super.add(headers);
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
return this;
|
||||
} else {
|
||||
return super.set(headers);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders add(final CharSequence name, final Object value) {
|
||||
CharSequence strVal;
|
||||
protected CharSequence convertName(CharSequence name) {
|
||||
name = super.convertName(name);
|
||||
if (validate) {
|
||||
validateHeaderName0(name);
|
||||
strVal = toCharSequence(value);
|
||||
validateHeaderValue(strVal);
|
||||
if (name instanceof AsciiString) {
|
||||
validateName((AsciiString) name);
|
||||
} else {
|
||||
strVal = toCharSequence(value);
|
||||
validateName(name);
|
||||
}
|
||||
int h = hash(name);
|
||||
int i = index(h);
|
||||
add0(h, i, name, strVal);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException("value");
|
||||
}
|
||||
|
||||
CharSequence seq;
|
||||
if (value instanceof CharSequence) {
|
||||
seq = (CharSequence) value;
|
||||
} else if (value instanceof Number) {
|
||||
seq = value.toString();
|
||||
} else if (value instanceof Date) {
|
||||
seq = HttpHeaderDateFormat.get().format((Date) value);
|
||||
} else if (value instanceof Calendar) {
|
||||
seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
|
||||
} else {
|
||||
seq = value.toString();
|
||||
}
|
||||
|
||||
if (validate) {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders add(CharSequence name, Object value) {
|
||||
super.add(name, value);
|
||||
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 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 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);
|
||||
super.add(name, values);
|
||||
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 add(CharSequence name, Object... values) {
|
||||
super.add(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
int h = hash(name);
|
||||
int i = index(h);
|
||||
|
||||
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 add(TextHeaders headers) {
|
||||
super.add(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(CharSequence name, Object value) {
|
||||
super.set(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(CharSequence name, Object... values) {
|
||||
super.set(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(CharSequence name, Iterable<?> values) {
|
||||
super.set(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(TextHeaders headers) {
|
||||
super.set(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders clear() {
|
||||
Arrays.fill(entries, null);
|
||||
head.before = head.after = head;
|
||||
super.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@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 value.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@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(CharSequence name) {
|
||||
return get(name) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return head == head.after;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
void encode(ByteBuf buf) {
|
||||
HeaderEntry e = head.after;
|
||||
while (e != head) {
|
||||
e.encode(buf);
|
||||
e = e.after;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@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) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException("value");
|
||||
}
|
||||
validateHeaderValue(value);
|
||||
CharSequence oldValue = this.value;
|
||||
this.value = value;
|
||||
return oldValue.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return key.toString() + '=' + value.toString();
|
||||
}
|
||||
|
||||
void encode(ByteBuf buf) {
|
||||
HttpHeaders.encode(key, value, buf);
|
||||
}
|
||||
public HttpHeaders forEachEntry(TextHeaderProcessor processor) {
|
||||
super.forEachEntry(processor);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public abstract class DefaultHttpMessage extends DefaultHttpObject implements Ht
|
||||
buf.append("(version: ");
|
||||
buf.append(getProtocolVersion().text());
|
||||
buf.append(", keepAlive: ");
|
||||
buf.append(HttpHeaders.isKeepAlive(this));
|
||||
buf.append(HttpHeaderUtil.isKeepAlive(this));
|
||||
buf.append(')');
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
appendHeaders(buf);
|
||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
import java.util.Map;
|
||||
@ -39,7 +40,7 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
|
||||
|
||||
public DefaultLastHttpContent(ByteBuf content, boolean validateHeaders) {
|
||||
super(content);
|
||||
trailingHeaders = new TrailingHeaders(validateHeaders);
|
||||
trailingHeaders = new TrailingHttpHeaders(validateHeaders);
|
||||
this.validateHeaders = validateHeaders;
|
||||
}
|
||||
|
||||
@ -106,20 +107,23 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TrailingHeaders extends DefaultHttpHeaders {
|
||||
TrailingHeaders(boolean validate) {
|
||||
private static final class TrailingHttpHeaders extends DefaultHttpHeaders {
|
||||
TrailingHttpHeaders(boolean validate) {
|
||||
super(validate);
|
||||
}
|
||||
|
||||
@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 (validate) {
|
||||
if (AsciiString.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) ||
|
||||
AsciiString.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) ||
|
||||
AsciiString.equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) {
|
||||
throw new IllegalArgumentException(
|
||||
"prohibited trailing header: " + name);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.handler.codec.EmptyTextHeaders;
|
||||
import io.netty.handler.codec.TextHeaderProcessor;
|
||||
import io.netty.handler.codec.TextHeaders;
|
||||
|
||||
public class EmptyHttpHeaders extends EmptyTextHeaders implements HttpHeaders {
|
||||
|
||||
public static final EmptyHttpHeaders INSTANCE = new EmptyHttpHeaders();
|
||||
|
||||
protected EmptyHttpHeaders() { }
|
||||
|
||||
@Override
|
||||
public HttpHeaders add(CharSequence name, Object value) {
|
||||
super.add(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders add(CharSequence name, Iterable<?> values) {
|
||||
super.add(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders add(CharSequence name, Object... values) {
|
||||
super.add(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders add(TextHeaders headers) {
|
||||
super.add(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(CharSequence name, Object value) {
|
||||
super.set(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(CharSequence name, Object... values) {
|
||||
super.set(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(CharSequence name, Iterable<?> values) {
|
||||
super.set(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders set(TextHeaders headers) {
|
||||
super.set(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders clear() {
|
||||
super.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders forEachEntry(TextHeaderProcessor processor) {
|
||||
super.forEachEntry(processor);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.compression.ZlibCodecFactory;
|
||||
import io.netty.handler.codec.compression.ZlibWrapper;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
@ -96,7 +97,7 @@ public class HttpContentCompressor extends HttpContentEncoder {
|
||||
protected Result beginEncode(HttpResponse headers, CharSequence acceptEncoding) throws Exception {
|
||||
String contentEncoding = headers.headers().get(HttpHeaders.Names.CONTENT_ENCODING);
|
||||
if (contentEncoding != null &&
|
||||
!HttpHeaders.equalsIgnoreCase(HttpHeaders.Values.IDENTITY, contentEncoding)) {
|
||||
!AsciiString.equalsIgnoreCase(HttpHeaders.Values.IDENTITY, contentEncoding)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
final class HttpHeaderEntity implements CharSequence {
|
||||
|
||||
private final String name;
|
||||
private final int hash;
|
||||
private final byte[] bytes;
|
||||
|
||||
public HttpHeaderEntity(String name) {
|
||||
this.name = name;
|
||||
hash = HttpHeaders.hash(name);
|
||||
bytes = name.getBytes(CharsetUtil.US_ASCII);
|
||||
}
|
||||
|
||||
int hash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return (char) bytes[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
return new HttpHeaderEntity(name.substring(start, end));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
void encode(ByteBuf buf) {
|
||||
buf.writeBytes(bytes);
|
||||
}
|
||||
}
|
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* 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.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpConstants.*;
|
||||
|
||||
public final class HttpHeaderUtil {
|
||||
|
||||
private static final byte[] HEADER_SEPERATOR = { COLON, SP };
|
||||
private static final byte[] CRLF = { CR, LF };
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if the connection can remain open and
|
||||
* thus 'kept alive'. This methods respects the value of the
|
||||
* {@code "Connection"} header first and then the return value of
|
||||
* {@link HttpVersion#isKeepAliveDefault()}.
|
||||
*/
|
||||
public static boolean isKeepAlive(HttpMessage message) {
|
||||
String connection = message.headers().get(Names.CONNECTION);
|
||||
if (connection != null && AsciiString.equalsIgnoreCase(Values.CLOSE, connection)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (message.getProtocolVersion().isKeepAliveDefault()) {
|
||||
return !AsciiString.equalsIgnoreCase(Values.CLOSE, connection);
|
||||
} else {
|
||||
return AsciiString.equalsIgnoreCase(Values.KEEP_ALIVE, connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the {@code "Connection"} header depending on the
|
||||
* protocol version of the specified message. This getMethod sets or removes
|
||||
* the {@code "Connection"} header depending on what the default keep alive
|
||||
* mode of the message's protocol version is, as specified by
|
||||
* {@link HttpVersion#isKeepAliveDefault()}.
|
||||
* <ul>
|
||||
* <li>If the connection is kept alive by default:
|
||||
* <ul>
|
||||
* <li>set to {@code "close"} if {@code keepAlive} is {@code false}.</li>
|
||||
* <li>remove otherwise.</li>
|
||||
* </ul></li>
|
||||
* <li>If the connection is closed by default:
|
||||
* <ul>
|
||||
* <li>set to {@code "keep-alive"} if {@code keepAlive} is {@code true}.</li>
|
||||
* <li>remove otherwise.</li>
|
||||
* </ul></li>
|
||||
* </ul>
|
||||
*/
|
||||
public static void setKeepAlive(HttpMessage message, boolean keepAlive) {
|
||||
HttpHeaders h = message.headers();
|
||||
if (message.getProtocolVersion().isKeepAliveDefault()) {
|
||||
if (keepAlive) {
|
||||
h.remove(Names.CONNECTION);
|
||||
} else {
|
||||
h.set(Names.CONNECTION, Values.CLOSE);
|
||||
}
|
||||
} else {
|
||||
if (keepAlive) {
|
||||
h.set(Names.CONNECTION, Values.KEEP_ALIVE);
|
||||
} else {
|
||||
h.remove(Names.CONNECTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the content. Please note that this value is
|
||||
* not retrieved from {@link HttpContent#content()} but from the
|
||||
* {@code "Content-Length"} header, and thus they are independent from each
|
||||
* other.
|
||||
*
|
||||
* @return the content length
|
||||
*
|
||||
* @throws NumberFormatException
|
||||
* if the message does not have the {@code "Content-Length"} header
|
||||
* or its value is not a number
|
||||
*/
|
||||
public static long getContentLength(HttpMessage message) {
|
||||
String value = message.headers().get(Names.CONTENT_LENGTH);
|
||||
if (value != null) {
|
||||
return Long.parseLong(value);
|
||||
}
|
||||
|
||||
// We know the content length if it's a Web Socket message even if
|
||||
// Content-Length header is missing.
|
||||
long webSocketContentLength = getWebSocketContentLength(message);
|
||||
if (webSocketContentLength >= 0) {
|
||||
return webSocketContentLength;
|
||||
}
|
||||
|
||||
// Otherwise we don't.
|
||||
throw new NumberFormatException("header not found: " + Names.CONTENT_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the content. Please note that this value is
|
||||
* not retrieved from {@link HttpContent#content()} but from the
|
||||
* {@code "Content-Length"} header, and thus they are independent from each
|
||||
* other.
|
||||
*
|
||||
* @return the content length or {@code defaultValue} if this message does
|
||||
* not have the {@code "Content-Length"} header or its value is not
|
||||
* a number
|
||||
*/
|
||||
public static long getContentLength(HttpMessage message, long defaultValue) {
|
||||
String contentLength = message.headers().get(Names.CONTENT_LENGTH);
|
||||
if (contentLength != null) {
|
||||
try {
|
||||
return Long.parseLong(contentLength);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// We know the content length if it's a Web Socket message even if
|
||||
// Content-Length header is missing.
|
||||
long webSocketContentLength = getWebSocketContentLength(message);
|
||||
if (webSocketContentLength >= 0) {
|
||||
return webSocketContentLength;
|
||||
}
|
||||
|
||||
// Otherwise we don't.
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content length of the specified web socket message. If the
|
||||
* specified message is not a web socket message, {@code -1} is returned.
|
||||
*/
|
||||
private static int getWebSocketContentLength(HttpMessage message) {
|
||||
// WebSockset messages have constant content-lengths.
|
||||
HttpHeaders h = message.headers();
|
||||
if (message instanceof HttpRequest) {
|
||||
HttpRequest req = (HttpRequest) message;
|
||||
if (HttpMethod.GET.equals(req.getMethod()) &&
|
||||
h.contains(Names.SEC_WEBSOCKET_KEY1) &&
|
||||
h.contains(Names.SEC_WEBSOCKET_KEY2)) {
|
||||
return 8;
|
||||
}
|
||||
} else if (message instanceof HttpResponse) {
|
||||
HttpResponse res = (HttpResponse) message;
|
||||
if (res.getStatus().code() == 101 &&
|
||||
h.contains(Names.SEC_WEBSOCKET_ORIGIN) &&
|
||||
h.contains(Names.SEC_WEBSOCKET_LOCATION)) {
|
||||
return 16;
|
||||
}
|
||||
}
|
||||
|
||||
// Not a web socket message
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "Content-Length"} header.
|
||||
*/
|
||||
public static void setContentLength(HttpMessage message, long length) {
|
||||
message.headers().set(Names.CONTENT_LENGTH, length);
|
||||
}
|
||||
|
||||
public static boolean isContentLengthSet(HttpMessage m) {
|
||||
return m.headers().contains(Names.CONTENT_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if the specified message contains the
|
||||
* {@code "Expect: 100-continue"} header.
|
||||
*/
|
||||
public static boolean is100ContinueExpected(HttpMessage message) {
|
||||
// Expect: 100-continue is for requests only.
|
||||
if (!(message instanceof HttpRequest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// It works only on HTTP/1.1 or later.
|
||||
if (message.getProtocolVersion().compareTo(HttpVersion.HTTP_1_1) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// In most cases, there will be one or zero 'Expect' header.
|
||||
String value = message.headers().get(Names.EXPECT);
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
if (AsciiString.equalsIgnoreCase(Values.CONTINUE, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Multiple 'Expect' headers. Search through them.
|
||||
return message.headers().contains(Names.EXPECT, Values.CONTINUE, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or removes the {@code "Expect: 100-continue"} header to / from the
|
||||
* specified message. If the specified {@code value} is {@code true},
|
||||
* the {@code "Expect: 100-continue"} header is set and all other previous
|
||||
* {@code "Expect"} headers are removed. Otherwise, all {@code "Expect"}
|
||||
* headers are removed completely.
|
||||
*/
|
||||
public static void set100ContinueExpected(HttpMessage message, boolean expected) {
|
||||
if (expected) {
|
||||
message.headers().set(Names.EXPECT, Values.CONTINUE);
|
||||
} else {
|
||||
message.headers().remove(Names.EXPECT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the transfer encoding in a specified {@link HttpMessage} is chunked
|
||||
*
|
||||
* @param message The message to check
|
||||
* @return True if transfer encoding is chunked, otherwise false
|
||||
*/
|
||||
public static boolean isTransferEncodingChunked(HttpMessage message) {
|
||||
return message.headers().contains(Names.TRANSFER_ENCODING, Values.CHUNKED, true);
|
||||
}
|
||||
|
||||
public static void setTransferEncodingChunked(HttpMessage m, boolean chunked) {
|
||||
if (chunked) {
|
||||
m.headers().add(Names.TRANSFER_ENCODING, Values.CHUNKED);
|
||||
m.headers().remove(Names.CONTENT_LENGTH);
|
||||
} else {
|
||||
List<CharSequence> values = m.headers().getAllUnconverted(Names.TRANSFER_ENCODING);
|
||||
if (values.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Iterator<CharSequence> valuesIt = values.iterator();
|
||||
while (valuesIt.hasNext()) {
|
||||
CharSequence value = valuesIt.next();
|
||||
if (AsciiString.equalsIgnoreCase(value, Values.CHUNKED)) {
|
||||
valuesIt.remove();
|
||||
}
|
||||
}
|
||||
if (values.isEmpty()) {
|
||||
m.headers().remove(Names.TRANSFER_ENCODING);
|
||||
} else {
|
||||
m.headers().set(Names.TRANSFER_ENCODING, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void encodeAscii0(CharSequence seq, ByteBuf buf) {
|
||||
int length = seq.length();
|
||||
for (int i = 0 ; i < length; i++) {
|
||||
buf.writeByte((byte) seq.charAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
private HttpHeaderUtil() { }
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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 : '?';
|
||||
}
|
||||
}
|
@ -197,7 +197,7 @@ public class HttpMethod implements Comparable<HttpMethod> {
|
||||
|
||||
void encode(ByteBuf buf) {
|
||||
if (bytes == null) {
|
||||
HttpHeaders.encodeAscii0(name, buf);
|
||||
HttpHeaderUtil.encodeAscii0(name, buf);
|
||||
} else {
|
||||
buf.writeBytes(bytes);
|
||||
}
|
||||
|
@ -95,17 +95,17 @@ public class HttpObjectAggregator
|
||||
|
||||
@Override
|
||||
protected boolean hasContentLength(HttpMessage start) throws Exception {
|
||||
return HttpHeaders.isContentLengthSet(start);
|
||||
return HttpHeaderUtil.isContentLengthSet(start);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long contentLength(HttpMessage start) throws Exception {
|
||||
return HttpHeaders.getContentLength(start);
|
||||
return HttpHeaderUtil.getContentLength(start);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object newContinueResponse(HttpMessage start) throws Exception {
|
||||
if (HttpHeaders.is100ContinueExpected(start)) {
|
||||
if (HttpHeaderUtil.is100ContinueExpected(start)) {
|
||||
return CONTINUE;
|
||||
} else {
|
||||
return null;
|
||||
@ -116,7 +116,7 @@ public class HttpObjectAggregator
|
||||
protected FullHttpMessage beginAggregation(HttpMessage start, ByteBuf content) throws Exception {
|
||||
assert !(start instanceof FullHttpMessage);
|
||||
|
||||
HttpHeaders.removeTransferEncodingChunked(start);
|
||||
HttpHeaderUtil.setTransferEncodingChunked(start, false);
|
||||
|
||||
FullHttpMessage ret;
|
||||
if (start instanceof HttpRequest) {
|
||||
@ -169,7 +169,7 @@ public class HttpObjectAggregator
|
||||
// If 'Expect: 100-continue' is missing, close becuase it's impossible to recover.
|
||||
// If keep-alive is off, no need to leave the connection open.
|
||||
if (oversized instanceof FullHttpMessage ||
|
||||
!HttpHeaders.is100ContinueExpected(oversized) || !HttpHeaders.isKeepAlive(oversized)) {
|
||||
!HttpHeaderUtil.is100ContinueExpected(oversized) || !HttpHeaderUtil.isKeepAlive(oversized)) {
|
||||
future.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import io.netty.buffer.ByteBufProcessor;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.DecoderResult;
|
||||
import io.netty.handler.codec.ReplayingDecoder;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
@ -520,9 +521,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
||||
State nextState;
|
||||
|
||||
if (isContentAlwaysEmpty(message)) {
|
||||
HttpHeaders.removeTransferEncodingChunked(message);
|
||||
HttpHeaderUtil.setTransferEncodingChunked(message, false);
|
||||
nextState = State.SKIP_CONTROL_CHARS;
|
||||
} else if (HttpHeaders.isTransferEncodingChunked(message)) {
|
||||
} else if (HttpHeaderUtil.isTransferEncodingChunked(message)) {
|
||||
nextState = State.READ_CHUNK_SIZE;
|
||||
} else if (contentLength() >= 0) {
|
||||
nextState = State.READ_FIXED_LENGTH_CONTENT;
|
||||
@ -534,7 +535,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
||||
|
||||
private long contentLength() {
|
||||
if (contentLength == Long.MIN_VALUE) {
|
||||
contentLength = HttpHeaders.getContentLength(message, -1);
|
||||
contentLength = HttpHeaderUtil.getContentLength(message, -1);
|
||||
}
|
||||
return contentLength;
|
||||
}
|
||||
@ -559,9 +560,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
||||
} else {
|
||||
String[] header = splitHeader(line);
|
||||
String name = header[0];
|
||||
if (!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.CONTENT_LENGTH) &&
|
||||
!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.TRANSFER_ENCODING) &&
|
||||
!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.TRAILER)) {
|
||||
if (!AsciiString.equalsIgnoreCase(name, HttpHeaders.Names.CONTENT_LENGTH) &&
|
||||
!AsciiString.equalsIgnoreCase(name, HttpHeaders.Names.TRANSFER_ENCODING) &&
|
||||
!AsciiString.equalsIgnoreCase(name, HttpHeaders.Names.TRAILER)) {
|
||||
trailer.trailingHeaders().add(name, header[1]);
|
||||
}
|
||||
lastHeader = name;
|
||||
|
@ -69,9 +69,9 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
|
||||
buf = ctx.alloc().buffer();
|
||||
// Encode the message.
|
||||
encodeInitialLine(buf, m);
|
||||
HttpHeaders.encode(m.headers(), buf);
|
||||
m.headers().forEachEntry(new HttpHeadersEncoder(buf));
|
||||
buf.writeBytes(CRLF);
|
||||
state = HttpHeaders.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
|
||||
state = HttpHeaderUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
|
||||
}
|
||||
if (msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion) {
|
||||
if (state == ST_INIT) {
|
||||
@ -137,7 +137,7 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
|
||||
} else {
|
||||
ByteBuf buf = ctx.alloc().buffer();
|
||||
buf.writeBytes(ZERO_CRLF);
|
||||
HttpHeaders.encode(headers, buf);
|
||||
headers.forEachEntry(new HttpHeadersEncoder(buf));
|
||||
buf.writeBytes(CRLF);
|
||||
out.add(buf);
|
||||
}
|
||||
|
@ -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;
|
||||
@ -548,9 +578,9 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
|
||||
|
||||
void encode(ByteBuf buf) {
|
||||
if (bytes == null) {
|
||||
HttpHeaders.encodeAscii0(String.valueOf(code()), buf);
|
||||
HttpHeaderUtil.encodeAscii0(String.valueOf(code()), buf);
|
||||
buf.writeByte(SP);
|
||||
HttpHeaders.encodeAscii0(String.valueOf(reasonPhrase()), buf);
|
||||
HttpHeaderUtil.encodeAscii0(String.valueOf(reasonPhrase()), buf);
|
||||
} else {
|
||||
buf.writeBytes(bytes);
|
||||
}
|
||||
|
@ -264,7 +264,7 @@ public class HttpVersion implements Comparable<HttpVersion> {
|
||||
|
||||
void encode(ByteBuf buf) {
|
||||
if (bytes == null) {
|
||||
HttpHeaders.encodeAscii0(text, buf);
|
||||
HttpHeaderUtil.encodeAscii0(text, buf);
|
||||
} else {
|
||||
buf.writeBytes(bytes);
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ public interface LastHttpContent extends HttpContent {
|
||||
|
||||
@Override
|
||||
public HttpHeaders trailingHeaders() {
|
||||
return HttpHeaders.EMPTY_HEADERS;
|
||||
return EmptyHttpHeaders.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -16,6 +16,7 @@
|
||||
package io.netty.handler.codec.http.cors;
|
||||
|
||||
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
||||
import io.netty.handler.codec.http.EmptyHttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
@ -202,7 +203,7 @@ public final class CorsConfig {
|
||||
*/
|
||||
public HttpHeaders preflightResponseHeaders() {
|
||||
if (preflightHeaders.isEmpty()) {
|
||||
return HttpHeaders.EMPTY_HEADERS;
|
||||
return EmptyHttpHeaders.INSTANCE;
|
||||
}
|
||||
final HttpHeaders preflightHeaders = new DefaultHttpHeaders();
|
||||
for (Entry<CharSequence, Callable<?>> entry : this.preflightHeaders.entrySet()) {
|
||||
|
@ -17,12 +17,15 @@ package io.netty.handler.codec.http.multipart;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.DecoderResult;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.DefaultHttpContent;
|
||||
import io.netty.handler.codec.http.EmptyHttpHeaders;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpConstants;
|
||||
import io.netty.handler.codec.http.HttpContent;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
@ -741,14 +744,14 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
|
||||
if (transferEncoding != null) {
|
||||
headers.remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
for (String v : transferEncoding) {
|
||||
if (HttpHeaders.equalsIgnoreCase(v, HttpHeaders.Values.CHUNKED)) {
|
||||
if (AsciiString.equalsIgnoreCase(v, HttpHeaders.Values.CHUNKED)) {
|
||||
// ignore
|
||||
} else {
|
||||
headers.add(HttpHeaders.Names.TRANSFER_ENCODING, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
HttpHeaders.setTransferEncodingChunked(request);
|
||||
HttpHeaderUtil.setTransferEncodingChunked(request, true);
|
||||
|
||||
// wrap to hide the possible content
|
||||
return new WrappedHttpRequest(request);
|
||||
@ -1237,7 +1240,7 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
|
||||
if (content instanceof LastHttpContent) {
|
||||
return ((LastHttpContent) content).trailingHeaders();
|
||||
} else {
|
||||
return HttpHeaders.EMPTY_HEADERS;
|
||||
return EmptyHttpHeaders.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
@ -198,13 +199,13 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
||||
HttpHeaders headers = response.headers();
|
||||
|
||||
String upgrade = headers.get(Names.UPGRADE);
|
||||
if (!HttpHeaders.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
|
||||
if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
|
||||
throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
|
||||
+ upgrade);
|
||||
}
|
||||
|
||||
String connection = headers.get(Names.CONNECTION);
|
||||
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, connection)) {
|
||||
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) {
|
||||
throw new WebSocketHandshakeException("Invalid handshake response connection: "
|
||||
+ connection);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
@ -40,7 +41,7 @@ import java.net.URI;
|
||||
public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker07.class);
|
||||
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
|
||||
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
|
||||
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
private String expectedChallengeResponseString;
|
||||
@ -173,12 +174,12 @@ public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker {
|
||||
}
|
||||
|
||||
String upgrade = headers.get(Names.UPGRADE);
|
||||
if (!HttpHeaders.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
|
||||
if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
|
||||
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade);
|
||||
}
|
||||
|
||||
String connection = headers.get(Names.CONNECTION);
|
||||
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, connection)) {
|
||||
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) {
|
||||
throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection);
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
@ -40,7 +41,7 @@ import java.net.URI;
|
||||
public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker08.class);
|
||||
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
|
||||
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
|
||||
|
||||
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
@ -174,12 +175,12 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
|
||||
}
|
||||
|
||||
String upgrade = headers.get(Names.UPGRADE);
|
||||
if (!HttpHeaders.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
|
||||
if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
|
||||
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade);
|
||||
}
|
||||
|
||||
String connection = headers.get(Names.CONNECTION);
|
||||
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, connection)) {
|
||||
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) {
|
||||
throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection);
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
@ -40,7 +41,7 @@ import java.net.URI;
|
||||
public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.class);
|
||||
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
|
||||
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
|
||||
|
||||
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
@ -184,12 +185,12 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
|
||||
}
|
||||
|
||||
String upgrade = headers.get(Names.UPGRADE);
|
||||
if (!HttpHeaders.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
|
||||
if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
|
||||
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade);
|
||||
}
|
||||
|
||||
String connection = headers.get(Names.CONNECTION);
|
||||
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, connection)) {
|
||||
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) {
|
||||
throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection);
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
@ -109,8 +110,8 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
protected FullHttpResponse newHandshakeResponse(FullHttpRequest req, HttpHeaders headers) {
|
||||
|
||||
// Serve the WebSocket handshake request.
|
||||
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, req.headers().get(CONNECTION))
|
||||
|| !HttpHeaders.equalsIgnoreCase(WEBSOCKET, req.headers().get(Names.UPGRADE))) {
|
||||
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, req.headers().get(CONNECTION))
|
||||
|| !AsciiString.equalsIgnoreCase(WEBSOCKET, req.headers().get(Names.UPGRADE))) {
|
||||
throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
@ -35,7 +36,7 @@ import static io.netty.handler.codec.http.HttpVersion.*;
|
||||
*/
|
||||
public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
|
||||
|
||||
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
|
||||
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
|
||||
|
||||
public static final String WEBSOCKET_07_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
@ -35,7 +36,7 @@ import static io.netty.handler.codec.http.HttpVersion.*;
|
||||
*/
|
||||
public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
|
||||
|
||||
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
|
||||
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
|
||||
|
||||
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
@ -34,7 +35,7 @@ import static io.netty.handler.codec.http.HttpVersion.*;
|
||||
*/
|
||||
public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
|
||||
|
||||
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
|
||||
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
|
||||
|
||||
public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
|
@ -22,12 +22,12 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaders.*;
|
||||
import static io.netty.handler.codec.http.HttpMethod.*;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||
@ -47,7 +47,7 @@ class WebSocketServerProtocolHandshakeHandler extends ChannelHandlerAdapter {
|
||||
this.websocketPath = websocketPath;
|
||||
this.subprotocols = subprotocols;
|
||||
this.allowExtensions = allowExtensions;
|
||||
this.maxFramePayloadSize = maxFrameSize;
|
||||
maxFramePayloadSize = maxFrameSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -89,7 +89,7 @@ class WebSocketServerProtocolHandshakeHandler extends ChannelHandlerAdapter {
|
||||
|
||||
private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
|
||||
ChannelFuture f = ctx.channel().writeAndFlush(res);
|
||||
if (!isKeepAlive(req) || res.getStatus().code() != 200) {
|
||||
if (!HttpHeaderUtil.isKeepAlive(req) || res.getStatus().code() != 200) {
|
||||
f.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.netty.handler.codec.rtsp;
|
||||
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
|
||||
|
||||
@ -42,7 +43,7 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "Allow"}
|
||||
*/
|
||||
public static final CharSequence ALLOW = HttpHeaders.newEntity("Allow");
|
||||
public static final CharSequence ALLOW = new AsciiString("Allow");
|
||||
/**
|
||||
* {@code "Authorization"}
|
||||
*/
|
||||
@ -50,11 +51,11 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "Bandwidth"}
|
||||
*/
|
||||
public static final CharSequence BANDWIDTH = HttpHeaders.newEntity("Bandwidth");
|
||||
public static final CharSequence BANDWIDTH = new AsciiString("Bandwidth");
|
||||
/**
|
||||
* {@code "Blocksize"}
|
||||
*/
|
||||
public static final CharSequence BLOCKSIZE = HttpHeaders.newEntity("Blocksize");
|
||||
public static final CharSequence BLOCKSIZE = new AsciiString("Blocksize");
|
||||
/**
|
||||
* {@code "Cache-Control"}
|
||||
*/
|
||||
@ -62,7 +63,7 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "Conference"}
|
||||
*/
|
||||
public static final CharSequence CONFERENCE = HttpHeaders.newEntity("Conference");
|
||||
public static final CharSequence CONFERENCE = new AsciiString("Conference");
|
||||
/**
|
||||
* {@code "Connection"}
|
||||
*/
|
||||
@ -94,7 +95,7 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "CSeq"}
|
||||
*/
|
||||
public static final CharSequence CSEQ = HttpHeaders.newEntity("CSeq");
|
||||
public static final CharSequence CSEQ = new AsciiString("CSeq");
|
||||
/**
|
||||
* {@code "Date"}
|
||||
*/
|
||||
@ -122,7 +123,7 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "KeyMgmt"}
|
||||
*/
|
||||
public static final CharSequence KEYMGMT = HttpHeaders.newEntity("KeyMgmt");
|
||||
public static final CharSequence KEYMGMT = new AsciiString("KeyMgmt");
|
||||
/**
|
||||
* {@code "Last-Modified"}
|
||||
*/
|
||||
@ -134,11 +135,11 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "Proxy-Require"}
|
||||
*/
|
||||
public static final CharSequence PROXY_REQUIRE = HttpHeaders.newEntity("Proxy-Require");
|
||||
public static final CharSequence PROXY_REQUIRE = new AsciiString("Proxy-Require");
|
||||
/**
|
||||
* {@code "Public"}
|
||||
*/
|
||||
public static final CharSequence PUBLIC = HttpHeaders.newEntity("Public");
|
||||
public static final CharSequence PUBLIC = new AsciiString("Public");
|
||||
/**
|
||||
* {@code "Range"}
|
||||
*/
|
||||
@ -150,7 +151,7 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "Require"}
|
||||
*/
|
||||
public static final CharSequence REQUIRE = HttpHeaders.newEntity("Require");
|
||||
public static final CharSequence REQUIRE = new AsciiString("Require");
|
||||
/**
|
||||
* {@code "Retry-After"}
|
||||
*/
|
||||
@ -158,15 +159,15 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "RTP-Info"}
|
||||
*/
|
||||
public static final CharSequence RTP_INFO = HttpHeaders.newEntity("RTP-Info");
|
||||
public static final CharSequence RTP_INFO = new AsciiString("RTP-Info");
|
||||
/**
|
||||
* {@code "Scale"}
|
||||
*/
|
||||
public static final CharSequence SCALE = HttpHeaders.newEntity("Scale");
|
||||
public static final CharSequence SCALE = new AsciiString("Scale");
|
||||
/**
|
||||
* {@code "Session"}
|
||||
*/
|
||||
public static final CharSequence SESSION = HttpHeaders.newEntity("Session");
|
||||
public static final CharSequence SESSION = new AsciiString("Session");
|
||||
/**
|
||||
* {@code "Server"}
|
||||
*/
|
||||
@ -174,19 +175,19 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "Speed"}
|
||||
*/
|
||||
public static final CharSequence SPEED = HttpHeaders.newEntity("Speed");
|
||||
public static final CharSequence SPEED = new AsciiString("Speed");
|
||||
/**
|
||||
* {@code "Timestamp"}
|
||||
*/
|
||||
public static final CharSequence TIMESTAMP = HttpHeaders.newEntity("Timestamp");
|
||||
public static final CharSequence TIMESTAMP = new AsciiString("Timestamp");
|
||||
/**
|
||||
* {@code "Transport"}
|
||||
*/
|
||||
public static final CharSequence TRANSPORT = HttpHeaders.newEntity("Transport");
|
||||
public static final CharSequence TRANSPORT = new AsciiString("Transport");
|
||||
/**
|
||||
* {@code "Unsupported"}
|
||||
*/
|
||||
public static final CharSequence UNSUPPORTED = HttpHeaders.newEntity("Unsupported");
|
||||
public static final CharSequence UNSUPPORTED = new AsciiString("Unsupported");
|
||||
/**
|
||||
* {@code "User-Agent"}
|
||||
*/
|
||||
@ -215,11 +216,11 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "append"}
|
||||
*/
|
||||
public static final CharSequence APPEND = HttpHeaders.newEntity("append");
|
||||
public static final CharSequence APPEND = new AsciiString("append");
|
||||
/**
|
||||
* {@code "AVP"}
|
||||
*/
|
||||
public static final CharSequence AVP = HttpHeaders.newEntity("AVP");
|
||||
public static final CharSequence AVP = new AsciiString("AVP");
|
||||
/**
|
||||
* {@code "bytes"}
|
||||
*/
|
||||
@ -231,11 +232,11 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "client_port"}
|
||||
*/
|
||||
public static final CharSequence CLIENT_PORT = HttpHeaders.newEntity("client_port");
|
||||
public static final CharSequence CLIENT_PORT = new AsciiString("client_port");
|
||||
/**
|
||||
* {@code "clock"}
|
||||
*/
|
||||
public static final CharSequence CLOCK = HttpHeaders.newEntity("clock");
|
||||
public static final CharSequence CLOCK = new AsciiString("clock");
|
||||
/**
|
||||
* {@code "close"}
|
||||
*/
|
||||
@ -255,7 +256,7 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "destination"}
|
||||
*/
|
||||
public static final CharSequence DESTINATION = HttpHeaders.newEntity("destination");
|
||||
public static final CharSequence DESTINATION = new AsciiString("destination");
|
||||
/**
|
||||
* {@code "gzip"}
|
||||
*/
|
||||
@ -267,7 +268,7 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "interleaved"}
|
||||
*/
|
||||
public static final CharSequence INTERLEAVED = HttpHeaders.newEntity("interleaved");
|
||||
public static final CharSequence INTERLEAVED = new AsciiString("interleaved");
|
||||
/**
|
||||
* {@code "keep-alive"}
|
||||
*/
|
||||
@ -275,7 +276,7 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "layers"}
|
||||
*/
|
||||
public static final CharSequence LAYERS = HttpHeaders.newEntity("layers");
|
||||
public static final CharSequence LAYERS = new AsciiString("layers");
|
||||
/**
|
||||
* {@code "max-age"}
|
||||
*/
|
||||
@ -291,11 +292,11 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "mode"}
|
||||
*/
|
||||
public static final CharSequence MODE = HttpHeaders.newEntity("mode");
|
||||
public static final CharSequence MODE = new AsciiString("mode");
|
||||
/**
|
||||
* {@code "multicast"}
|
||||
*/
|
||||
public static final CharSequence MULTICAST = HttpHeaders.newEntity("multicast");
|
||||
public static final CharSequence MULTICAST = new AsciiString("multicast");
|
||||
/**
|
||||
* {@code "must-revalidate"}
|
||||
*/
|
||||
@ -319,7 +320,7 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "port"}
|
||||
*/
|
||||
public static final CharSequence PORT = HttpHeaders.newEntity("port");
|
||||
public static final CharSequence PORT = new AsciiString("port");
|
||||
/**
|
||||
* {@code "private"}
|
||||
*/
|
||||
@ -335,51 +336,51 @@ public final class RtspHeaders {
|
||||
/**
|
||||
* {@code "RTP"}
|
||||
*/
|
||||
public static final CharSequence RTP = HttpHeaders.newEntity("RTP");
|
||||
public static final CharSequence RTP = new AsciiString("RTP");
|
||||
/**
|
||||
* {@code "rtptime"}
|
||||
*/
|
||||
public static final CharSequence RTPTIME = HttpHeaders.newEntity("rtptime");
|
||||
public static final CharSequence RTPTIME = new AsciiString("rtptime");
|
||||
/**
|
||||
* {@code "seq"}
|
||||
*/
|
||||
public static final CharSequence SEQ = HttpHeaders.newEntity("seq");
|
||||
public static final CharSequence SEQ = new AsciiString("seq");
|
||||
/**
|
||||
* {@code "server_port"}
|
||||
*/
|
||||
public static final CharSequence SERVER_PORT = HttpHeaders.newEntity("server_port");
|
||||
public static final CharSequence SERVER_PORT = new AsciiString("server_port");
|
||||
/**
|
||||
* {@code "ssrc"}
|
||||
*/
|
||||
public static final CharSequence SSRC = HttpHeaders.newEntity("ssrc");
|
||||
public static final CharSequence SSRC = new AsciiString("ssrc");
|
||||
/**
|
||||
* {@code "TCP"}
|
||||
*/
|
||||
public static final CharSequence TCP = HttpHeaders.newEntity("TCP");
|
||||
public static final CharSequence TCP = new AsciiString("TCP");
|
||||
/**
|
||||
* {@code "time"}
|
||||
*/
|
||||
public static final CharSequence TIME = HttpHeaders.newEntity("time");
|
||||
public static final CharSequence TIME = new AsciiString("time");
|
||||
/**
|
||||
* {@code "timeout"}
|
||||
*/
|
||||
public static final CharSequence TIMEOUT = HttpHeaders.newEntity("timeout");
|
||||
public static final CharSequence TIMEOUT = new AsciiString("timeout");
|
||||
/**
|
||||
* {@code "ttl"}
|
||||
*/
|
||||
public static final CharSequence TTL = HttpHeaders.newEntity("ttl");
|
||||
public static final CharSequence TTL = new AsciiString("ttl");
|
||||
/**
|
||||
* {@code "UDP"}
|
||||
*/
|
||||
public static final CharSequence UDP = HttpHeaders.newEntity("UDP");
|
||||
public static final CharSequence UDP = new AsciiString("UDP");
|
||||
/**
|
||||
* {@code "unicast"}
|
||||
*/
|
||||
public static final CharSequence UNICAST = HttpHeaders.newEntity("unicast");
|
||||
public static final CharSequence UNICAST = new AsciiString("unicast");
|
||||
/**
|
||||
* {@code "url"}
|
||||
*/
|
||||
public static final CharSequence URL = HttpHeaders.newEntity("url");
|
||||
public static final CharSequence URL = new AsciiString("url");
|
||||
|
||||
private Values() { }
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ package io.netty.handler.codec.rtsp;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
@ -37,13 +36,12 @@ public class RtspRequestEncoder extends RtspObjectEncoder<HttpRequest> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encodeInitialLine(ByteBuf buf, HttpRequest request)
|
||||
throws Exception {
|
||||
HttpHeaders.encodeAscii(request.getMethod().toString(), buf);
|
||||
protected void encodeInitialLine(ByteBuf buf, HttpRequest request) throws Exception {
|
||||
buf.writeBytes(request.getMethod().toString().getBytes(CharsetUtil.US_ASCII));
|
||||
buf.writeByte(SP);
|
||||
buf.writeBytes(request.getUri().getBytes(CharsetUtil.UTF_8));
|
||||
buf.writeByte(SP);
|
||||
HttpHeaders.encodeAscii(request.getProtocolVersion().toString(), buf);
|
||||
buf.writeBytes(request.getProtocolVersion().toString().getBytes(CharsetUtil.US_ASCII));
|
||||
buf.writeBytes(CRLF);
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ package io.netty.handler.codec.rtsp;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
@ -37,13 +36,12 @@ public class RtspResponseEncoder extends RtspObjectEncoder<HttpResponse> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encodeInitialLine(ByteBuf buf, HttpResponse response)
|
||||
throws Exception {
|
||||
HttpHeaders.encodeAscii(response.getProtocolVersion().toString(), buf);
|
||||
protected void encodeInitialLine(ByteBuf buf, HttpResponse response) throws Exception {
|
||||
buf.writeBytes(response.getProtocolVersion().toString().getBytes(CharsetUtil.US_ASCII));
|
||||
buf.writeByte(SP);
|
||||
buf.writeBytes(String.valueOf(response.getStatus().code()).getBytes(CharsetUtil.US_ASCII));
|
||||
buf.writeByte(SP);
|
||||
HttpHeaders.encodeAscii(String.valueOf(response.getStatus().reasonPhrase()), buf);
|
||||
buf.writeBytes(response.getStatus().reasonPhrase().getBytes(CharsetUtil.US_ASCII));
|
||||
buf.writeBytes(CRLF);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
int h = hash(lowerCaseName);
|
||||
int i = index(h);
|
||||
|
||||
remove0(h, i, lowerCaseName);
|
||||
for (Object v: values) {
|
||||
if (v == null) {
|
||||
break;
|
||||
@Override
|
||||
public SpdyHeaders add(CharSequence name, Object... values) {
|
||||
super.add(name, values);
|
||||
return this;
|
||||
}
|
||||
String strVal = toString(v);
|
||||
SpdyCodecUtil.validateHeaderValue(strVal);
|
||||
add0(h, i, lowerCaseName, strVal);
|
||||
|
||||
@Override
|
||||
public SpdyHeaders add(TextHeaders headers) {
|
||||
super.add(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if there is a header with the specified
|
||||
* header name.
|
||||
*/
|
||||
public abstract boolean contains(String name);
|
||||
|
||||
/**
|
||||
* Returns the {@link Set} of all header names that this frame contains.
|
||||
*/
|
||||
public abstract Set<String> names();
|
||||
|
||||
/**
|
||||
* Adds a new header with the specified name and value.
|
||||
*/
|
||||
public abstract SpdyHeaders add(String name, Object value);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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();
|
||||
SpdyHeaders add(CharSequence name, Iterable<?> values);
|
||||
|
||||
@Override
|
||||
SpdyHeaders add(CharSequence name, Object... values);
|
||||
|
||||
@Override
|
||||
SpdyHeaders add(TextHeaders headers);
|
||||
|
||||
@Override
|
||||
SpdyHeaders set(CharSequence name, Object value);
|
||||
|
||||
@Override
|
||||
SpdyHeaders set(CharSequence name, Iterable<?> values);
|
||||
|
||||
@Override
|
||||
SpdyHeaders set(CharSequence name, Object... values);
|
||||
|
||||
@Override
|
||||
SpdyHeaders set(TextHeaders headers);
|
||||
|
||||
@Override
|
||||
SpdyHeaders clear();
|
||||
|
||||
@Override
|
||||
SpdyHeaders forEachEntry(TextHeaderProcessor processor);
|
||||
}
|
||||
|
@ -24,15 +24,19 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpMessage;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
|
||||
|
||||
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 +115,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
|
||||
@ -136,19 +140,19 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
||||
createHttpResponse(spdyVersion, spdySynStreamFrame);
|
||||
|
||||
// Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers
|
||||
SpdyHttpHeaders.setStreamId(httpResponseWithEntity, streamId);
|
||||
SpdyHttpHeaders.setAssociatedToStreamId(httpResponseWithEntity, associatedToStreamId);
|
||||
SpdyHttpHeaders.setPriority(httpResponseWithEntity, spdySynStreamFrame.getPriority());
|
||||
SpdyHttpHeaders.setUrl(httpResponseWithEntity, URL);
|
||||
httpResponseWithEntity.headers().set(Names.STREAM_ID, streamId);
|
||||
httpResponseWithEntity.headers().set(Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId);
|
||||
httpResponseWithEntity.headers().set(Names.PRIORITY, spdySynStreamFrame.getPriority());
|
||||
httpResponseWithEntity.headers().set(Names.URL, URL);
|
||||
|
||||
if (spdySynStreamFrame.isLast()) {
|
||||
HttpHeaders.setContentLength(httpResponseWithEntity, 0);
|
||||
HttpHeaderUtil.setContentLength(httpResponseWithEntity, 0);
|
||||
out.add(httpResponseWithEntity);
|
||||
} else {
|
||||
// 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 +165,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;
|
||||
}
|
||||
@ -173,7 +176,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
||||
FullHttpRequest httpRequestWithEntity = createHttpRequest(spdyVersion, spdySynStreamFrame);
|
||||
|
||||
// Set the Stream-ID as a header
|
||||
SpdyHttpHeaders.setStreamId(httpRequestWithEntity, streamId);
|
||||
httpRequestWithEntity.headers().set(Names.STREAM_ID, streamId);
|
||||
|
||||
if (spdySynStreamFrame.isLast()) {
|
||||
out.add(httpRequestWithEntity);
|
||||
@ -187,8 +190,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);
|
||||
}
|
||||
}
|
||||
@ -211,10 +215,10 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
||||
FullHttpResponse httpResponseWithEntity = createHttpResponse(spdyVersion, spdySynReplyFrame);
|
||||
|
||||
// Set the Stream-ID as a header
|
||||
SpdyHttpHeaders.setStreamId(httpResponseWithEntity, streamId);
|
||||
httpResponseWithEntity.headers().set(Names.STREAM_ID, streamId);
|
||||
|
||||
if (spdySynReplyFrame.isLast()) {
|
||||
HttpHeaders.setContentLength(httpResponseWithEntity, 0);
|
||||
HttpHeaderUtil.setContentLength(httpResponseWithEntity, 0);
|
||||
out.add(httpResponseWithEntity);
|
||||
} else {
|
||||
// Response body will follow in a series of Data Frames
|
||||
@ -247,7 +251,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
||||
}
|
||||
|
||||
if (spdyHeadersFrame.isLast()) {
|
||||
HttpHeaders.setContentLength(fullHttpMessage, fullHttpMessage.content().readableBytes());
|
||||
HttpHeaderUtil.setContentLength(fullHttpMessage, fullHttpMessage.content().readableBytes());
|
||||
removeMessage(streamId);
|
||||
out.add(fullHttpMessage);
|
||||
}
|
||||
@ -275,7 +279,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
||||
content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen);
|
||||
|
||||
if (spdyDataFrame.isLast()) {
|
||||
HttpHeaders.setContentLength(fullHttpMessage, content.readableBytes());
|
||||
HttpHeaderUtil.setContentLength(fullHttpMessage, content.readableBytes());
|
||||
removeMessage(streamId);
|
||||
out.add(fullHttpMessage);
|
||||
}
|
||||
@ -291,23 +295,24 @@ 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);
|
||||
HttpHeaders.setHost(req, host);
|
||||
String host = headers.get(HOST);
|
||||
headers.remove(HOST);
|
||||
req.headers().set(HttpHeaders.Names.HOST, host);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> e: requestFrame.headers()) {
|
||||
@ -315,7 +320,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
||||
}
|
||||
|
||||
// The Connection and Keep-Alive headers are no longer valid
|
||||
HttpHeaders.setKeepAlive(req, true);
|
||||
HttpHeaderUtil.setKeepAlive(req, true);
|
||||
|
||||
// Transfer-Encoding header is not valid
|
||||
req.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
@ -323,13 +328,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()) {
|
||||
@ -337,7 +344,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
||||
}
|
||||
|
||||
// The Connection and Keep-Alive headers are no longer valid
|
||||
HttpHeaders.setKeepAlive(res, true);
|
||||
HttpHeaderUtil.setKeepAlive(res, true);
|
||||
|
||||
// Transfer-Encoding header is not valid
|
||||
res.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
|
@ -27,10 +27,13 @@ import io.netty.handler.codec.http.HttpObject;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.LastHttpContent;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
|
||||
|
||||
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.
|
||||
@ -202,61 +205,62 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
||||
}
|
||||
}
|
||||
|
||||
private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage)
|
||||
throws Exception {
|
||||
private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage) throws Exception {
|
||||
// Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers
|
||||
int streamID = SpdyHttpHeaders.getStreamId(httpMessage);
|
||||
int associatedToStreamId = SpdyHttpHeaders.getAssociatedToStreamId(httpMessage);
|
||||
byte priority = SpdyHttpHeaders.getPriority(httpMessage);
|
||||
String URL = SpdyHttpHeaders.getUrl(httpMessage);
|
||||
String scheme = SpdyHttpHeaders.getScheme(httpMessage);
|
||||
SpdyHttpHeaders.removeStreamId(httpMessage);
|
||||
SpdyHttpHeaders.removeAssociatedToStreamId(httpMessage);
|
||||
SpdyHttpHeaders.removePriority(httpMessage);
|
||||
SpdyHttpHeaders.removeUrl(httpMessage);
|
||||
SpdyHttpHeaders.removeScheme(httpMessage);
|
||||
final HttpHeaders httpHeaders = httpMessage.headers();
|
||||
int streamID = httpHeaders.getInt(Names.STREAM_ID);
|
||||
int associatedToStreamId = httpHeaders.getInt(Names.ASSOCIATED_TO_STREAM_ID);
|
||||
byte priority = (byte) httpHeaders.getInt(Names.PRIORITY, 0);
|
||||
String URL = httpHeaders.get(Names.URL);
|
||||
String scheme = httpHeaders.get(Names.SCHEME);
|
||||
httpHeaders.remove(Names.STREAM_ID);
|
||||
httpHeaders.remove(Names.ASSOCIATED_TO_STREAM_ID);
|
||||
httpHeaders.remove(Names.PRIORITY);
|
||||
httpHeaders.remove(Names.URL);
|
||||
httpHeaders.remove(Names.SCHEME);
|
||||
|
||||
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
|
||||
// headers are not valid and MUST not be sent.
|
||||
httpMessage.headers().remove(HttpHeaders.Names.CONNECTION);
|
||||
httpMessage.headers().remove("Keep-Alive");
|
||||
httpMessage.headers().remove("Proxy-Connection");
|
||||
httpMessage.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
httpHeaders.remove(HttpHeaders.Names.CONNECTION);
|
||||
httpHeaders.remove("Keep-Alive");
|
||||
httpHeaders.remove("Proxy-Connection");
|
||||
httpHeaders.remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
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);
|
||||
}
|
||||
|
||||
// Replace the HTTP host header with the SPDY host header
|
||||
if (spdyVersion >= 3) {
|
||||
String host = HttpHeaders.getHost(httpMessage);
|
||||
httpMessage.headers().remove(HttpHeaders.Names.HOST);
|
||||
SpdyHeaders.setHost(spdySynStreamFrame, host);
|
||||
CharSequence host = httpHeaders.getUnconverted(HttpHeaders.Names.HOST);
|
||||
httpHeaders.remove(HttpHeaders.Names.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());
|
||||
for (Map.Entry<String, String> entry: httpHeaders) {
|
||||
frameHeaders.add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
currentStreamId = spdySynStreamFrame.getStreamId();
|
||||
spdySynStreamFrame.setLast(isLast(httpMessage));
|
||||
@ -264,27 +268,27 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
||||
return spdySynStreamFrame;
|
||||
}
|
||||
|
||||
private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse)
|
||||
throws Exception {
|
||||
private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse) throws Exception {
|
||||
// Get the Stream-ID from the headers
|
||||
int streamID = SpdyHttpHeaders.getStreamId(httpResponse);
|
||||
SpdyHttpHeaders.removeStreamId(httpResponse);
|
||||
final HttpHeaders httpHeaders = httpResponse.headers();
|
||||
int streamID = httpHeaders.getInt(Names.STREAM_ID);
|
||||
httpHeaders.remove(Names.STREAM_ID);
|
||||
|
||||
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
|
||||
// headers are not valid and MUST not be sent.
|
||||
httpResponse.headers().remove(HttpHeaders.Names.CONNECTION);
|
||||
httpResponse.headers().remove("Keep-Alive");
|
||||
httpResponse.headers().remove("Proxy-Connection");
|
||||
httpResponse.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||
httpHeaders.remove(HttpHeaders.Names.CONNECTION);
|
||||
httpHeaders.remove("Keep-Alive");
|
||||
httpHeaders.remove("Proxy-Connection");
|
||||
httpHeaders.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()) {
|
||||
for (Map.Entry<String, String> entry: httpHeaders) {
|
||||
spdySynReplyFrame.headers().add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,10 @@
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
|
||||
/**
|
||||
* Provides the constants for the header names and the utility methods
|
||||
* used by the {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}.
|
||||
* Provides the constants for the header names used by the {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}.
|
||||
*/
|
||||
public final class SpdyHttpHeaders {
|
||||
|
||||
@ -31,138 +29,26 @@ public final class SpdyHttpHeaders {
|
||||
/**
|
||||
* {@code "X-SPDY-Stream-ID"}
|
||||
*/
|
||||
public static final String STREAM_ID = "X-SPDY-Stream-ID";
|
||||
public static final AsciiString STREAM_ID = new AsciiString("X-SPDY-Stream-ID");
|
||||
/**
|
||||
* {@code "X-SPDY-Associated-To-Stream-ID"}
|
||||
*/
|
||||
public static final String ASSOCIATED_TO_STREAM_ID = "X-SPDY-Associated-To-Stream-ID";
|
||||
public static final AsciiString ASSOCIATED_TO_STREAM_ID = new AsciiString("X-SPDY-Associated-To-Stream-ID");
|
||||
/**
|
||||
* {@code "X-SPDY-Priority"}
|
||||
*/
|
||||
public static final String PRIORITY = "X-SPDY-Priority";
|
||||
public static final AsciiString PRIORITY = new AsciiString("X-SPDY-Priority");
|
||||
/**
|
||||
* {@code "X-SPDY-URL"}
|
||||
*/
|
||||
public static final String URL = "X-SPDY-URL";
|
||||
public static final AsciiString URL = new AsciiString("X-SPDY-URL");
|
||||
/**
|
||||
* {@code "X-SPDY-Scheme"}
|
||||
*/
|
||||
public static final String SCHEME = "X-SPDY-Scheme";
|
||||
public static final AsciiString SCHEME = new AsciiString("X-SPDY-Scheme");
|
||||
|
||||
private Names() { }
|
||||
}
|
||||
|
||||
private SpdyHttpHeaders() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-Stream-ID"} header.
|
||||
*/
|
||||
public static void removeStreamId(HttpMessage message) {
|
||||
message.headers().remove(Names.STREAM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-Stream-ID"} header.
|
||||
*/
|
||||
public static int getStreamId(HttpMessage message) {
|
||||
return HttpHeaders.getIntHeader(message, Names.STREAM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-Stream-ID"} header.
|
||||
*/
|
||||
public static void setStreamId(HttpMessage message, int streamId) {
|
||||
HttpHeaders.setIntHeader(message, Names.STREAM_ID, streamId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-Associated-To-Stream-ID"} header.
|
||||
*/
|
||||
public static void removeAssociatedToStreamId(HttpMessage message) {
|
||||
message.headers().remove(Names.ASSOCIATED_TO_STREAM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-Associated-To-Stream-ID"} header.
|
||||
*
|
||||
* @return the header value or {@code 0} if there is no such header or
|
||||
* if the header value is not a number
|
||||
*/
|
||||
public static int getAssociatedToStreamId(HttpMessage message) {
|
||||
return HttpHeaders.getIntHeader(message, Names.ASSOCIATED_TO_STREAM_ID, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-Associated-To-Stream-ID"} header.
|
||||
*/
|
||||
public static void setAssociatedToStreamId(HttpMessage message, int associatedToStreamId) {
|
||||
HttpHeaders.setIntHeader(message, Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-Priority"} header.
|
||||
*/
|
||||
public static void removePriority(HttpMessage message) {
|
||||
message.headers().remove(Names.PRIORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-Priority"} header.
|
||||
*
|
||||
* @return the header value or {@code 0} if there is no such header or
|
||||
* if the header value is not a number
|
||||
*/
|
||||
public static byte getPriority(HttpMessage message) {
|
||||
return (byte) HttpHeaders.getIntHeader(message, Names.PRIORITY, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-Priority"} header.
|
||||
*/
|
||||
public static void setPriority(HttpMessage message, byte priority) {
|
||||
HttpHeaders.setIntHeader(message, Names.PRIORITY, priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-URL"} header.
|
||||
*/
|
||||
public static void removeUrl(HttpMessage message) {
|
||||
message.headers().remove(Names.URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-URL"} header.
|
||||
*/
|
||||
public static String getUrl(HttpMessage message) {
|
||||
return message.headers().get(Names.URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-URL"} header.
|
||||
*/
|
||||
public static void setUrl(HttpMessage message, String url) {
|
||||
message.headers().set(Names.URL, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@code "X-SPDY-Scheme"} header.
|
||||
*/
|
||||
public static void removeScheme(HttpMessage message) {
|
||||
message.headers().remove(Names.SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code "X-SPDY-Scheme"} header.
|
||||
*/
|
||||
public static String getScheme(HttpMessage message) {
|
||||
return message.headers().get(Names.SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code "X-SPDY-Scheme"} header.
|
||||
*/
|
||||
public static void setScheme(HttpMessage message, String scheme) {
|
||||
message.headers().set(Names.SCHEME, scheme);
|
||||
}
|
||||
private SpdyHttpHeaders() { }
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package io.netty.handler.codec.spdy;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageCodec;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import java.util.LinkedList;
|
||||
@ -43,7 +44,7 @@ public class SpdyHttpResponseStreamIdHandler extends
|
||||
protected void encode(ChannelHandlerContext ctx, HttpMessage msg, List<Object> out) throws Exception {
|
||||
Integer id = ids.poll();
|
||||
if (id != null && id.intValue() != NO_ID && !msg.headers().contains(SpdyHttpHeaders.Names.STREAM_ID)) {
|
||||
SpdyHttpHeaders.setStreamId(msg, id);
|
||||
msg.headers().set(Names.STREAM_ID, id);
|
||||
}
|
||||
|
||||
out.add(ReferenceCountUtil.retain(msg));
|
||||
@ -56,7 +57,7 @@ public class SpdyHttpResponseStreamIdHandler extends
|
||||
if (!contains) {
|
||||
ids.add(NO_ID);
|
||||
} else {
|
||||
ids.add(SpdyHttpHeaders.getStreamId((HttpMessage) msg));
|
||||
ids.add(((HttpMessage) msg).headers().getInt(Names.STREAM_ID));
|
||||
}
|
||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||
ids.remove(((SpdyRstStreamFrame) msg).getStreamId());
|
||||
|
@ -15,22 +15,23 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http;
|
||||
|
||||
import org.junit.Assert;
|
||||
import io.netty.handler.codec.AsciiString;
|
||||
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 {
|
||||
public class HttpHeaderUtilTest {
|
||||
|
||||
@Test
|
||||
public void testRemoveTransferEncodingIgnoreCase() {
|
||||
HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
|
||||
message.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, "Chunked");
|
||||
HttpHeaders.removeTransferEncodingChunked(message);
|
||||
Assert.assertTrue(message.headers().isEmpty());
|
||||
assertFalse(message.headers().isEmpty());
|
||||
HttpHeaderUtil.setTransferEncodingChunked(message, false);
|
||||
assertTrue(message.headers().isEmpty());
|
||||
}
|
||||
|
||||
// Test for https://github.com/netty/netty/issues/1690
|
||||
@ -40,19 +41,19 @@ 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
|
||||
public void testEquansIgnoreCase() {
|
||||
assertThat(HttpHeaders.equalsIgnoreCase(null, null), is(true));
|
||||
assertThat(HttpHeaders.equalsIgnoreCase(null, "foo"), is(false));
|
||||
assertThat(HttpHeaders.equalsIgnoreCase("bar", null), is(false));
|
||||
assertThat(HttpHeaders.equalsIgnoreCase("FoO", "fOo"), is(true));
|
||||
assertThat(AsciiString.equalsIgnoreCase(null, null), is(true));
|
||||
assertThat(AsciiString.equalsIgnoreCase(null, "foo"), is(false));
|
||||
assertThat(AsciiString.equalsIgnoreCase("bar", null), is(false));
|
||||
assertThat(AsciiString.equalsIgnoreCase("FoO", "fOo"), is(true));
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ public class HttpObjectAggregatorTest {
|
||||
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
|
||||
|
||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost");
|
||||
HttpHeaders.setHeader(message, "X-Test", true);
|
||||
message.headers().set("X-Test", true);
|
||||
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
||||
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
||||
HttpContent chunk3 = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
|
||||
@ -56,7 +56,7 @@ public class HttpObjectAggregatorTest {
|
||||
assertNotNull(aggratedMessage);
|
||||
|
||||
assertEquals(chunk1.content().readableBytes() + chunk2.content().readableBytes(),
|
||||
HttpHeaders.getContentLength(aggratedMessage));
|
||||
HttpHeaderUtil.getContentLength(aggratedMessage));
|
||||
assertEquals(aggratedMessage.headers().get("X-Test"), Boolean.TRUE.toString());
|
||||
checkContentBuffer(aggratedMessage);
|
||||
assertNull(embedder.readInbound());
|
||||
@ -79,8 +79,8 @@ public class HttpObjectAggregatorTest {
|
||||
HttpObjectAggregator aggr = new HttpObjectAggregator(1024 * 1024);
|
||||
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
|
||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost");
|
||||
HttpHeaders.setHeader(message, "X-Test", true);
|
||||
HttpHeaders.setTransferEncodingChunked(message);
|
||||
message.headers().set("X-Test", true);
|
||||
HttpHeaderUtil.setTransferEncodingChunked(message, true);
|
||||
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
||||
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
||||
LastHttpContent trailer = new DefaultLastHttpContent();
|
||||
@ -97,7 +97,7 @@ public class HttpObjectAggregatorTest {
|
||||
assertNotNull(aggratedMessage);
|
||||
|
||||
assertEquals(chunk1.content().readableBytes() + chunk2.content().readableBytes(),
|
||||
HttpHeaders.getContentLength(aggratedMessage));
|
||||
HttpHeaderUtil.getContentLength(aggratedMessage));
|
||||
assertEquals(aggratedMessage.headers().get("X-Test"), Boolean.TRUE.toString());
|
||||
assertEquals(aggratedMessage.headers().get("X-Trailer"), Boolean.TRUE.toString());
|
||||
checkContentBuffer(aggratedMessage);
|
||||
@ -135,14 +135,14 @@ public class HttpObjectAggregatorTest {
|
||||
public void testOversizedRequestWithoutKeepAlive() {
|
||||
// send a HTTP/1.0 request with no keep-alive header
|
||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.PUT, "http://localhost");
|
||||
HttpHeaders.setContentLength(message, 5);
|
||||
HttpHeaderUtil.setContentLength(message, 5);
|
||||
checkOversizedRequest(message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOversizedRequestWithContentLength() {
|
||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
|
||||
HttpHeaders.setContentLength(message, 5);
|
||||
HttpHeaderUtil.setContentLength(message, 5);
|
||||
checkOversizedRequest(message);
|
||||
}
|
||||
|
||||
@ -152,8 +152,8 @@ public class HttpObjectAggregatorTest {
|
||||
|
||||
// send an oversized request with 100 continue
|
||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
|
||||
HttpHeaders.set100ContinueExpected(message);
|
||||
HttpHeaders.setContentLength(message, 16);
|
||||
HttpHeaderUtil.set100ContinueExpected(message, true);
|
||||
HttpHeaderUtil.setContentLength(message, 16);
|
||||
|
||||
HttpContent chunk1 = releaseLater(new DefaultHttpContent(Unpooled.copiedBuffer("some", CharsetUtil.US_ASCII)));
|
||||
HttpContent chunk2 = releaseLater(new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII)));
|
||||
@ -185,9 +185,9 @@ public class HttpObjectAggregatorTest {
|
||||
|
||||
assertEquals(
|
||||
chunk2.content().readableBytes() + chunk3.content().readableBytes(),
|
||||
HttpHeaders.getContentLength(fullMsg));
|
||||
HttpHeaderUtil.getContentLength(fullMsg));
|
||||
|
||||
assertEquals(HttpHeaders.getContentLength(fullMsg), fullMsg.content().readableBytes());
|
||||
assertEquals(HttpHeaderUtil.getContentLength(fullMsg), fullMsg.content().readableBytes());
|
||||
|
||||
fullMsg.release();
|
||||
assertFalse(embedder.finish());
|
||||
@ -280,8 +280,8 @@ public class HttpObjectAggregatorTest {
|
||||
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
|
||||
|
||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
|
||||
HttpHeaders.setHeader(message, "X-Test", true);
|
||||
HttpHeaders.setHeader(message, "Transfer-Encoding", "Chunked");
|
||||
message.headers().set("X-Test", true);
|
||||
message.headers().set("Transfer-Encoding", "Chunked");
|
||||
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
||||
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
||||
HttpContent chunk3 = LastHttpContent.EMPTY_LAST_CONTENT;
|
||||
@ -296,7 +296,7 @@ public class HttpObjectAggregatorTest {
|
||||
assertNotNull(aggratedMessage);
|
||||
|
||||
assertEquals(chunk1.content().readableBytes() + chunk2.content().readableBytes(),
|
||||
HttpHeaders.getContentLength(aggratedMessage));
|
||||
HttpHeaderUtil.getContentLength(aggratedMessage));
|
||||
assertEquals(aggratedMessage.headers().get("X-Test"), Boolean.TRUE.toString());
|
||||
checkContentBuffer(aggratedMessage);
|
||||
assertNull(embedder.readInbound());
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http.cors;
|
||||
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import org.junit.Test;
|
||||
@ -115,7 +114,7 @@ public class CorsConfigTest {
|
||||
@Test
|
||||
public void emptyPreflightResponseHeaders() {
|
||||
final CorsConfig cors = withAnyOrigin().noPreflightResponseHeaders().build();
|
||||
assertThat(cors.preflightResponseHeaders(), equalTo(HttpHeaders.EMPTY_HEADERS));
|
||||
assertThat(cors.preflightResponseHeaders().size(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test (expected = IllegalArgumentException.class)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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 : '?';
|
||||
}
|
||||
}
|
1466
codec/src/main/java/io/netty/handler/codec/AsciiString.java
Normal file
1466
codec/src/main/java/io/netty/handler/codec/AsciiString.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,834 @@
|
||||
/*
|
||||
* 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.ParseException;
|
||||
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.caseInsensitiveHashCode(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 (ignoreCase) {
|
||||
return AsciiString.equalsIgnoreCase(a, b);
|
||||
} else {
|
||||
return AsciiString.equals(a, 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) {
|
||||
CharSequence v = getUnconverted(name);
|
||||
if (v == null) {
|
||||
throw new NoSuchElementException(String.valueOf(name));
|
||||
}
|
||||
|
||||
if (v instanceof AsciiString) {
|
||||
return ((AsciiString) v).parseInt();
|
||||
} else {
|
||||
return Integer.parseInt(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) {
|
||||
CharSequence v = getUnconverted(name);
|
||||
if (v == null) {
|
||||
throw new NoSuchElementException(String.valueOf(name));
|
||||
}
|
||||
|
||||
if (v instanceof AsciiString) {
|
||||
return ((AsciiString) v).parseLong();
|
||||
} else {
|
||||
return Long.parseLong(v.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
CharSequence v = getUnconverted(name);
|
||||
if (v == null) {
|
||||
throw new NoSuchElementException(String.valueOf(name));
|
||||
}
|
||||
|
||||
return HttpHeaderDateFormat.get().parse(v.toString());
|
||||
}
|
||||
|
||||
@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) {
|
||||
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) {
|
||||
PlatformDependent.throwException(new ParseException(text, 0));
|
||||
}
|
||||
return date.getTime();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
194
codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java
Normal file
194
codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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.NoSuchElementException;
|
||||
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) {
|
||||
throw new NoSuchElementException(String.valueOf(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(CharSequence name, int defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(CharSequence name) {
|
||||
throw new NoSuchElementException(String.valueOf(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(CharSequence name, long defaultValue) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeMillis(CharSequence name) {
|
||||
throw new NoSuchElementException(String.valueOf(name));
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
239
codec/src/main/java/io/netty/handler/codec/TextHeaders.java
Normal file
239
codec/src/main/java/io/netty/handler/codec/TextHeaders.java
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
* 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 getInt(CharSequence name, int defaultValue);
|
||||
long getLong(CharSequence name);
|
||||
long getLong(CharSequence name, long defaultValue);
|
||||
long getTimeMillis(CharSequence name);
|
||||
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);
|
||||
}
|
@ -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];
|
||||
|
@ -28,6 +28,7 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.DefaultHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
@ -52,7 +53,6 @@ import java.util.TimeZone;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.*;
|
||||
import static io.netty.handler.codec.http.HttpMethod.*;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||
@ -174,10 +174,10 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
|
||||
long fileLength = raf.length();
|
||||
|
||||
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
|
||||
setContentLength(response, fileLength);
|
||||
HttpHeaderUtil.setContentLength(response, fileLength);
|
||||
setContentTypeHeader(response, file);
|
||||
setDateAndCacheHeaders(response, file);
|
||||
if (isKeepAlive(request)) {
|
||||
if (HttpHeaderUtil.isKeepAlive(request)) {
|
||||
response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
|
||||
}
|
||||
|
||||
@ -214,7 +214,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
|
||||
ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
|
||||
|
||||
// Decide whether to close the connection or not.
|
||||
if (!isKeepAlive(request)) {
|
||||
if (!HttpHeaderUtil.isKeepAlive(request)) {
|
||||
// Close the connection when the whole content is written out.
|
||||
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
@ -21,13 +21,15 @@ import io.netty.channel.ChannelHandlerAdapter;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||
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.*;
|
||||
|
||||
|
||||
public class HttpHelloWorldServerHandler extends ChannelHandlerAdapter {
|
||||
private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' };
|
||||
|
||||
@ -41,10 +43,10 @@ public class HttpHelloWorldServerHandler extends ChannelHandlerAdapter {
|
||||
if (msg instanceof HttpRequest) {
|
||||
HttpRequest req = (HttpRequest) msg;
|
||||
|
||||
if (is100ContinueExpected(req)) {
|
||||
if (HttpHeaderUtil.is100ContinueExpected(req)) {
|
||||
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
|
||||
}
|
||||
boolean keepAlive = isKeepAlive(req);
|
||||
boolean keepAlive = HttpHeaderUtil.isKeepAlive(req);
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT));
|
||||
response.headers().set(CONTENT_TYPE, "text/plain");
|
||||
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
|
||||
|
@ -18,7 +18,7 @@ package io.netty.example.http.snoop;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.HttpContent;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpObject;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.LastHttpContent;
|
||||
@ -44,7 +44,7 @@ public class HttpSnoopClientHandler extends SimpleChannelInboundHandler<HttpObje
|
||||
System.err.println();
|
||||
}
|
||||
|
||||
if (HttpHeaders.isTransferEncodingChunked(response)) {
|
||||
if (HttpHeaderUtil.isTransferEncodingChunked(response)) {
|
||||
System.err.println("CHUNKED CONTENT {");
|
||||
} else {
|
||||
System.err.println("CONTENT {");
|
||||
|
@ -26,6 +26,7 @@ import io.netty.handler.codec.http.CookieDecoder;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpContent;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpObject;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
@ -40,7 +41,6 @@ import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
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.*;
|
||||
|
||||
@ -60,7 +60,7 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object>
|
||||
if (msg instanceof HttpRequest) {
|
||||
HttpRequest request = this.request = (HttpRequest) msg;
|
||||
|
||||
if (is100ContinueExpected(request)) {
|
||||
if (HttpHeaderUtil.is100ContinueExpected(request)) {
|
||||
send100Continue(ctx);
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object>
|
||||
buf.append("===================================\r\n");
|
||||
|
||||
buf.append("VERSION: ").append(request.getProtocolVersion()).append("\r\n");
|
||||
buf.append("HOSTNAME: ").append(getHost(request, "unknown")).append("\r\n");
|
||||
buf.append("HOSTNAME: ").append(request.headers().get(HOST, "unknown")).append("\r\n");
|
||||
buf.append("REQUEST_URI: ").append(request.getUri()).append("\r\n\r\n");
|
||||
|
||||
HttpHeaders headers = request.headers();
|
||||
@ -145,7 +145,7 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object>
|
||||
|
||||
private boolean writeResponse(HttpObject currentObj, ChannelHandlerContext ctx) {
|
||||
// Decide whether to close the connection or not.
|
||||
boolean keepAlive = isKeepAlive(request);
|
||||
boolean keepAlive = HttpHeaderUtil.isKeepAlive(request);
|
||||
// Build the response object.
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||
HTTP_1_1, currentObj.decoderResult().isSuccess()? OK : BAD_REQUEST,
|
||||
|
@ -18,7 +18,7 @@ package io.netty.example.http.upload;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.HttpContent;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpObject;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.LastHttpContent;
|
||||
@ -47,7 +47,7 @@ public class HttpUploadClientHandler extends SimpleChannelInboundHandler<HttpObj
|
||||
}
|
||||
}
|
||||
|
||||
if (response.getStatus().code() == 200 && HttpHeaders.isTransferEncodingChunked(response)) {
|
||||
if (response.getStatus().code() == 200 && HttpHeaderUtil.isTransferEncodingChunked(response)) {
|
||||
readingChunks = true;
|
||||
System.err.println("CHUNKED CONTENT {");
|
||||
} else {
|
||||
|
@ -26,6 +26,7 @@ import io.netty.handler.codec.http.CookieDecoder;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpContent;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpObject;
|
||||
@ -157,7 +158,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
|
||||
return;
|
||||
}
|
||||
|
||||
readingChunks = HttpHeaders.isTransferEncodingChunked(request);
|
||||
readingChunks = HttpHeaderUtil.isTransferEncodingChunked(request);
|
||||
responseContent.append("Is Chunked: " + readingChunks + "\r\n");
|
||||
responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n");
|
||||
if (readingChunks) {
|
||||
|
@ -24,6 +24,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
|
||||
@ -34,7 +35,6 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.*;
|
||||
import static io.netty.handler.codec.http.HttpMethod.*;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||
@ -81,7 +81,7 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
|
||||
FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content);
|
||||
|
||||
res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
|
||||
setContentLength(res, content.readableBytes());
|
||||
HttpHeaderUtil.setContentLength(res, content.readableBytes());
|
||||
|
||||
sendHttpResponse(ctx, req, res);
|
||||
return;
|
||||
@ -132,12 +132,12 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
|
||||
ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
|
||||
res.content().writeBytes(buf);
|
||||
buf.release();
|
||||
setContentLength(res, res.content().readableBytes());
|
||||
HttpHeaderUtil.setContentLength(res, res.content().readableBytes());
|
||||
}
|
||||
|
||||
// Send the response and close the connection if necessary.
|
||||
ChannelFuture f = ctx.channel().writeAndFlush(res);
|
||||
if (!isKeepAlive(req) || res.getStatus().code() != 200) {
|
||||
if (!HttpHeaderUtil.isKeepAlive(req) || res.getStatus().code() != 200) {
|
||||
f.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
||||
@ -35,10 +36,10 @@ public class HelloWorldHttp1Handler extends SimpleChannelInboundHandler<HttpRequ
|
||||
|
||||
@Override
|
||||
public void messageReceived(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
|
||||
if (is100ContinueExpected(req)) {
|
||||
if (HttpHeaderUtil.is100ContinueExpected(req)) {
|
||||
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
|
||||
}
|
||||
boolean keepAlive = isKeepAlive(req);
|
||||
boolean keepAlive = HttpHeaderUtil.isKeepAlive(req);
|
||||
|
||||
ByteBuf content = ctx.alloc().buffer();
|
||||
content.writeBytes(HelloWorldHttp2Handler.RESPONSE_BYTES);
|
||||
|
@ -20,7 +20,7 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.example.http.snoop.HttpSnoopClientHandler;
|
||||
import io.netty.handler.codec.http.HttpContent;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpObject;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.LastHttpContent;
|
||||
@ -55,7 +55,7 @@ public class HttpResponseClientHandler extends SimpleChannelInboundHandler<HttpO
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
if (HttpHeaders.isTransferEncodingChunked(response)) {
|
||||
if (HttpHeaderUtil.isTransferEncodingChunked(response)) {
|
||||
System.out.println("CHUNKED CONTENT {");
|
||||
} else {
|
||||
System.out.println("CONTENT {");
|
||||
|
@ -20,6 +20,7 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpHeaders;
|
||||
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
|
||||
|
||||
/**
|
||||
* Adds a unique client stream ID to the SPDY header. Client stream IDs MUST be odd.
|
||||
@ -37,7 +38,7 @@ public class SpdyClientStreamIdHandler extends ChannelHandlerAdapter {
|
||||
if (acceptOutboundMessage(msg)) {
|
||||
HttpMessage httpMsg = (HttpMessage) msg;
|
||||
if (!httpMsg.headers().contains(SpdyHttpHeaders.Names.STREAM_ID)) {
|
||||
SpdyHttpHeaders.setStreamId(httpMsg, currentStreamId);
|
||||
httpMsg.headers().set(Names.STREAM_ID, currentStreamId);
|
||||
// Client stream IDs are always odd
|
||||
currentStreamId += 2;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
@ -42,10 +43,10 @@ public class SpdyServerHandler extends SimpleChannelInboundHandler<Object> {
|
||||
if (msg instanceof HttpRequest) {
|
||||
HttpRequest req = (HttpRequest) msg;
|
||||
|
||||
if (is100ContinueExpected(req)) {
|
||||
if (HttpHeaderUtil.is100ContinueExpected(req)) {
|
||||
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
|
||||
}
|
||||
boolean keepAlive = isKeepAlive(req);
|
||||
boolean keepAlive = HttpHeaderUtil.isKeepAlive(req);
|
||||
|
||||
ByteBuf content = Unpooled.copiedBuffer("Hello World " + new Date(), CharsetUtil.UTF_8);
|
||||
|
||||
|
177
license/LICENSE.harmony.txt
Normal file
177
license/LICENSE.harmony.txt
Normal 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
|
||||
|
@ -24,6 +24,7 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
|
||||
@ -126,12 +127,12 @@ public class AutobahnServerHandler extends ChannelHandlerAdapter {
|
||||
ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
|
||||
res.content().writeBytes(buf);
|
||||
buf.release();
|
||||
setContentLength(res, res.content().readableBytes());
|
||||
HttpHeaderUtil.setContentLength(res, res.content().readableBytes());
|
||||
}
|
||||
|
||||
// Send the response and close the connection if necessary.
|
||||
ChannelFuture f = ctx.channel().writeAndFlush(res);
|
||||
if (!isKeepAlive(req) || res.getStatus().code() != 200) {
|
||||
if (!HttpHeaderUtil.isKeepAlive(req) || res.getStatus().code() != 200) {
|
||||
f.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user