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/LICENSE.deque.txt (Public Domain)
|
* 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
|
This product contains a modified version of Roland Kuhn's ASL2
|
||||||
AbstractNodeQueue, which is based on Dmitriy Vyukov's non-intrusive MPSC queue.
|
AbstractNodeQueue, which is based on Dmitriy Vyukov's non-intrusive MPSC queue.
|
||||||
It can be obtained at:
|
It can be obtained at:
|
||||||
@ -137,3 +145,4 @@ can be obtained at:
|
|||||||
* license/LICENSE.log4j.txt (Apache License 2.0)
|
* license/LICENSE.log4j.txt (Apache License 2.0)
|
||||||
* HOMEPAGE:
|
* HOMEPAGE:
|
||||||
* http://logging.apache.org/log4j/
|
* http://logging.apache.org/log4j/
|
||||||
|
|
||||||
|
@ -15,30 +15,36 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http;
|
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.Calendar;
|
||||||
import java.util.Date;
|
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;
|
protected final boolean validate;
|
||||||
|
|
||||||
public DefaultHttpHeaders() {
|
public DefaultHttpHeaders() {
|
||||||
@ -47,407 +53,236 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
|||||||
|
|
||||||
public DefaultHttpHeaders(boolean validate) {
|
public DefaultHttpHeaders(boolean validate) {
|
||||||
this.validate = validate;
|
this.validate = validate;
|
||||||
head.before = head.after = head;
|
|
||||||
}
|
|
||||||
|
|
||||||
void validateHeaderName0(CharSequence headerName) {
|
|
||||||
validateHeaderName(headerName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders add(HttpHeaders headers) {
|
protected CharSequence convertName(CharSequence name) {
|
||||||
if (headers instanceof DefaultHttpHeaders) {
|
name = super.convertName(name);
|
||||||
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;
|
|
||||||
if (validate) {
|
if (validate) {
|
||||||
validateHeaderName0(name);
|
if (name instanceof AsciiString) {
|
||||||
strVal = toCharSequence(value);
|
validateName((AsciiString) name);
|
||||||
validateHeaderValue(strVal);
|
} else {
|
||||||
} else {
|
validateName(name);
|
||||||
strVal = toCharSequence(value);
|
}
|
||||||
}
|
}
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
return name;
|
||||||
add0(h, i, name, strVal);
|
}
|
||||||
|
|
||||||
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders add(CharSequence name, Iterable<?> values) {
|
public HttpHeaders add(CharSequence name, Iterable<?> values) {
|
||||||
if (validate) {
|
super.add(name, values);
|
||||||
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);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders set(final CharSequence name, final Iterable<?> values) {
|
public HttpHeaders add(CharSequence name, Object... values) {
|
||||||
if (values == null) {
|
super.add(name, values);
|
||||||
throw new NullPointerException("values");
|
return this;
|
||||||
}
|
}
|
||||||
if (validate) {
|
|
||||||
validateHeaderName0(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
int h = hash(name);
|
@Override
|
||||||
int i = index(h);
|
public HttpHeaders add(TextHeaders headers) {
|
||||||
|
super.add(headers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
remove0(h, i, name);
|
@Override
|
||||||
for (Object v: values) {
|
public HttpHeaders set(CharSequence name, Object value) {
|
||||||
if (v == null) {
|
super.set(name, value);
|
||||||
break;
|
return this;
|
||||||
}
|
}
|
||||||
CharSequence strVal = toCharSequence(v);
|
|
||||||
if (validate) {
|
|
||||||
validateHeaderValue(strVal);
|
|
||||||
}
|
|
||||||
add0(h, i, name, strVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders clear() {
|
public HttpHeaders clear() {
|
||||||
Arrays.fill(entries, null);
|
super.clear();
|
||||||
head.before = head.after = head;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String get(final CharSequence name) {
|
public HttpHeaders forEachEntry(TextHeaderProcessor processor) {
|
||||||
if (name == null) {
|
super.forEachEntry(processor);
|
||||||
throw new NullPointerException("name");
|
return this;
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ public abstract class DefaultHttpMessage extends DefaultHttpObject implements Ht
|
|||||||
buf.append("(version: ");
|
buf.append("(version: ");
|
||||||
buf.append(getProtocolVersion().text());
|
buf.append(getProtocolVersion().text());
|
||||||
buf.append(", keepAlive: ");
|
buf.append(", keepAlive: ");
|
||||||
buf.append(HttpHeaders.isKeepAlive(this));
|
buf.append(HttpHeaderUtil.isKeepAlive(this));
|
||||||
buf.append(')');
|
buf.append(')');
|
||||||
buf.append(StringUtil.NEWLINE);
|
buf.append(StringUtil.NEWLINE);
|
||||||
appendHeaders(buf);
|
appendHeaders(buf);
|
||||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.util.internal.StringUtil;
|
import io.netty.util.internal.StringUtil;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -39,7 +40,7 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
|
|||||||
|
|
||||||
public DefaultLastHttpContent(ByteBuf content, boolean validateHeaders) {
|
public DefaultLastHttpContent(ByteBuf content, boolean validateHeaders) {
|
||||||
super(content);
|
super(content);
|
||||||
trailingHeaders = new TrailingHeaders(validateHeaders);
|
trailingHeaders = new TrailingHttpHeaders(validateHeaders);
|
||||||
this.validateHeaders = validateHeaders;
|
this.validateHeaders = validateHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,20 +107,23 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class TrailingHeaders extends DefaultHttpHeaders {
|
private static final class TrailingHttpHeaders extends DefaultHttpHeaders {
|
||||||
TrailingHeaders(boolean validate) {
|
TrailingHttpHeaders(boolean validate) {
|
||||||
super(validate);
|
super(validate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void validateHeaderName0(CharSequence name) {
|
protected CharSequence convertName(CharSequence name) {
|
||||||
super.validateHeaderName0(name);
|
name = super.convertName(name);
|
||||||
if (equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) ||
|
if (validate) {
|
||||||
equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) ||
|
if (AsciiString.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) ||
|
||||||
equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) {
|
AsciiString.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) ||
|
||||||
throw new IllegalArgumentException(
|
AsciiString.equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) {
|
||||||
"prohibited trailing header: " + 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;
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
import io.netty.channel.embedded.EmbeddedChannel;
|
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.ZlibCodecFactory;
|
||||||
import io.netty.handler.codec.compression.ZlibWrapper;
|
import io.netty.handler.codec.compression.ZlibWrapper;
|
||||||
import io.netty.util.internal.StringUtil;
|
import io.netty.util.internal.StringUtil;
|
||||||
@ -96,7 +97,7 @@ public class HttpContentCompressor extends HttpContentEncoder {
|
|||||||
protected Result beginEncode(HttpResponse headers, CharSequence acceptEncoding) throws Exception {
|
protected Result beginEncode(HttpResponse headers, CharSequence acceptEncoding) throws Exception {
|
||||||
String contentEncoding = headers.headers().get(HttpHeaders.Names.CONTENT_ENCODING);
|
String contentEncoding = headers.headers().get(HttpHeaders.Names.CONTENT_ENCODING);
|
||||||
if (contentEncoding != null &&
|
if (contentEncoding != null &&
|
||||||
!HttpHeaders.equalsIgnoreCase(HttpHeaders.Values.IDENTITY, contentEncoding)) {
|
!AsciiString.equalsIgnoreCase(HttpHeaders.Values.IDENTITY, contentEncoding)) {
|
||||||
return null;
|
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) {
|
void encode(ByteBuf buf) {
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
HttpHeaders.encodeAscii0(name, buf);
|
HttpHeaderUtil.encodeAscii0(name, buf);
|
||||||
} else {
|
} else {
|
||||||
buf.writeBytes(bytes);
|
buf.writeBytes(bytes);
|
||||||
}
|
}
|
||||||
|
@ -95,17 +95,17 @@ public class HttpObjectAggregator
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean hasContentLength(HttpMessage start) throws Exception {
|
protected boolean hasContentLength(HttpMessage start) throws Exception {
|
||||||
return HttpHeaders.isContentLengthSet(start);
|
return HttpHeaderUtil.isContentLengthSet(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected long contentLength(HttpMessage start) throws Exception {
|
protected long contentLength(HttpMessage start) throws Exception {
|
||||||
return HttpHeaders.getContentLength(start);
|
return HttpHeaderUtil.getContentLength(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object newContinueResponse(HttpMessage start) throws Exception {
|
protected Object newContinueResponse(HttpMessage start) throws Exception {
|
||||||
if (HttpHeaders.is100ContinueExpected(start)) {
|
if (HttpHeaderUtil.is100ContinueExpected(start)) {
|
||||||
return CONTINUE;
|
return CONTINUE;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@ -116,7 +116,7 @@ public class HttpObjectAggregator
|
|||||||
protected FullHttpMessage beginAggregation(HttpMessage start, ByteBuf content) throws Exception {
|
protected FullHttpMessage beginAggregation(HttpMessage start, ByteBuf content) throws Exception {
|
||||||
assert !(start instanceof FullHttpMessage);
|
assert !(start instanceof FullHttpMessage);
|
||||||
|
|
||||||
HttpHeaders.removeTransferEncodingChunked(start);
|
HttpHeaderUtil.setTransferEncodingChunked(start, false);
|
||||||
|
|
||||||
FullHttpMessage ret;
|
FullHttpMessage ret;
|
||||||
if (start instanceof HttpRequest) {
|
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 '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 keep-alive is off, no need to leave the connection open.
|
||||||
if (oversized instanceof FullHttpMessage ||
|
if (oversized instanceof FullHttpMessage ||
|
||||||
!HttpHeaders.is100ContinueExpected(oversized) || !HttpHeaders.isKeepAlive(oversized)) {
|
!HttpHeaderUtil.is100ContinueExpected(oversized) || !HttpHeaderUtil.isKeepAlive(oversized)) {
|
||||||
future.addListener(ChannelFutureListener.CLOSE);
|
future.addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import io.netty.buffer.ByteBufProcessor;
|
|||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelPipeline;
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.DecoderResult;
|
import io.netty.handler.codec.DecoderResult;
|
||||||
import io.netty.handler.codec.ReplayingDecoder;
|
import io.netty.handler.codec.ReplayingDecoder;
|
||||||
import io.netty.handler.codec.TooLongFrameException;
|
import io.netty.handler.codec.TooLongFrameException;
|
||||||
@ -520,9 +521,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
State nextState;
|
State nextState;
|
||||||
|
|
||||||
if (isContentAlwaysEmpty(message)) {
|
if (isContentAlwaysEmpty(message)) {
|
||||||
HttpHeaders.removeTransferEncodingChunked(message);
|
HttpHeaderUtil.setTransferEncodingChunked(message, false);
|
||||||
nextState = State.SKIP_CONTROL_CHARS;
|
nextState = State.SKIP_CONTROL_CHARS;
|
||||||
} else if (HttpHeaders.isTransferEncodingChunked(message)) {
|
} else if (HttpHeaderUtil.isTransferEncodingChunked(message)) {
|
||||||
nextState = State.READ_CHUNK_SIZE;
|
nextState = State.READ_CHUNK_SIZE;
|
||||||
} else if (contentLength() >= 0) {
|
} else if (contentLength() >= 0) {
|
||||||
nextState = State.READ_FIXED_LENGTH_CONTENT;
|
nextState = State.READ_FIXED_LENGTH_CONTENT;
|
||||||
@ -534,7 +535,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
|
|
||||||
private long contentLength() {
|
private long contentLength() {
|
||||||
if (contentLength == Long.MIN_VALUE) {
|
if (contentLength == Long.MIN_VALUE) {
|
||||||
contentLength = HttpHeaders.getContentLength(message, -1);
|
contentLength = HttpHeaderUtil.getContentLength(message, -1);
|
||||||
}
|
}
|
||||||
return contentLength;
|
return contentLength;
|
||||||
}
|
}
|
||||||
@ -559,9 +560,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
|
|||||||
} else {
|
} else {
|
||||||
String[] header = splitHeader(line);
|
String[] header = splitHeader(line);
|
||||||
String name = header[0];
|
String name = header[0];
|
||||||
if (!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.CONTENT_LENGTH) &&
|
if (!AsciiString.equalsIgnoreCase(name, HttpHeaders.Names.CONTENT_LENGTH) &&
|
||||||
!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.TRANSFER_ENCODING) &&
|
!AsciiString.equalsIgnoreCase(name, HttpHeaders.Names.TRANSFER_ENCODING) &&
|
||||||
!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.TRAILER)) {
|
!AsciiString.equalsIgnoreCase(name, HttpHeaders.Names.TRAILER)) {
|
||||||
trailer.trailingHeaders().add(name, header[1]);
|
trailer.trailingHeaders().add(name, header[1]);
|
||||||
}
|
}
|
||||||
lastHeader = name;
|
lastHeader = name;
|
||||||
|
@ -69,9 +69,9 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
|
|||||||
buf = ctx.alloc().buffer();
|
buf = ctx.alloc().buffer();
|
||||||
// Encode the message.
|
// Encode the message.
|
||||||
encodeInitialLine(buf, m);
|
encodeInitialLine(buf, m);
|
||||||
HttpHeaders.encode(m.headers(), buf);
|
m.headers().forEachEntry(new HttpHeadersEncoder(buf));
|
||||||
buf.writeBytes(CRLF);
|
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 (msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion) {
|
||||||
if (state == ST_INIT) {
|
if (state == ST_INIT) {
|
||||||
@ -137,7 +137,7 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
|
|||||||
} else {
|
} else {
|
||||||
ByteBuf buf = ctx.alloc().buffer();
|
ByteBuf buf = ctx.alloc().buffer();
|
||||||
buf.writeBytes(ZERO_CRLF);
|
buf.writeBytes(ZERO_CRLF);
|
||||||
HttpHeaders.encode(headers, buf);
|
headers.forEachEntry(new HttpHeadersEncoder(buf));
|
||||||
buf.writeBytes(CRLF);
|
buf.writeBytes(CRLF);
|
||||||
out.add(buf);
|
out.add(buf);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ package io.netty.handler.codec.http;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.util.CharsetUtil;
|
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
|
* 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 + ')');
|
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 int code;
|
||||||
|
|
||||||
private final String reasonPhrase;
|
private final String reasonPhrase;
|
||||||
@ -548,9 +578,9 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
|
|||||||
|
|
||||||
void encode(ByteBuf buf) {
|
void encode(ByteBuf buf) {
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
HttpHeaders.encodeAscii0(String.valueOf(code()), buf);
|
HttpHeaderUtil.encodeAscii0(String.valueOf(code()), buf);
|
||||||
buf.writeByte(SP);
|
buf.writeByte(SP);
|
||||||
HttpHeaders.encodeAscii0(String.valueOf(reasonPhrase()), buf);
|
HttpHeaderUtil.encodeAscii0(String.valueOf(reasonPhrase()), buf);
|
||||||
} else {
|
} else {
|
||||||
buf.writeBytes(bytes);
|
buf.writeBytes(bytes);
|
||||||
}
|
}
|
||||||
|
@ -264,7 +264,7 @@ public class HttpVersion implements Comparable<HttpVersion> {
|
|||||||
|
|
||||||
void encode(ByteBuf buf) {
|
void encode(ByteBuf buf) {
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
HttpHeaders.encodeAscii0(text, buf);
|
HttpHeaderUtil.encodeAscii0(text, buf);
|
||||||
} else {
|
} else {
|
||||||
buf.writeBytes(bytes);
|
buf.writeBytes(bytes);
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ public interface LastHttpContent extends HttpContent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders trailingHeaders() {
|
public HttpHeaders trailingHeaders() {
|
||||||
return HttpHeaders.EMPTY_HEADERS;
|
return EmptyHttpHeaders.INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.handler.codec.http.cors;
|
package io.netty.handler.codec.http.cors;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
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;
|
||||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
@ -202,7 +203,7 @@ public final class CorsConfig {
|
|||||||
*/
|
*/
|
||||||
public HttpHeaders preflightResponseHeaders() {
|
public HttpHeaders preflightResponseHeaders() {
|
||||||
if (preflightHeaders.isEmpty()) {
|
if (preflightHeaders.isEmpty()) {
|
||||||
return HttpHeaders.EMPTY_HEADERS;
|
return EmptyHttpHeaders.INSTANCE;
|
||||||
}
|
}
|
||||||
final HttpHeaders preflightHeaders = new DefaultHttpHeaders();
|
final HttpHeaders preflightHeaders = new DefaultHttpHeaders();
|
||||||
for (Entry<CharSequence, Callable<?>> entry : this.preflightHeaders.entrySet()) {
|
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.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.DecoderResult;
|
import io.netty.handler.codec.DecoderResult;
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||||
import io.netty.handler.codec.http.DefaultHttpContent;
|
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.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.HttpConstants;
|
import io.netty.handler.codec.http.HttpConstants;
|
||||||
import io.netty.handler.codec.http.HttpContent;
|
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.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
@ -741,14 +744,14 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
|
|||||||
if (transferEncoding != null) {
|
if (transferEncoding != null) {
|
||||||
headers.remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
headers.remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||||
for (String v : transferEncoding) {
|
for (String v : transferEncoding) {
|
||||||
if (HttpHeaders.equalsIgnoreCase(v, HttpHeaders.Values.CHUNKED)) {
|
if (AsciiString.equalsIgnoreCase(v, HttpHeaders.Values.CHUNKED)) {
|
||||||
// ignore
|
// ignore
|
||||||
} else {
|
} else {
|
||||||
headers.add(HttpHeaders.Names.TRANSFER_ENCODING, v);
|
headers.add(HttpHeaders.Names.TRANSFER_ENCODING, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HttpHeaders.setTransferEncodingChunked(request);
|
HttpHeaderUtil.setTransferEncodingChunked(request, true);
|
||||||
|
|
||||||
// wrap to hide the possible content
|
// wrap to hide the possible content
|
||||||
return new WrappedHttpRequest(request);
|
return new WrappedHttpRequest(request);
|
||||||
@ -1237,7 +1240,7 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
|
|||||||
if (content instanceof LastHttpContent) {
|
if (content instanceof LastHttpContent) {
|
||||||
return ((LastHttpContent) content).trailingHeaders();
|
return ((LastHttpContent) content).trailingHeaders();
|
||||||
} else {
|
} 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.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
@ -198,13 +199,13 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
|||||||
HttpHeaders headers = response.headers();
|
HttpHeaders headers = response.headers();
|
||||||
|
|
||||||
String upgrade = headers.get(Names.UPGRADE);
|
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: "
|
throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
|
||||||
+ upgrade);
|
+ upgrade);
|
||||||
}
|
}
|
||||||
|
|
||||||
String connection = headers.get(Names.CONNECTION);
|
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: "
|
throw new WebSocketHandshakeException("Invalid handshake response connection: "
|
||||||
+ connection);
|
+ connection);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http.websocketx;
|
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.DefaultFullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
@ -40,7 +41,7 @@ import java.net.URI;
|
|||||||
public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker {
|
public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker {
|
||||||
|
|
||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker07.class);
|
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";
|
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
private String expectedChallengeResponseString;
|
private String expectedChallengeResponseString;
|
||||||
@ -173,12 +174,12 @@ public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String upgrade = headers.get(Names.UPGRADE);
|
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);
|
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade);
|
||||||
}
|
}
|
||||||
|
|
||||||
String connection = headers.get(Names.CONNECTION);
|
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);
|
throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http.websocketx;
|
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.DefaultFullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
@ -40,7 +41,7 @@ import java.net.URI;
|
|||||||
public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
|
public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
|
||||||
|
|
||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker08.class);
|
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";
|
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);
|
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);
|
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade);
|
||||||
}
|
}
|
||||||
|
|
||||||
String connection = headers.get(Names.CONNECTION);
|
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);
|
throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http.websocketx;
|
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.DefaultFullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
@ -40,7 +41,7 @@ import java.net.URI;
|
|||||||
public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
|
public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
|
||||||
|
|
||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.class);
|
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";
|
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);
|
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);
|
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade);
|
||||||
}
|
}
|
||||||
|
|
||||||
String connection = headers.get(Names.CONNECTION);
|
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);
|
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.Channel;
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
@ -109,8 +110,8 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
|||||||
protected FullHttpResponse newHandshakeResponse(FullHttpRequest req, HttpHeaders headers) {
|
protected FullHttpResponse newHandshakeResponse(FullHttpRequest req, HttpHeaders headers) {
|
||||||
|
|
||||||
// Serve the WebSocket handshake request.
|
// Serve the WebSocket handshake request.
|
||||||
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, req.headers().get(CONNECTION))
|
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, req.headers().get(CONNECTION))
|
||||||
|| !HttpHeaders.equalsIgnoreCase(WEBSOCKET, req.headers().get(Names.UPGRADE))) {
|
|| !AsciiString.equalsIgnoreCase(WEBSOCKET, req.headers().get(Names.UPGRADE))) {
|
||||||
throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
|
throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http.websocketx;
|
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.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
@ -35,7 +36,7 @@ import static io.netty.handler.codec.http.HttpVersion.*;
|
|||||||
*/
|
*/
|
||||||
public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
|
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";
|
public static final String WEBSOCKET_07_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http.websocketx;
|
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.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
@ -35,7 +36,7 @@ import static io.netty.handler.codec.http.HttpVersion.*;
|
|||||||
*/
|
*/
|
||||||
public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
|
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";
|
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http.websocketx;
|
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.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
@ -34,7 +35,7 @@ import static io.netty.handler.codec.http.HttpVersion.*;
|
|||||||
*/
|
*/
|
||||||
public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
|
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";
|
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.channel.ChannelPipeline;
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
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.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
import io.netty.handler.ssl.SslHandler;
|
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.HttpMethod.*;
|
||||||
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
||||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||||
@ -47,7 +47,7 @@ class WebSocketServerProtocolHandshakeHandler extends ChannelHandlerAdapter {
|
|||||||
this.websocketPath = websocketPath;
|
this.websocketPath = websocketPath;
|
||||||
this.subprotocols = subprotocols;
|
this.subprotocols = subprotocols;
|
||||||
this.allowExtensions = allowExtensions;
|
this.allowExtensions = allowExtensions;
|
||||||
this.maxFramePayloadSize = maxFrameSize;
|
maxFramePayloadSize = maxFrameSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -89,7 +89,7 @@ class WebSocketServerProtocolHandshakeHandler extends ChannelHandlerAdapter {
|
|||||||
|
|
||||||
private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
|
private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
|
||||||
ChannelFuture f = ctx.channel().writeAndFlush(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);
|
f.addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.rtsp;
|
package io.netty.handler.codec.rtsp;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.http.HttpHeaders;
|
import io.netty.handler.codec.http.HttpHeaders;
|
||||||
|
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "Allow"}
|
* {@code "Allow"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence ALLOW = HttpHeaders.newEntity("Allow");
|
public static final CharSequence ALLOW = new AsciiString("Allow");
|
||||||
/**
|
/**
|
||||||
* {@code "Authorization"}
|
* {@code "Authorization"}
|
||||||
*/
|
*/
|
||||||
@ -50,11 +51,11 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "Bandwidth"}
|
* {@code "Bandwidth"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence BANDWIDTH = HttpHeaders.newEntity("Bandwidth");
|
public static final CharSequence BANDWIDTH = new AsciiString("Bandwidth");
|
||||||
/**
|
/**
|
||||||
* {@code "Blocksize"}
|
* {@code "Blocksize"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence BLOCKSIZE = HttpHeaders.newEntity("Blocksize");
|
public static final CharSequence BLOCKSIZE = new AsciiString("Blocksize");
|
||||||
/**
|
/**
|
||||||
* {@code "Cache-Control"}
|
* {@code "Cache-Control"}
|
||||||
*/
|
*/
|
||||||
@ -62,7 +63,7 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "Conference"}
|
* {@code "Conference"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence CONFERENCE = HttpHeaders.newEntity("Conference");
|
public static final CharSequence CONFERENCE = new AsciiString("Conference");
|
||||||
/**
|
/**
|
||||||
* {@code "Connection"}
|
* {@code "Connection"}
|
||||||
*/
|
*/
|
||||||
@ -94,7 +95,7 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "CSeq"}
|
* {@code "CSeq"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence CSEQ = HttpHeaders.newEntity("CSeq");
|
public static final CharSequence CSEQ = new AsciiString("CSeq");
|
||||||
/**
|
/**
|
||||||
* {@code "Date"}
|
* {@code "Date"}
|
||||||
*/
|
*/
|
||||||
@ -122,7 +123,7 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "KeyMgmt"}
|
* {@code "KeyMgmt"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence KEYMGMT = HttpHeaders.newEntity("KeyMgmt");
|
public static final CharSequence KEYMGMT = new AsciiString("KeyMgmt");
|
||||||
/**
|
/**
|
||||||
* {@code "Last-Modified"}
|
* {@code "Last-Modified"}
|
||||||
*/
|
*/
|
||||||
@ -134,11 +135,11 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "Proxy-Require"}
|
* {@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"}
|
* {@code "Public"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence PUBLIC = HttpHeaders.newEntity("Public");
|
public static final CharSequence PUBLIC = new AsciiString("Public");
|
||||||
/**
|
/**
|
||||||
* {@code "Range"}
|
* {@code "Range"}
|
||||||
*/
|
*/
|
||||||
@ -150,7 +151,7 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "Require"}
|
* {@code "Require"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence REQUIRE = HttpHeaders.newEntity("Require");
|
public static final CharSequence REQUIRE = new AsciiString("Require");
|
||||||
/**
|
/**
|
||||||
* {@code "Retry-After"}
|
* {@code "Retry-After"}
|
||||||
*/
|
*/
|
||||||
@ -158,15 +159,15 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "RTP-Info"}
|
* {@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"}
|
* {@code "Scale"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence SCALE = HttpHeaders.newEntity("Scale");
|
public static final CharSequence SCALE = new AsciiString("Scale");
|
||||||
/**
|
/**
|
||||||
* {@code "Session"}
|
* {@code "Session"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence SESSION = HttpHeaders.newEntity("Session");
|
public static final CharSequence SESSION = new AsciiString("Session");
|
||||||
/**
|
/**
|
||||||
* {@code "Server"}
|
* {@code "Server"}
|
||||||
*/
|
*/
|
||||||
@ -174,19 +175,19 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "Speed"}
|
* {@code "Speed"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence SPEED = HttpHeaders.newEntity("Speed");
|
public static final CharSequence SPEED = new AsciiString("Speed");
|
||||||
/**
|
/**
|
||||||
* {@code "Timestamp"}
|
* {@code "Timestamp"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence TIMESTAMP = HttpHeaders.newEntity("Timestamp");
|
public static final CharSequence TIMESTAMP = new AsciiString("Timestamp");
|
||||||
/**
|
/**
|
||||||
* {@code "Transport"}
|
* {@code "Transport"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence TRANSPORT = HttpHeaders.newEntity("Transport");
|
public static final CharSequence TRANSPORT = new AsciiString("Transport");
|
||||||
/**
|
/**
|
||||||
* {@code "Unsupported"}
|
* {@code "Unsupported"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence UNSUPPORTED = HttpHeaders.newEntity("Unsupported");
|
public static final CharSequence UNSUPPORTED = new AsciiString("Unsupported");
|
||||||
/**
|
/**
|
||||||
* {@code "User-Agent"}
|
* {@code "User-Agent"}
|
||||||
*/
|
*/
|
||||||
@ -215,11 +216,11 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "append"}
|
* {@code "append"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence APPEND = HttpHeaders.newEntity("append");
|
public static final CharSequence APPEND = new AsciiString("append");
|
||||||
/**
|
/**
|
||||||
* {@code "AVP"}
|
* {@code "AVP"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence AVP = HttpHeaders.newEntity("AVP");
|
public static final CharSequence AVP = new AsciiString("AVP");
|
||||||
/**
|
/**
|
||||||
* {@code "bytes"}
|
* {@code "bytes"}
|
||||||
*/
|
*/
|
||||||
@ -231,11 +232,11 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "client_port"}
|
* {@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"}
|
* {@code "clock"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence CLOCK = HttpHeaders.newEntity("clock");
|
public static final CharSequence CLOCK = new AsciiString("clock");
|
||||||
/**
|
/**
|
||||||
* {@code "close"}
|
* {@code "close"}
|
||||||
*/
|
*/
|
||||||
@ -255,7 +256,7 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "destination"}
|
* {@code "destination"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence DESTINATION = HttpHeaders.newEntity("destination");
|
public static final CharSequence DESTINATION = new AsciiString("destination");
|
||||||
/**
|
/**
|
||||||
* {@code "gzip"}
|
* {@code "gzip"}
|
||||||
*/
|
*/
|
||||||
@ -267,7 +268,7 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "interleaved"}
|
* {@code "interleaved"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence INTERLEAVED = HttpHeaders.newEntity("interleaved");
|
public static final CharSequence INTERLEAVED = new AsciiString("interleaved");
|
||||||
/**
|
/**
|
||||||
* {@code "keep-alive"}
|
* {@code "keep-alive"}
|
||||||
*/
|
*/
|
||||||
@ -275,7 +276,7 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "layers"}
|
* {@code "layers"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence LAYERS = HttpHeaders.newEntity("layers");
|
public static final CharSequence LAYERS = new AsciiString("layers");
|
||||||
/**
|
/**
|
||||||
* {@code "max-age"}
|
* {@code "max-age"}
|
||||||
*/
|
*/
|
||||||
@ -291,11 +292,11 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "mode"}
|
* {@code "mode"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence MODE = HttpHeaders.newEntity("mode");
|
public static final CharSequence MODE = new AsciiString("mode");
|
||||||
/**
|
/**
|
||||||
* {@code "multicast"}
|
* {@code "multicast"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence MULTICAST = HttpHeaders.newEntity("multicast");
|
public static final CharSequence MULTICAST = new AsciiString("multicast");
|
||||||
/**
|
/**
|
||||||
* {@code "must-revalidate"}
|
* {@code "must-revalidate"}
|
||||||
*/
|
*/
|
||||||
@ -319,7 +320,7 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "port"}
|
* {@code "port"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence PORT = HttpHeaders.newEntity("port");
|
public static final CharSequence PORT = new AsciiString("port");
|
||||||
/**
|
/**
|
||||||
* {@code "private"}
|
* {@code "private"}
|
||||||
*/
|
*/
|
||||||
@ -335,51 +336,51 @@ public final class RtspHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "RTP"}
|
* {@code "RTP"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence RTP = HttpHeaders.newEntity("RTP");
|
public static final CharSequence RTP = new AsciiString("RTP");
|
||||||
/**
|
/**
|
||||||
* {@code "rtptime"}
|
* {@code "rtptime"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence RTPTIME = HttpHeaders.newEntity("rtptime");
|
public static final CharSequence RTPTIME = new AsciiString("rtptime");
|
||||||
/**
|
/**
|
||||||
* {@code "seq"}
|
* {@code "seq"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence SEQ = HttpHeaders.newEntity("seq");
|
public static final CharSequence SEQ = new AsciiString("seq");
|
||||||
/**
|
/**
|
||||||
* {@code "server_port"}
|
* {@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"}
|
* {@code "ssrc"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence SSRC = HttpHeaders.newEntity("ssrc");
|
public static final CharSequence SSRC = new AsciiString("ssrc");
|
||||||
/**
|
/**
|
||||||
* {@code "TCP"}
|
* {@code "TCP"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence TCP = HttpHeaders.newEntity("TCP");
|
public static final CharSequence TCP = new AsciiString("TCP");
|
||||||
/**
|
/**
|
||||||
* {@code "time"}
|
* {@code "time"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence TIME = HttpHeaders.newEntity("time");
|
public static final CharSequence TIME = new AsciiString("time");
|
||||||
/**
|
/**
|
||||||
* {@code "timeout"}
|
* {@code "timeout"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence TIMEOUT = HttpHeaders.newEntity("timeout");
|
public static final CharSequence TIMEOUT = new AsciiString("timeout");
|
||||||
/**
|
/**
|
||||||
* {@code "ttl"}
|
* {@code "ttl"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence TTL = HttpHeaders.newEntity("ttl");
|
public static final CharSequence TTL = new AsciiString("ttl");
|
||||||
/**
|
/**
|
||||||
* {@code "UDP"}
|
* {@code "UDP"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence UDP = HttpHeaders.newEntity("UDP");
|
public static final CharSequence UDP = new AsciiString("UDP");
|
||||||
/**
|
/**
|
||||||
* {@code "unicast"}
|
* {@code "unicast"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence UNICAST = HttpHeaders.newEntity("unicast");
|
public static final CharSequence UNICAST = new AsciiString("unicast");
|
||||||
/**
|
/**
|
||||||
* {@code "url"}
|
* {@code "url"}
|
||||||
*/
|
*/
|
||||||
public static final CharSequence URL = HttpHeaders.newEntity("url");
|
public static final CharSequence URL = new AsciiString("url");
|
||||||
|
|
||||||
private Values() { }
|
private Values() { }
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ package io.netty.handler.codec.rtsp;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.HttpHeaders;
|
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
@ -37,13 +36,12 @@ public class RtspRequestEncoder extends RtspObjectEncoder<HttpRequest> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void encodeInitialLine(ByteBuf buf, HttpRequest request)
|
protected void encodeInitialLine(ByteBuf buf, HttpRequest request) throws Exception {
|
||||||
throws Exception {
|
buf.writeBytes(request.getMethod().toString().getBytes(CharsetUtil.US_ASCII));
|
||||||
HttpHeaders.encodeAscii(request.getMethod().toString(), buf);
|
|
||||||
buf.writeByte(SP);
|
buf.writeByte(SP);
|
||||||
buf.writeBytes(request.getUri().getBytes(CharsetUtil.UTF_8));
|
buf.writeBytes(request.getUri().getBytes(CharsetUtil.UTF_8));
|
||||||
buf.writeByte(SP);
|
buf.writeByte(SP);
|
||||||
HttpHeaders.encodeAscii(request.getProtocolVersion().toString(), buf);
|
buf.writeBytes(request.getProtocolVersion().toString().getBytes(CharsetUtil.US_ASCII));
|
||||||
buf.writeBytes(CRLF);
|
buf.writeBytes(CRLF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ package io.netty.handler.codec.rtsp;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
import io.netty.handler.codec.http.HttpHeaders;
|
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
@ -37,13 +36,12 @@ public class RtspResponseEncoder extends RtspObjectEncoder<HttpResponse> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void encodeInitialLine(ByteBuf buf, HttpResponse response)
|
protected void encodeInitialLine(ByteBuf buf, HttpResponse response) throws Exception {
|
||||||
throws Exception {
|
buf.writeBytes(response.getProtocolVersion().toString().getBytes(CharsetUtil.US_ASCII));
|
||||||
HttpHeaders.encodeAscii(response.getProtocolVersion().toString(), buf);
|
|
||||||
buf.writeByte(SP);
|
buf.writeByte(SP);
|
||||||
buf.writeBytes(String.valueOf(response.getStatus().code()).getBytes(CharsetUtil.US_ASCII));
|
buf.writeBytes(String.valueOf(response.getStatus().code()).getBytes(CharsetUtil.US_ASCII));
|
||||||
buf.writeByte(SP);
|
buf.writeByte(SP);
|
||||||
HttpHeaders.encodeAscii(String.valueOf(response.getStatus().reasonPhrase()), buf);
|
buf.writeBytes(response.getStatus().reasonPhrase().getBytes(CharsetUtil.US_ASCII));
|
||||||
buf.writeBytes(CRLF);
|
buf.writeBytes(CRLF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,365 +15,101 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.spdy;
|
package io.netty.handler.codec.spdy;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import io.netty.handler.codec.AsciiString;
|
||||||
import java.util.LinkedList;
|
import io.netty.handler.codec.DefaultTextHeaders;
|
||||||
import java.util.List;
|
import io.netty.handler.codec.TextHeaderProcessor;
|
||||||
import java.util.Map;
|
import io.netty.handler.codec.TextHeaders;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.NoSuchElementException;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
|
|
||||||
public class DefaultSpdyHeaders extends SpdyHeaders {
|
public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeaders {
|
||||||
|
@Override
|
||||||
private static final int BUCKET_SIZE = 17;
|
protected CharSequence convertName(CharSequence name) {
|
||||||
|
name = super.convertName(name);
|
||||||
private static int hash(String name) {
|
if (name instanceof AsciiString) {
|
||||||
int h = 0;
|
name = ((AsciiString) name).toLowerCase();
|
||||||
for (int i = name.length() - 1; i >= 0; i --) {
|
|
||||||
char c = name.charAt(i);
|
|
||||||
if (c >= 'A' && c <= 'Z') {
|
|
||||||
c += 32;
|
|
||||||
}
|
|
||||||
h = 31 * h + c;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (h > 0) {
|
|
||||||
return h;
|
|
||||||
} else if (h == Integer.MIN_VALUE) {
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
} else {
|
} else {
|
||||||
return -h;
|
name = name.toString().toLowerCase(Locale.US);
|
||||||
}
|
}
|
||||||
}
|
SpdyCodecUtil.validateHeaderName(name);
|
||||||
|
return name;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpdyHeaders add(final String name, final Object value) {
|
protected CharSequence convertValue(Object value) {
|
||||||
String lowerCaseName = name.toLowerCase();
|
if (value == null) {
|
||||||
SpdyCodecUtil.validateHeaderName(lowerCaseName);
|
throw new NullPointerException("value");
|
||||||
String strVal = toString(value);
|
}
|
||||||
SpdyCodecUtil.validateHeaderValue(strVal);
|
|
||||||
int h = hash(lowerCaseName);
|
|
||||||
int i = index(h);
|
|
||||||
add0(h, i, lowerCaseName, strVal);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add0(int h, int i, final String name, final String value) {
|
CharSequence seq;
|
||||||
// Update the hash table.
|
if (value instanceof CharSequence) {
|
||||||
HeaderEntry e = entries[i];
|
seq = (CharSequence) value;
|
||||||
HeaderEntry newEntry;
|
} else {
|
||||||
entries[i] = newEntry = new HeaderEntry(h, name, value);
|
seq = value.toString();
|
||||||
newEntry.next = e;
|
}
|
||||||
|
|
||||||
// Update the linked list.
|
SpdyCodecUtil.validateHeaderValue(seq);
|
||||||
newEntry.addBefore(head);
|
return seq;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpdyHeaders remove(final String name) {
|
public SpdyHeaders add(CharSequence name, Object value) {
|
||||||
if (name == null) {
|
super.add(name, value);
|
||||||
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);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpdyHeaders set(final String name, final Iterable<?> values) {
|
public SpdyHeaders add(CharSequence name, Iterable<?> values) {
|
||||||
if (values == null) {
|
super.add(name, values);
|
||||||
throw new NullPointerException("values");
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
String lowerCaseName = name.toLowerCase();
|
@Override
|
||||||
SpdyCodecUtil.validateHeaderName(lowerCaseName);
|
public SpdyHeaders add(CharSequence name, Object... values) {
|
||||||
|
super.add(name, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
int h = hash(lowerCaseName);
|
@Override
|
||||||
int i = index(h);
|
public SpdyHeaders add(TextHeaders headers) {
|
||||||
|
super.add(headers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
remove0(h, i, lowerCaseName);
|
@Override
|
||||||
for (Object v: values) {
|
public SpdyHeaders set(CharSequence name, Object value) {
|
||||||
if (v == null) {
|
super.set(name, value);
|
||||||
break;
|
return this;
|
||||||
}
|
}
|
||||||
String strVal = toString(v);
|
|
||||||
SpdyCodecUtil.validateHeaderValue(strVal);
|
@Override
|
||||||
add0(h, i, lowerCaseName, strVal);
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpdyHeaders clear() {
|
public SpdyHeaders clear() {
|
||||||
for (int i = 0; i < entries.length; i ++) {
|
super.clear();
|
||||||
entries[i] = null;
|
|
||||||
}
|
|
||||||
head.before = head.after = head;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String get(final String name) {
|
public SpdyHeaders forEachEntry(TextHeaderProcessor processor) {
|
||||||
if (name == null) {
|
super.forEachEntry(processor);
|
||||||
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);
|
|
||||||
}
|
|
||||||
return this;
|
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.
|
* Validate a SPDY header name.
|
||||||
*/
|
*/
|
||||||
static void validateHeaderName(String name) {
|
static void validateHeaderName(CharSequence name) {
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
throw new NullPointerException("name");
|
throw new NullPointerException("name");
|
||||||
}
|
}
|
||||||
if (name.isEmpty()) {
|
if (name.length() == 0) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"name cannot be length zero");
|
"name cannot be length zero");
|
||||||
}
|
}
|
||||||
@ -315,7 +315,7 @@ final class SpdyCodecUtil {
|
|||||||
/**
|
/**
|
||||||
* Validate a SPDY header value. Does not validate max length.
|
* Validate a SPDY header value. Does not validate max length.
|
||||||
*/
|
*/
|
||||||
static void validateHeaderValue(String value) {
|
static void validateHeaderValue(CharSequence value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw new NullPointerException("value");
|
throw new NullPointerException("value");
|
||||||
}
|
}
|
||||||
|
@ -15,399 +15,75 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.spdy;
|
package io.netty.handler.codec.spdy;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.TextHeaderProcessor;
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.TextHeaders;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the constants for the standard SPDY HTTP header names and commonly
|
* Provides the constants for the standard SPDY HTTP header names and commonly
|
||||||
* used utility methods that access a {@link SpdyHeadersFrame}.
|
* used utility methods that access a {@link SpdyHeadersFrame}.
|
||||||
*/
|
*/
|
||||||
public abstract class SpdyHeaders implements Iterable<Map.Entry<String, String>> {
|
public interface SpdyHeaders extends TextHeaders {
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SPDY HTTP header names
|
* SPDY HTTP header names
|
||||||
*/
|
*/
|
||||||
public static final class HttpNames {
|
final class HttpNames {
|
||||||
/**
|
/**
|
||||||
* {@code ":host"}
|
* {@code ":host"}
|
||||||
*/
|
*/
|
||||||
public static final String HOST = ":host";
|
public static final AsciiString HOST = new AsciiString(":host");
|
||||||
/**
|
/**
|
||||||
* {@code ":method"}
|
* {@code ":method"}
|
||||||
*/
|
*/
|
||||||
public static final String METHOD = ":method";
|
public static final AsciiString METHOD = new AsciiString(":method");
|
||||||
/**
|
/**
|
||||||
* {@code ":path"}
|
* {@code ":path"}
|
||||||
*/
|
*/
|
||||||
public static final String PATH = ":path";
|
public static final AsciiString PATH = new AsciiString(":path");
|
||||||
/**
|
/**
|
||||||
* {@code ":scheme"}
|
* {@code ":scheme"}
|
||||||
*/
|
*/
|
||||||
public static final String SCHEME = ":scheme";
|
public static final AsciiString SCHEME = new AsciiString(":scheme");
|
||||||
/**
|
/**
|
||||||
* {@code ":status"}
|
* {@code ":status"}
|
||||||
*/
|
*/
|
||||||
public static final String STATUS = ":status";
|
public static final AsciiString STATUS = new AsciiString(":status");
|
||||||
/**
|
/**
|
||||||
* {@code ":version"}
|
* {@code ":version"}
|
||||||
*/
|
*/
|
||||||
public static final String VERSION = ":version";
|
public static final AsciiString VERSION = new AsciiString(":version");
|
||||||
|
|
||||||
private HttpNames() { }
|
private HttpNames() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the header value with the specified header name. If there are
|
SpdyHeaders add(CharSequence name, Object value);
|
||||||
* 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
|
@Override
|
||||||
public Iterator<Map.Entry<String, String>> iterator() {
|
SpdyHeaders add(CharSequence name, Iterable<?> values);
|
||||||
return entries().iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the header value with the specified header name. If there is
|
SpdyHeaders add(CharSequence name, Object... values);
|
||||||
* more than one header value for the specified header name, the first
|
|
||||||
* value is returned.
|
|
||||||
*
|
|
||||||
* @return the header value or {@code null} if there is no such header
|
|
||||||
*/
|
|
||||||
public abstract String get(String name);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the header values with the specified header name.
|
SpdyHeaders add(TextHeaders headers);
|
||||||
*
|
|
||||||
* @return the {@link List} of header values. An empty list if there is no
|
|
||||||
* such header.
|
|
||||||
*/
|
|
||||||
public abstract List<String> getAll(String name);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns all header names and values that this frame contains.
|
SpdyHeaders set(CharSequence name, Object value);
|
||||||
*
|
|
||||||
* @return the {@link List} of the header name-value pairs. An empty list
|
|
||||||
* if there is no header in this message.
|
|
||||||
*/
|
|
||||||
public abstract List<Map.Entry<String, String>> entries();
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns {@code true} if and only if there is a header with the specified
|
SpdyHeaders set(CharSequence name, Iterable<?> values);
|
||||||
* header name.
|
|
||||||
*/
|
|
||||||
public abstract boolean contains(String name);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the {@link Set} of all header names that this frame contains.
|
SpdyHeaders set(CharSequence name, Object... values);
|
||||||
*/
|
|
||||||
public abstract Set<String> names();
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Adds a new header with the specified name and value.
|
SpdyHeaders set(TextHeaders headers);
|
||||||
*/
|
|
||||||
public abstract SpdyHeaders add(String name, Object value);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Adds a new header with the specified name and values. If there is an
|
SpdyHeaders clear();
|
||||||
* existing header with the same name, the existing header is removed.
|
|
||||||
*/
|
|
||||||
public abstract SpdyHeaders add(String name, Iterable<?> values);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Sets a new header with the specified name and value. If there is an
|
SpdyHeaders forEachEntry(TextHeaderProcessor processor);
|
||||||
* 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();
|
|
||||||
}
|
}
|
||||||
|
@ -24,15 +24,19 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
|||||||
import io.netty.handler.codec.http.FullHttpMessage;
|
import io.netty.handler.codec.http.FullHttpMessage;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
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.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
|
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s,
|
* Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s,
|
||||||
* and {@link SpdyDataFrame}s into {@link FullHttpRequest}s and {@link FullHttpResponse}s.
|
* and {@link SpdyDataFrame}s into {@link FullHttpRequest}s and {@link FullHttpResponse}s.
|
||||||
@ -111,7 +115,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String URL = SpdyHeaders.getUrl(spdyVersion, spdySynStreamFrame);
|
String URL = spdySynStreamFrame.headers().get(PATH);
|
||||||
|
|
||||||
// If a client receives a SYN_STREAM without a 'url' header
|
// If a client receives a SYN_STREAM without a 'url' header
|
||||||
// it must reply with a RST_STREAM with error code PROTOCOL_ERROR
|
// 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);
|
createHttpResponse(spdyVersion, spdySynStreamFrame);
|
||||||
|
|
||||||
// Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers
|
// Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers
|
||||||
SpdyHttpHeaders.setStreamId(httpResponseWithEntity, streamId);
|
httpResponseWithEntity.headers().set(Names.STREAM_ID, streamId);
|
||||||
SpdyHttpHeaders.setAssociatedToStreamId(httpResponseWithEntity, associatedToStreamId);
|
httpResponseWithEntity.headers().set(Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId);
|
||||||
SpdyHttpHeaders.setPriority(httpResponseWithEntity, spdySynStreamFrame.getPriority());
|
httpResponseWithEntity.headers().set(Names.PRIORITY, spdySynStreamFrame.getPriority());
|
||||||
SpdyHttpHeaders.setUrl(httpResponseWithEntity, URL);
|
httpResponseWithEntity.headers().set(Names.URL, URL);
|
||||||
|
|
||||||
if (spdySynStreamFrame.isLast()) {
|
if (spdySynStreamFrame.isLast()) {
|
||||||
HttpHeaders.setContentLength(httpResponseWithEntity, 0);
|
HttpHeaderUtil.setContentLength(httpResponseWithEntity, 0);
|
||||||
out.add(httpResponseWithEntity);
|
out.add(httpResponseWithEntity);
|
||||||
} else {
|
} else {
|
||||||
// Response body will follow in a series of Data Frames
|
// Response body will follow in a series of Data Frames
|
||||||
putMessage(streamId, httpResponseWithEntity);
|
putMessage(streamId, httpResponseWithEntity);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) {
|
||||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||||
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||||
ctx.writeAndFlush(spdyRstStreamFrame);
|
ctx.writeAndFlush(spdyRstStreamFrame);
|
||||||
@ -161,10 +165,9 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
if (spdySynStreamFrame.isTruncated()) {
|
if (spdySynStreamFrame.isTruncated()) {
|
||||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
||||||
spdySynReplyFrame.setLast(true);
|
spdySynReplyFrame.setLast(true);
|
||||||
SpdyHeaders.setStatus(spdyVersion,
|
SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
|
||||||
spdySynReplyFrame,
|
frameHeaders.set(STATUS, HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE);
|
||||||
HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE);
|
frameHeaders.set(VERSION, HttpVersion.HTTP_1_0);
|
||||||
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
|
|
||||||
ctx.writeAndFlush(spdySynReplyFrame);
|
ctx.writeAndFlush(spdySynReplyFrame);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -173,7 +176,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
FullHttpRequest httpRequestWithEntity = createHttpRequest(spdyVersion, spdySynStreamFrame);
|
FullHttpRequest httpRequestWithEntity = createHttpRequest(spdyVersion, spdySynStreamFrame);
|
||||||
|
|
||||||
// Set the Stream-ID as a header
|
// Set the Stream-ID as a header
|
||||||
SpdyHttpHeaders.setStreamId(httpRequestWithEntity, streamId);
|
httpRequestWithEntity.headers().set(Names.STREAM_ID, streamId);
|
||||||
|
|
||||||
if (spdySynStreamFrame.isLast()) {
|
if (spdySynStreamFrame.isLast()) {
|
||||||
out.add(httpRequestWithEntity);
|
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
|
// Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
|
||||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
||||||
spdySynReplyFrame.setLast(true);
|
spdySynReplyFrame.setLast(true);
|
||||||
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST);
|
SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
|
||||||
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
|
frameHeaders.set(STATUS, HttpResponseStatus.BAD_REQUEST);
|
||||||
|
frameHeaders.set(VERSION, HttpVersion.HTTP_1_0);
|
||||||
ctx.writeAndFlush(spdySynReplyFrame);
|
ctx.writeAndFlush(spdySynReplyFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,10 +215,10 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
FullHttpResponse httpResponseWithEntity = createHttpResponse(spdyVersion, spdySynReplyFrame);
|
FullHttpResponse httpResponseWithEntity = createHttpResponse(spdyVersion, spdySynReplyFrame);
|
||||||
|
|
||||||
// Set the Stream-ID as a header
|
// Set the Stream-ID as a header
|
||||||
SpdyHttpHeaders.setStreamId(httpResponseWithEntity, streamId);
|
httpResponseWithEntity.headers().set(Names.STREAM_ID, streamId);
|
||||||
|
|
||||||
if (spdySynReplyFrame.isLast()) {
|
if (spdySynReplyFrame.isLast()) {
|
||||||
HttpHeaders.setContentLength(httpResponseWithEntity, 0);
|
HttpHeaderUtil.setContentLength(httpResponseWithEntity, 0);
|
||||||
out.add(httpResponseWithEntity);
|
out.add(httpResponseWithEntity);
|
||||||
} else {
|
} else {
|
||||||
// Response body will follow in a series of Data Frames
|
// Response body will follow in a series of Data Frames
|
||||||
@ -247,7 +251,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (spdyHeadersFrame.isLast()) {
|
if (spdyHeadersFrame.isLast()) {
|
||||||
HttpHeaders.setContentLength(fullHttpMessage, fullHttpMessage.content().readableBytes());
|
HttpHeaderUtil.setContentLength(fullHttpMessage, fullHttpMessage.content().readableBytes());
|
||||||
removeMessage(streamId);
|
removeMessage(streamId);
|
||||||
out.add(fullHttpMessage);
|
out.add(fullHttpMessage);
|
||||||
}
|
}
|
||||||
@ -275,7 +279,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen);
|
content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen);
|
||||||
|
|
||||||
if (spdyDataFrame.isLast()) {
|
if (spdyDataFrame.isLast()) {
|
||||||
HttpHeaders.setContentLength(fullHttpMessage, content.readableBytes());
|
HttpHeaderUtil.setContentLength(fullHttpMessage, content.readableBytes());
|
||||||
removeMessage(streamId);
|
removeMessage(streamId);
|
||||||
out.add(fullHttpMessage);
|
out.add(fullHttpMessage);
|
||||||
}
|
}
|
||||||
@ -291,23 +295,24 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
private static FullHttpRequest createHttpRequest(int spdyVersion, SpdyHeadersFrame requestFrame)
|
private static FullHttpRequest createHttpRequest(int spdyVersion, SpdyHeadersFrame requestFrame)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
// Create the first line of the request from the name/value pairs
|
// Create the first line of the request from the name/value pairs
|
||||||
HttpMethod method = SpdyHeaders.getMethod(spdyVersion, requestFrame);
|
SpdyHeaders headers = requestFrame.headers();
|
||||||
String url = SpdyHeaders.getUrl(spdyVersion, requestFrame);
|
HttpMethod method = HttpMethod.valueOf(headers.get(METHOD));
|
||||||
HttpVersion httpVersion = SpdyHeaders.getVersion(spdyVersion, requestFrame);
|
String url = headers.get(PATH);
|
||||||
SpdyHeaders.removeMethod(spdyVersion, requestFrame);
|
HttpVersion httpVersion = HttpVersion.valueOf(headers.get(VERSION));
|
||||||
SpdyHeaders.removeUrl(spdyVersion, requestFrame);
|
headers.remove(METHOD);
|
||||||
SpdyHeaders.removeVersion(spdyVersion, requestFrame);
|
headers.remove(PATH);
|
||||||
|
headers.remove(VERSION);
|
||||||
|
|
||||||
FullHttpRequest req = new DefaultFullHttpRequest(httpVersion, method, url);
|
FullHttpRequest req = new DefaultFullHttpRequest(httpVersion, method, url);
|
||||||
|
|
||||||
// Remove the scheme header
|
// Remove the scheme header
|
||||||
SpdyHeaders.removeScheme(spdyVersion, requestFrame);
|
headers.remove(SCHEME);
|
||||||
|
|
||||||
if (spdyVersion >= 3) {
|
if (spdyVersion >= 3) {
|
||||||
// Replace the SPDY host header with the HTTP host header
|
// Replace the SPDY host header with the HTTP host header
|
||||||
String host = SpdyHeaders.getHost(requestFrame);
|
String host = headers.get(HOST);
|
||||||
SpdyHeaders.removeHost(requestFrame);
|
headers.remove(HOST);
|
||||||
HttpHeaders.setHost(req, host);
|
req.headers().set(HttpHeaders.Names.HOST, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, String> e: requestFrame.headers()) {
|
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
|
// The Connection and Keep-Alive headers are no longer valid
|
||||||
HttpHeaders.setKeepAlive(req, true);
|
HttpHeaderUtil.setKeepAlive(req, true);
|
||||||
|
|
||||||
// Transfer-Encoding header is not valid
|
// Transfer-Encoding header is not valid
|
||||||
req.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
req.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||||
@ -323,13 +328,15 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FullHttpResponse createHttpResponse(int spdyVersion, SpdyHeadersFrame responseFrame)
|
private static FullHttpResponse createHttpResponse(
|
||||||
throws Exception {
|
int spdyVersion, SpdyHeadersFrame responseFrame) throws Exception {
|
||||||
|
|
||||||
// Create the first line of the response from the name/value pairs
|
// Create the first line of the response from the name/value pairs
|
||||||
HttpResponseStatus status = SpdyHeaders.getStatus(spdyVersion, responseFrame);
|
SpdyHeaders headers = responseFrame.headers();
|
||||||
HttpVersion version = SpdyHeaders.getVersion(spdyVersion, responseFrame);
|
HttpResponseStatus status = HttpResponseStatus.parseLine(headers.get(STATUS));
|
||||||
SpdyHeaders.removeStatus(spdyVersion, responseFrame);
|
HttpVersion version = HttpVersion.valueOf(headers.get(VERSION));
|
||||||
SpdyHeaders.removeVersion(spdyVersion, responseFrame);
|
headers.remove(STATUS);
|
||||||
|
headers.remove(VERSION);
|
||||||
|
|
||||||
FullHttpResponse res = new DefaultFullHttpResponse(version, status);
|
FullHttpResponse res = new DefaultFullHttpResponse(version, status);
|
||||||
for (Map.Entry<String, String> e: responseFrame.headers()) {
|
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
|
// The Connection and Keep-Alive headers are no longer valid
|
||||||
HttpHeaders.setKeepAlive(res, true);
|
HttpHeaderUtil.setKeepAlive(res, true);
|
||||||
|
|
||||||
// Transfer-Encoding header is not valid
|
// Transfer-Encoding header is not valid
|
||||||
res.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
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.HttpRequest;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
import io.netty.handler.codec.http.LastHttpContent;
|
import io.netty.handler.codec.http.LastHttpContent;
|
||||||
|
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpContent}s
|
* Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpContent}s
|
||||||
* into {@link SpdySynStreamFrame}s and {@link SpdySynReplyFrame}s.
|
* into {@link SpdySynStreamFrame}s and {@link SpdySynReplyFrame}s.
|
||||||
@ -202,61 +205,62 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage)
|
private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage) throws Exception {
|
||||||
throws Exception {
|
|
||||||
// Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers
|
// Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers
|
||||||
int streamID = SpdyHttpHeaders.getStreamId(httpMessage);
|
final HttpHeaders httpHeaders = httpMessage.headers();
|
||||||
int associatedToStreamId = SpdyHttpHeaders.getAssociatedToStreamId(httpMessage);
|
int streamID = httpHeaders.getInt(Names.STREAM_ID);
|
||||||
byte priority = SpdyHttpHeaders.getPriority(httpMessage);
|
int associatedToStreamId = httpHeaders.getInt(Names.ASSOCIATED_TO_STREAM_ID);
|
||||||
String URL = SpdyHttpHeaders.getUrl(httpMessage);
|
byte priority = (byte) httpHeaders.getInt(Names.PRIORITY, 0);
|
||||||
String scheme = SpdyHttpHeaders.getScheme(httpMessage);
|
String URL = httpHeaders.get(Names.URL);
|
||||||
SpdyHttpHeaders.removeStreamId(httpMessage);
|
String scheme = httpHeaders.get(Names.SCHEME);
|
||||||
SpdyHttpHeaders.removeAssociatedToStreamId(httpMessage);
|
httpHeaders.remove(Names.STREAM_ID);
|
||||||
SpdyHttpHeaders.removePriority(httpMessage);
|
httpHeaders.remove(Names.ASSOCIATED_TO_STREAM_ID);
|
||||||
SpdyHttpHeaders.removeUrl(httpMessage);
|
httpHeaders.remove(Names.PRIORITY);
|
||||||
SpdyHttpHeaders.removeScheme(httpMessage);
|
httpHeaders.remove(Names.URL);
|
||||||
|
httpHeaders.remove(Names.SCHEME);
|
||||||
|
|
||||||
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
|
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
|
||||||
// headers are not valid and MUST not be sent.
|
// headers are not valid and MUST not be sent.
|
||||||
httpMessage.headers().remove(HttpHeaders.Names.CONNECTION);
|
httpHeaders.remove(HttpHeaders.Names.CONNECTION);
|
||||||
httpMessage.headers().remove("Keep-Alive");
|
httpHeaders.remove("Keep-Alive");
|
||||||
httpMessage.headers().remove("Proxy-Connection");
|
httpHeaders.remove("Proxy-Connection");
|
||||||
httpMessage.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
httpHeaders.remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||||
|
|
||||||
SpdySynStreamFrame spdySynStreamFrame =
|
SpdySynStreamFrame spdySynStreamFrame =
|
||||||
new DefaultSpdySynStreamFrame(streamID, associatedToStreamId, priority);
|
new DefaultSpdySynStreamFrame(streamID, associatedToStreamId, priority);
|
||||||
|
|
||||||
// Unfold the first line of the message into name/value pairs
|
// Unfold the first line of the message into name/value pairs
|
||||||
|
SpdyHeaders frameHeaders = spdySynStreamFrame.headers();
|
||||||
if (httpMessage instanceof FullHttpRequest) {
|
if (httpMessage instanceof FullHttpRequest) {
|
||||||
HttpRequest httpRequest = (HttpRequest) httpMessage;
|
HttpRequest httpRequest = (HttpRequest) httpMessage;
|
||||||
SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod());
|
frameHeaders.set(METHOD, httpRequest.getMethod());
|
||||||
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri());
|
frameHeaders.set(PATH, httpRequest.getUri());
|
||||||
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
|
frameHeaders.set(VERSION, httpMessage.getProtocolVersion());
|
||||||
}
|
}
|
||||||
if (httpMessage instanceof HttpResponse) {
|
if (httpMessage instanceof HttpResponse) {
|
||||||
HttpResponse httpResponse = (HttpResponse) httpMessage;
|
HttpResponse httpResponse = (HttpResponse) httpMessage;
|
||||||
SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus());
|
frameHeaders.set(STATUS, httpResponse.getStatus());
|
||||||
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL);
|
frameHeaders.set(PATH, URL);
|
||||||
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
|
frameHeaders.set(VERSION, httpMessage.getProtocolVersion());
|
||||||
spdySynStreamFrame.setUnidirectional(true);
|
spdySynStreamFrame.setUnidirectional(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the HTTP host header with the SPDY host header
|
// Replace the HTTP host header with the SPDY host header
|
||||||
if (spdyVersion >= 3) {
|
if (spdyVersion >= 3) {
|
||||||
String host = HttpHeaders.getHost(httpMessage);
|
CharSequence host = httpHeaders.getUnconverted(HttpHeaders.Names.HOST);
|
||||||
httpMessage.headers().remove(HttpHeaders.Names.HOST);
|
httpHeaders.remove(HttpHeaders.Names.HOST);
|
||||||
SpdyHeaders.setHost(spdySynStreamFrame, host);
|
frameHeaders.set(HOST, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the SPDY scheme header
|
// Set the SPDY scheme header
|
||||||
if (scheme == null) {
|
if (scheme == null) {
|
||||||
scheme = "https";
|
scheme = "https";
|
||||||
}
|
}
|
||||||
SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme);
|
frameHeaders.set(SCHEME, scheme);
|
||||||
|
|
||||||
// Transfer the remaining HTTP headers
|
// Transfer the remaining HTTP headers
|
||||||
for (Map.Entry<String, String> entry: httpMessage.headers()) {
|
for (Map.Entry<String, String> entry: httpHeaders) {
|
||||||
spdySynStreamFrame.headers().add(entry.getKey(), entry.getValue());
|
frameHeaders.add(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
currentStreamId = spdySynStreamFrame.getStreamId();
|
currentStreamId = spdySynStreamFrame.getStreamId();
|
||||||
spdySynStreamFrame.setLast(isLast(httpMessage));
|
spdySynStreamFrame.setLast(isLast(httpMessage));
|
||||||
@ -264,27 +268,27 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
|||||||
return spdySynStreamFrame;
|
return spdySynStreamFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse)
|
private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse) throws Exception {
|
||||||
throws Exception {
|
|
||||||
// Get the Stream-ID from the headers
|
// Get the Stream-ID from the headers
|
||||||
int streamID = SpdyHttpHeaders.getStreamId(httpResponse);
|
final HttpHeaders httpHeaders = httpResponse.headers();
|
||||||
SpdyHttpHeaders.removeStreamId(httpResponse);
|
int streamID = httpHeaders.getInt(Names.STREAM_ID);
|
||||||
|
httpHeaders.remove(Names.STREAM_ID);
|
||||||
|
|
||||||
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
|
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
|
||||||
// headers are not valid and MUST not be sent.
|
// headers are not valid and MUST not be sent.
|
||||||
httpResponse.headers().remove(HttpHeaders.Names.CONNECTION);
|
httpHeaders.remove(HttpHeaders.Names.CONNECTION);
|
||||||
httpResponse.headers().remove("Keep-Alive");
|
httpHeaders.remove("Keep-Alive");
|
||||||
httpResponse.headers().remove("Proxy-Connection");
|
httpHeaders.remove("Proxy-Connection");
|
||||||
httpResponse.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
httpHeaders.remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||||
|
|
||||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
||||||
|
SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
|
||||||
// Unfold the first line of the response into name/value pairs
|
// Unfold the first line of the response into name/value pairs
|
||||||
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus());
|
frameHeaders.set(STATUS, httpResponse.getStatus());
|
||||||
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion());
|
frameHeaders.set(VERSION, httpResponse.getProtocolVersion());
|
||||||
|
|
||||||
// Transfer the remaining HTTP headers
|
// 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());
|
spdySynReplyFrame.headers().add(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,12 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.spdy;
|
package io.netty.handler.codec.spdy;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpHeaders;
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.http.HttpMessage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the constants for the header names and the utility methods
|
* Provides the constants for the header names used by the {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}.
|
||||||
* used by the {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}.
|
|
||||||
*/
|
*/
|
||||||
public final class SpdyHttpHeaders {
|
public final class SpdyHttpHeaders {
|
||||||
|
|
||||||
@ -31,138 +29,26 @@ public final class SpdyHttpHeaders {
|
|||||||
/**
|
/**
|
||||||
* {@code "X-SPDY-Stream-ID"}
|
* {@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"}
|
* {@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"}
|
* {@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"}
|
* {@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"}
|
* {@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 Names() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpdyHttpHeaders() {
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package io.netty.handler.codec.spdy;
|
|||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.MessageToMessageCodec;
|
import io.netty.handler.codec.MessageToMessageCodec;
|
||||||
import io.netty.handler.codec.http.HttpMessage;
|
import io.netty.handler.codec.http.HttpMessage;
|
||||||
|
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
|
||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -43,7 +44,7 @@ public class SpdyHttpResponseStreamIdHandler extends
|
|||||||
protected void encode(ChannelHandlerContext ctx, HttpMessage msg, List<Object> out) throws Exception {
|
protected void encode(ChannelHandlerContext ctx, HttpMessage msg, List<Object> out) throws Exception {
|
||||||
Integer id = ids.poll();
|
Integer id = ids.poll();
|
||||||
if (id != null && id.intValue() != NO_ID && !msg.headers().contains(SpdyHttpHeaders.Names.STREAM_ID)) {
|
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));
|
out.add(ReferenceCountUtil.retain(msg));
|
||||||
@ -56,7 +57,7 @@ public class SpdyHttpResponseStreamIdHandler extends
|
|||||||
if (!contains) {
|
if (!contains) {
|
||||||
ids.add(NO_ID);
|
ids.add(NO_ID);
|
||||||
} else {
|
} else {
|
||||||
ids.add(SpdyHttpHeaders.getStreamId((HttpMessage) msg));
|
ids.add(((HttpMessage) msg).headers().getInt(Names.STREAM_ID));
|
||||||
}
|
}
|
||||||
} else if (msg instanceof SpdyRstStreamFrame) {
|
} else if (msg instanceof SpdyRstStreamFrame) {
|
||||||
ids.remove(((SpdyRstStreamFrame) msg).getStreamId());
|
ids.remove(((SpdyRstStreamFrame) msg).getStreamId());
|
||||||
|
@ -15,22 +15,23 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http;
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import io.netty.handler.codec.AsciiString;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class HttpHeadersTest {
|
public class HttpHeaderUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRemoveTransferEncodingIgnoreCase() {
|
public void testRemoveTransferEncodingIgnoreCase() {
|
||||||
HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
|
HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
|
||||||
message.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, "Chunked");
|
message.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, "Chunked");
|
||||||
HttpHeaders.removeTransferEncodingChunked(message);
|
assertFalse(message.headers().isEmpty());
|
||||||
Assert.assertTrue(message.headers().isEmpty());
|
HttpHeaderUtil.setTransferEncodingChunked(message, false);
|
||||||
|
assertTrue(message.headers().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for https://github.com/netty/netty/issues/1690
|
// Test for https://github.com/netty/netty/issues/1690
|
||||||
@ -40,19 +41,19 @@ public class HttpHeadersTest {
|
|||||||
headers.add("Foo", "1");
|
headers.add("Foo", "1");
|
||||||
headers.add("Foo", "2");
|
headers.add("Foo", "2");
|
||||||
|
|
||||||
Assert.assertEquals("1", headers.get("Foo"));
|
assertEquals("1", headers.get("Foo"));
|
||||||
|
|
||||||
List<String> values = headers.getAll("Foo");
|
List<String> values = headers.getAll("Foo");
|
||||||
Assert.assertEquals(2, values.size());
|
assertEquals(2, values.size());
|
||||||
Assert.assertEquals("1", values.get(0));
|
assertEquals("1", values.get(0));
|
||||||
Assert.assertEquals("2", values.get(1));
|
assertEquals("2", values.get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEquansIgnoreCase() {
|
public void testEquansIgnoreCase() {
|
||||||
assertThat(HttpHeaders.equalsIgnoreCase(null, null), is(true));
|
assertThat(AsciiString.equalsIgnoreCase(null, null), is(true));
|
||||||
assertThat(HttpHeaders.equalsIgnoreCase(null, "foo"), is(false));
|
assertThat(AsciiString.equalsIgnoreCase(null, "foo"), is(false));
|
||||||
assertThat(HttpHeaders.equalsIgnoreCase("bar", null), is(false));
|
assertThat(AsciiString.equalsIgnoreCase("bar", null), is(false));
|
||||||
assertThat(HttpHeaders.equalsIgnoreCase("FoO", "fOo"), is(true));
|
assertThat(AsciiString.equalsIgnoreCase("FoO", "fOo"), is(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -41,7 +41,7 @@ public class HttpObjectAggregatorTest {
|
|||||||
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
|
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
|
||||||
|
|
||||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost");
|
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 chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
||||||
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
||||||
HttpContent chunk3 = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
|
HttpContent chunk3 = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
|
||||||
@ -56,7 +56,7 @@ public class HttpObjectAggregatorTest {
|
|||||||
assertNotNull(aggratedMessage);
|
assertNotNull(aggratedMessage);
|
||||||
|
|
||||||
assertEquals(chunk1.content().readableBytes() + chunk2.content().readableBytes(),
|
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-Test"), Boolean.TRUE.toString());
|
||||||
checkContentBuffer(aggratedMessage);
|
checkContentBuffer(aggratedMessage);
|
||||||
assertNull(embedder.readInbound());
|
assertNull(embedder.readInbound());
|
||||||
@ -79,8 +79,8 @@ public class HttpObjectAggregatorTest {
|
|||||||
HttpObjectAggregator aggr = new HttpObjectAggregator(1024 * 1024);
|
HttpObjectAggregator aggr = new HttpObjectAggregator(1024 * 1024);
|
||||||
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
|
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
|
||||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost");
|
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost");
|
||||||
HttpHeaders.setHeader(message, "X-Test", true);
|
message.headers().set("X-Test", true);
|
||||||
HttpHeaders.setTransferEncodingChunked(message);
|
HttpHeaderUtil.setTransferEncodingChunked(message, true);
|
||||||
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
||||||
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
||||||
LastHttpContent trailer = new DefaultLastHttpContent();
|
LastHttpContent trailer = new DefaultLastHttpContent();
|
||||||
@ -97,7 +97,7 @@ public class HttpObjectAggregatorTest {
|
|||||||
assertNotNull(aggratedMessage);
|
assertNotNull(aggratedMessage);
|
||||||
|
|
||||||
assertEquals(chunk1.content().readableBytes() + chunk2.content().readableBytes(),
|
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-Test"), Boolean.TRUE.toString());
|
||||||
assertEquals(aggratedMessage.headers().get("X-Trailer"), Boolean.TRUE.toString());
|
assertEquals(aggratedMessage.headers().get("X-Trailer"), Boolean.TRUE.toString());
|
||||||
checkContentBuffer(aggratedMessage);
|
checkContentBuffer(aggratedMessage);
|
||||||
@ -135,14 +135,14 @@ public class HttpObjectAggregatorTest {
|
|||||||
public void testOversizedRequestWithoutKeepAlive() {
|
public void testOversizedRequestWithoutKeepAlive() {
|
||||||
// send a HTTP/1.0 request with no keep-alive header
|
// send a HTTP/1.0 request with no keep-alive header
|
||||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.PUT, "http://localhost");
|
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.PUT, "http://localhost");
|
||||||
HttpHeaders.setContentLength(message, 5);
|
HttpHeaderUtil.setContentLength(message, 5);
|
||||||
checkOversizedRequest(message);
|
checkOversizedRequest(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOversizedRequestWithContentLength() {
|
public void testOversizedRequestWithContentLength() {
|
||||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
|
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
|
||||||
HttpHeaders.setContentLength(message, 5);
|
HttpHeaderUtil.setContentLength(message, 5);
|
||||||
checkOversizedRequest(message);
|
checkOversizedRequest(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,8 +152,8 @@ public class HttpObjectAggregatorTest {
|
|||||||
|
|
||||||
// send an oversized request with 100 continue
|
// send an oversized request with 100 continue
|
||||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
|
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
|
||||||
HttpHeaders.set100ContinueExpected(message);
|
HttpHeaderUtil.set100ContinueExpected(message, true);
|
||||||
HttpHeaders.setContentLength(message, 16);
|
HttpHeaderUtil.setContentLength(message, 16);
|
||||||
|
|
||||||
HttpContent chunk1 = releaseLater(new DefaultHttpContent(Unpooled.copiedBuffer("some", CharsetUtil.US_ASCII)));
|
HttpContent chunk1 = releaseLater(new DefaultHttpContent(Unpooled.copiedBuffer("some", CharsetUtil.US_ASCII)));
|
||||||
HttpContent chunk2 = releaseLater(new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII)));
|
HttpContent chunk2 = releaseLater(new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII)));
|
||||||
@ -185,9 +185,9 @@ public class HttpObjectAggregatorTest {
|
|||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
chunk2.content().readableBytes() + chunk3.content().readableBytes(),
|
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();
|
fullMsg.release();
|
||||||
assertFalse(embedder.finish());
|
assertFalse(embedder.finish());
|
||||||
@ -280,8 +280,8 @@ public class HttpObjectAggregatorTest {
|
|||||||
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
|
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
|
||||||
|
|
||||||
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
|
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
|
||||||
HttpHeaders.setHeader(message, "X-Test", true);
|
message.headers().set("X-Test", true);
|
||||||
HttpHeaders.setHeader(message, "Transfer-Encoding", "Chunked");
|
message.headers().set("Transfer-Encoding", "Chunked");
|
||||||
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
|
||||||
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
|
||||||
HttpContent chunk3 = LastHttpContent.EMPTY_LAST_CONTENT;
|
HttpContent chunk3 = LastHttpContent.EMPTY_LAST_CONTENT;
|
||||||
@ -296,7 +296,7 @@ public class HttpObjectAggregatorTest {
|
|||||||
assertNotNull(aggratedMessage);
|
assertNotNull(aggratedMessage);
|
||||||
|
|
||||||
assertEquals(chunk1.content().readableBytes() + chunk2.content().readableBytes(),
|
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-Test"), Boolean.TRUE.toString());
|
||||||
checkContentBuffer(aggratedMessage);
|
checkContentBuffer(aggratedMessage);
|
||||||
assertNull(embedder.readInbound());
|
assertNull(embedder.readInbound());
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http.cors;
|
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.HttpHeaders.Names;
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -115,7 +114,7 @@ public class CorsConfigTest {
|
|||||||
@Test
|
@Test
|
||||||
public void emptyPreflightResponseHeaders() {
|
public void emptyPreflightResponseHeaders() {
|
||||||
final CorsConfig cors = withAnyOrigin().noPreflightResponseHeaders().build();
|
final CorsConfig cors = withAnyOrigin().noPreflightResponseHeaders().build();
|
||||||
assertThat(cors.preflightResponseHeaders(), equalTo(HttpHeaders.EMPTY_HEADERS));
|
assertThat(cors.preflightResponseHeaders().size(), equalTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test (expected = IllegalArgumentException.class)
|
@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 final StompCommand command;
|
||||||
protected DecoderResult decoderResult = DecoderResult.SUCCESS;
|
protected DecoderResult decoderResult = DecoderResult.SUCCESS;
|
||||||
protected final StompHeaders headers = new StompHeaders();
|
protected final StompHeaders headers = new DefaultStompHeaders();
|
||||||
|
|
||||||
public DefaultStompHeadersSubframe(StompCommand command) {
|
public DefaultStompHeadersSubframe(StompCommand command) {
|
||||||
if (command == null) {
|
if (command == null) {
|
||||||
|
@ -15,93 +15,63 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.stomp;
|
package io.netty.handler.codec.stomp;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import io.netty.handler.codec.AsciiString;
|
||||||
import java.util.Arrays;
|
import io.netty.handler.codec.TextHeaderProcessor;
|
||||||
import java.util.LinkedHashMap;
|
import io.netty.handler.codec.TextHeaders;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the constants for the standard STOMP header names and values and
|
* The multimap data structure for the STOMP header names and values. It also provides the constants for the standard
|
||||||
* commonly used utility methods that accesses an {@link StompHeadersSubframe}.
|
* STOMP header names and values.
|
||||||
*/
|
*/
|
||||||
public class StompHeaders {
|
public interface StompHeaders extends TextHeaders {
|
||||||
|
|
||||||
public static final String ACCEPT_VERSION = "accept-version";
|
AsciiString ACCEPT_VERSION = new AsciiString("accept-version");
|
||||||
public static final String HOST = "host";
|
AsciiString HOST = new AsciiString("host");
|
||||||
public static final String LOGIN = "login";
|
AsciiString LOGIN = new AsciiString("login");
|
||||||
public static final String PASSCODE = "passcode";
|
AsciiString PASSCODE = new AsciiString("passcode");
|
||||||
public static final String HEART_BEAT = "heart-beat";
|
AsciiString HEART_BEAT = new AsciiString("heart-beat");
|
||||||
public static final String VERSION = "version";
|
AsciiString VERSION = new AsciiString("version");
|
||||||
public static final String SESSION = "session";
|
AsciiString SESSION = new AsciiString("session");
|
||||||
public static final String SERVER = "server";
|
AsciiString SERVER = new AsciiString("server");
|
||||||
public static final String DESTINATION = "destination";
|
AsciiString DESTINATION = new AsciiString("destination");
|
||||||
public static final String ID = "id";
|
AsciiString ID = new AsciiString("id");
|
||||||
public static final String ACK = "ack";
|
AsciiString ACK = new AsciiString("ack");
|
||||||
public static final String TRANSACTION = "transaction";
|
AsciiString TRANSACTION = new AsciiString("transaction");
|
||||||
public static final String RECEIPT = "receipt";
|
AsciiString RECEIPT = new AsciiString("receipt");
|
||||||
public static final String MESSAGE_ID = "message-id";
|
AsciiString MESSAGE_ID = new AsciiString("message-id");
|
||||||
public static final String SUBSCRIPTION = "subscription";
|
AsciiString SUBSCRIPTION = new AsciiString("subscription");
|
||||||
public static final String RECEIPT_ID = "receipt-id";
|
AsciiString RECEIPT_ID = new AsciiString("receipt-id");
|
||||||
public static final String MESSAGE = "message";
|
AsciiString MESSAGE = new AsciiString("message");
|
||||||
public static final String CONTENT_LENGTH = "content-length";
|
AsciiString CONTENT_LENGTH = new AsciiString("content-length");
|
||||||
public static final String CONTENT_TYPE = "content-type";
|
AsciiString CONTENT_TYPE = new AsciiString("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();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
StompHeaders add(CharSequence name, Object value);
|
||||||
return "StompHeaders{" +
|
|
||||||
headers +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(StompHeaders headers) {
|
@Override
|
||||||
for (String key: headers.keySet()) {
|
StompHeaders add(CharSequence name, Iterable<?> values);
|
||||||
List<String> values = headers.getAll(key);
|
|
||||||
this.headers.put(key, 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.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelPipeline;
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.MessageAggregator;
|
import io.netty.handler.codec.MessageAggregator;
|
||||||
import io.netty.handler.codec.TooLongFrameException;
|
import io.netty.handler.codec.TooLongFrameException;
|
||||||
|
|
||||||
@ -64,12 +65,17 @@ public class StompSubframeAggregator
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean hasContentLength(StompHeadersSubframe start) throws Exception {
|
protected boolean hasContentLength(StompHeadersSubframe start) throws Exception {
|
||||||
return start.headers().has(StompHeaders.CONTENT_LENGTH);
|
return start.headers().contains(StompHeaders.CONTENT_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected long contentLength(StompHeadersSubframe start) throws Exception {
|
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
|
@Override
|
||||||
|
@ -18,6 +18,7 @@ package io.netty.handler.codec.stomp;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.DecoderException;
|
import io.netty.handler.codec.DecoderException;
|
||||||
import io.netty.handler.codec.DecoderResult;
|
import io.netty.handler.codec.DecoderResult;
|
||||||
import io.netty.handler.codec.ReplayingDecoder;
|
import io.netty.handler.codec.ReplayingDecoder;
|
||||||
@ -199,7 +200,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
long contentLength = -1;
|
long contentLength = -1;
|
||||||
if (headers.has(StompHeaders.CONTENT_LENGTH)) {
|
if (headers.contains(StompHeaders.CONTENT_LENGTH)) {
|
||||||
contentLength = getContentLength(headers, 0);
|
contentLength = getContentLength(headers, 0);
|
||||||
} else {
|
} else {
|
||||||
int globalIndex = indexOf(buffer, buffer.readerIndex(),
|
int globalIndex = indexOf(buffer, buffer.readerIndex(),
|
||||||
@ -219,10 +220,14 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static long getContentLength(StompHeaders headers, long defaultValue) {
|
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) {
|
if (contentLength != null) {
|
||||||
try {
|
try {
|
||||||
return Long.parseLong(contentLength);
|
if (contentLength instanceof AsciiString) {
|
||||||
|
return ((AsciiString) contentLength).parseLong();
|
||||||
|
} else {
|
||||||
|
return Long.parseLong(contentLength.toString());
|
||||||
|
}
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,9 @@ package io.netty.handler.codec.stomp;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
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.handler.codec.MessageToMessageEncoder;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
@ -61,18 +64,9 @@ public class StompSubframeEncoder extends MessageToMessageEncoder<StompSubframe>
|
|||||||
ByteBuf buf = ctx.alloc().buffer();
|
ByteBuf buf = ctx.alloc().buffer();
|
||||||
|
|
||||||
buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII));
|
buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII));
|
||||||
buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF);
|
buf.writeByte(StompConstants.LF);
|
||||||
|
frame.headers().forEachEntry(new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF));
|
||||||
StompHeaders headers = frame.headers();
|
buf.writeByte(StompConstants.LF);
|
||||||
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);
|
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,29 +17,29 @@ package io.netty.handler.codec.stomp;
|
|||||||
|
|
||||||
public final class StompTestConstants {
|
public final class StompTestConstants {
|
||||||
public static final String CONNECT_FRAME =
|
public static final String CONNECT_FRAME =
|
||||||
"CONNECT\r\n" +
|
"CONNECT\n" +
|
||||||
"host:stomp.github.org\r\n" +
|
"host:stomp.github.org\n" +
|
||||||
"accept-version:1.1,1.2\r\n" +
|
"accept-version:1.1,1.2\n" +
|
||||||
"\r\n" +
|
'\n' +
|
||||||
'\0';
|
'\0';
|
||||||
public static final String CONNECTED_FRAME =
|
public static final String CONNECTED_FRAME =
|
||||||
"CONNECTED\r\n" +
|
"CONNECTED\n" +
|
||||||
"version:1.2\n" +
|
"version:1.2\n" +
|
||||||
"\r\n" +
|
'\n' +
|
||||||
"\0\n";
|
"\0\n";
|
||||||
public static final String SEND_FRAME_1 =
|
public static final String SEND_FRAME_1 =
|
||||||
"SEND\r\n" +
|
"SEND\n" +
|
||||||
"destination:/queue/a\n" +
|
"destination:/queue/a\n" +
|
||||||
"content-type:text/plain\n" +
|
"content-type:text/plain\n" +
|
||||||
"\r\n" +
|
'\n' +
|
||||||
"hello, queue a!" +
|
"hello, queue a!" +
|
||||||
"\0\n";
|
"\0\n";
|
||||||
public static final String SEND_FRAME_2 =
|
public static final String SEND_FRAME_2 =
|
||||||
"SEND\r\n" +
|
"SEND\n" +
|
||||||
"destination:/queue/a\n" +
|
"destination:/queue/a\n" +
|
||||||
"content-type:text/plain\n" +
|
"content-type:text/plain\n" +
|
||||||
"content-length:17\n" +
|
"content-length:17\n" +
|
||||||
"\r\n" +
|
'\n' +
|
||||||
"hello, queue a!!!" +
|
"hello, queue a!!!" +
|
||||||
"\0\n";
|
"\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 final class EmptyArrays {
|
||||||
|
|
||||||
public static final byte[] EMPTY_BYTES = new byte[0];
|
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 boolean[] EMPTY_BOOLEANS = new boolean[0];
|
||||||
public static final double[] EMPTY_DOUBLES = new double[0];
|
public static final double[] EMPTY_DOUBLES = new double[0];
|
||||||
public static final float[] EMPTY_FLOATS = new float[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.DefaultHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
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.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
@ -52,7 +53,6 @@ import java.util.TimeZone;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
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.HttpMethod.*;
|
||||||
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
||||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||||
@ -174,10 +174,10 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
|
|||||||
long fileLength = raf.length();
|
long fileLength = raf.length();
|
||||||
|
|
||||||
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
|
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
|
||||||
setContentLength(response, fileLength);
|
HttpHeaderUtil.setContentLength(response, fileLength);
|
||||||
setContentTypeHeader(response, file);
|
setContentTypeHeader(response, file);
|
||||||
setDateAndCacheHeaders(response, file);
|
setDateAndCacheHeaders(response, file);
|
||||||
if (isKeepAlive(request)) {
|
if (HttpHeaderUtil.isKeepAlive(request)) {
|
||||||
response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
|
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);
|
ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
|
||||||
|
|
||||||
// Decide whether to close the connection or not.
|
// 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.
|
// Close the connection when the whole content is written out.
|
||||||
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
|
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,15 @@ import io.netty.channel.ChannelHandlerAdapter;
|
|||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
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 io.netty.handler.codec.http.HttpRequest;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
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.HttpResponseStatus.*;
|
||||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||||
|
|
||||||
|
|
||||||
public class HttpHelloWorldServerHandler extends ChannelHandlerAdapter {
|
public class HttpHelloWorldServerHandler extends ChannelHandlerAdapter {
|
||||||
private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' };
|
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) {
|
if (msg instanceof HttpRequest) {
|
||||||
HttpRequest req = (HttpRequest) msg;
|
HttpRequest req = (HttpRequest) msg;
|
||||||
|
|
||||||
if (is100ContinueExpected(req)) {
|
if (HttpHeaderUtil.is100ContinueExpected(req)) {
|
||||||
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
|
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));
|
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT));
|
||||||
response.headers().set(CONTENT_TYPE, "text/plain");
|
response.headers().set(CONTENT_TYPE, "text/plain");
|
||||||
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
|
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.ChannelHandlerContext;
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
import io.netty.handler.codec.http.HttpContent;
|
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.HttpObject;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
import io.netty.handler.codec.http.LastHttpContent;
|
import io.netty.handler.codec.http.LastHttpContent;
|
||||||
@ -44,7 +44,7 @@ public class HttpSnoopClientHandler extends SimpleChannelInboundHandler<HttpObje
|
|||||||
System.err.println();
|
System.err.println();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HttpHeaders.isTransferEncodingChunked(response)) {
|
if (HttpHeaderUtil.isTransferEncodingChunked(response)) {
|
||||||
System.err.println("CHUNKED CONTENT {");
|
System.err.println("CHUNKED CONTENT {");
|
||||||
} else {
|
} else {
|
||||||
System.err.println("CONTENT {");
|
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.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
import io.netty.handler.codec.http.HttpContent;
|
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.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpObject;
|
import io.netty.handler.codec.http.HttpObject;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
@ -40,7 +41,6 @@ import java.util.Map.Entry;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
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.HttpResponseStatus.*;
|
||||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object>
|
|||||||
if (msg instanceof HttpRequest) {
|
if (msg instanceof HttpRequest) {
|
||||||
HttpRequest request = this.request = (HttpRequest) msg;
|
HttpRequest request = this.request = (HttpRequest) msg;
|
||||||
|
|
||||||
if (is100ContinueExpected(request)) {
|
if (HttpHeaderUtil.is100ContinueExpected(request)) {
|
||||||
send100Continue(ctx);
|
send100Continue(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object>
|
|||||||
buf.append("===================================\r\n");
|
buf.append("===================================\r\n");
|
||||||
|
|
||||||
buf.append("VERSION: ").append(request.getProtocolVersion()).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");
|
buf.append("REQUEST_URI: ").append(request.getUri()).append("\r\n\r\n");
|
||||||
|
|
||||||
HttpHeaders headers = request.headers();
|
HttpHeaders headers = request.headers();
|
||||||
@ -145,7 +145,7 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object>
|
|||||||
|
|
||||||
private boolean writeResponse(HttpObject currentObj, ChannelHandlerContext ctx) {
|
private boolean writeResponse(HttpObject currentObj, ChannelHandlerContext ctx) {
|
||||||
// Decide whether to close the connection or not.
|
// Decide whether to close the connection or not.
|
||||||
boolean keepAlive = isKeepAlive(request);
|
boolean keepAlive = HttpHeaderUtil.isKeepAlive(request);
|
||||||
// Build the response object.
|
// Build the response object.
|
||||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||||
HTTP_1_1, currentObj.decoderResult().isSuccess()? OK : BAD_REQUEST,
|
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.ChannelHandlerContext;
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
import io.netty.handler.codec.http.HttpContent;
|
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.HttpObject;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
import io.netty.handler.codec.http.LastHttpContent;
|
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;
|
readingChunks = true;
|
||||||
System.err.println("CHUNKED CONTENT {");
|
System.err.println("CHUNKED CONTENT {");
|
||||||
} else {
|
} 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.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
import io.netty.handler.codec.http.HttpContent;
|
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.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import io.netty.handler.codec.http.HttpObject;
|
import io.netty.handler.codec.http.HttpObject;
|
||||||
@ -157,7 +158,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
readingChunks = HttpHeaders.isTransferEncodingChunked(request);
|
readingChunks = HttpHeaderUtil.isTransferEncodingChunked(request);
|
||||||
responseContent.append("Is Chunked: " + readingChunks + "\r\n");
|
responseContent.append("Is Chunked: " + readingChunks + "\r\n");
|
||||||
responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n");
|
responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n");
|
||||||
if (readingChunks) {
|
if (readingChunks) {
|
||||||
|
@ -24,6 +24,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
|
|||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
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.CloseWebSocketFrame;
|
||||||
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
|
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
|
||||||
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
|
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 io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
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.HttpMethod.*;
|
||||||
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
||||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
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);
|
FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content);
|
||||||
|
|
||||||
res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
|
res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
|
||||||
setContentLength(res, content.readableBytes());
|
HttpHeaderUtil.setContentLength(res, content.readableBytes());
|
||||||
|
|
||||||
sendHttpResponse(ctx, req, res);
|
sendHttpResponse(ctx, req, res);
|
||||||
return;
|
return;
|
||||||
@ -132,12 +132,12 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
|
|||||||
ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
|
ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
|
||||||
res.content().writeBytes(buf);
|
res.content().writeBytes(buf);
|
||||||
buf.release();
|
buf.release();
|
||||||
setContentLength(res, res.content().readableBytes());
|
HttpHeaderUtil.setContentLength(res, res.content().readableBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the response and close the connection if necessary.
|
// Send the response and close the connection if necessary.
|
||||||
ChannelFuture f = ctx.channel().writeAndFlush(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);
|
f.addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
||||||
@ -35,10 +36,10 @@ public class HelloWorldHttp1Handler extends SimpleChannelInboundHandler<HttpRequ
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void messageReceived(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
|
public void messageReceived(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
|
||||||
if (is100ContinueExpected(req)) {
|
if (HttpHeaderUtil.is100ContinueExpected(req)) {
|
||||||
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
|
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
|
||||||
}
|
}
|
||||||
boolean keepAlive = isKeepAlive(req);
|
boolean keepAlive = HttpHeaderUtil.isKeepAlive(req);
|
||||||
|
|
||||||
ByteBuf content = ctx.alloc().buffer();
|
ByteBuf content = ctx.alloc().buffer();
|
||||||
content.writeBytes(HelloWorldHttp2Handler.RESPONSE_BYTES);
|
content.writeBytes(HelloWorldHttp2Handler.RESPONSE_BYTES);
|
||||||
|
@ -20,7 +20,7 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
import io.netty.example.http.snoop.HttpSnoopClientHandler;
|
import io.netty.example.http.snoop.HttpSnoopClientHandler;
|
||||||
import io.netty.handler.codec.http.HttpContent;
|
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.HttpObject;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
import io.netty.handler.codec.http.LastHttpContent;
|
import io.netty.handler.codec.http.LastHttpContent;
|
||||||
@ -55,7 +55,7 @@ public class HttpResponseClientHandler extends SimpleChannelInboundHandler<HttpO
|
|||||||
System.out.println();
|
System.out.println();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HttpHeaders.isTransferEncodingChunked(response)) {
|
if (HttpHeaderUtil.isTransferEncodingChunked(response)) {
|
||||||
System.out.println("CHUNKED CONTENT {");
|
System.out.println("CHUNKED CONTENT {");
|
||||||
} else {
|
} else {
|
||||||
System.out.println("CONTENT {");
|
System.out.println("CONTENT {");
|
||||||
|
@ -20,6 +20,7 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
import io.netty.handler.codec.http.HttpMessage;
|
import io.netty.handler.codec.http.HttpMessage;
|
||||||
import io.netty.handler.codec.spdy.SpdyHttpHeaders;
|
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.
|
* 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)) {
|
if (acceptOutboundMessage(msg)) {
|
||||||
HttpMessage httpMsg = (HttpMessage) msg;
|
HttpMessage httpMsg = (HttpMessage) msg;
|
||||||
if (!httpMsg.headers().contains(SpdyHttpHeaders.Names.STREAM_ID)) {
|
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
|
// Client stream IDs are always odd
|
||||||
currentStreamId += 2;
|
currentStreamId += 2;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaderUtil;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
@ -42,10 +43,10 @@ public class SpdyServerHandler extends SimpleChannelInboundHandler<Object> {
|
|||||||
if (msg instanceof HttpRequest) {
|
if (msg instanceof HttpRequest) {
|
||||||
HttpRequest req = (HttpRequest) msg;
|
HttpRequest req = (HttpRequest) msg;
|
||||||
|
|
||||||
if (is100ContinueExpected(req)) {
|
if (HttpHeaderUtil.is100ContinueExpected(req)) {
|
||||||
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
|
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);
|
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.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
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.BinaryWebSocketFrame;
|
||||||
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
|
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
|
||||||
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
|
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);
|
ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
|
||||||
res.content().writeBytes(buf);
|
res.content().writeBytes(buf);
|
||||||
buf.release();
|
buf.release();
|
||||||
setContentLength(res, res.content().readableBytes());
|
HttpHeaderUtil.setContentLength(res, res.content().readableBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the response and close the connection if necessary.
|
// Send the response and close the connection if necessary.
|
||||||
ChannelFuture f = ctx.channel().writeAndFlush(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);
|
f.addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user