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:
Trustin Lee 2014-06-05 18:31:04 +09:00
parent 7d37af5dfb
commit de2872f7f7
68 changed files with 4459 additions and 2712 deletions

View File

@ -64,6 +64,14 @@ Bloch of Google, Inc:
* LICENSE:
* license/LICENSE.deque.txt (Public Domain)
This product contains a modified portion of 'Apache Harmony', an open source
Java SE, which can be obtained at:
* LICENSE:
* license/LICENSE.harmony.txt (Apache License 2.0)
* HOMEPAGE:
* http://archive.apache.org/dist/harmony/
This product contains a modified version of Roland Kuhn's ASL2
AbstractNodeQueue, which is based on Dmitriy Vyukov's non-intrusive MPSC queue.
It can be obtained at:
@ -137,3 +145,4 @@ can be obtained at:
* license/LICENSE.log4j.txt (Apache License 2.0)
* HOMEPAGE:
* http://logging.apache.org/log4j/

View File

@ -15,30 +15,36 @@
*/
package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.DefaultTextHeaders;
import io.netty.handler.codec.TextHeaderProcessor;
import io.netty.handler.codec.TextHeaders;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
public class DefaultHttpHeaders extends HttpHeaders {
public class DefaultHttpHeaders extends DefaultTextHeaders implements HttpHeaders {
private static final int BUCKET_SIZE = 17;
private static final int HIGHEST_INVALID_NAME_CHAR_MASK = ~63;
private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15;
private static int index(int hash) {
return hash % BUCKET_SIZE;
/**
* A look-up table used for checking if a character in a header name is prohibited.
*/
private static final byte[] LOOKUP_TABLE = new byte[~HIGHEST_INVALID_NAME_CHAR_MASK + 1];
static {
LOOKUP_TABLE['\t'] = -1;
LOOKUP_TABLE['\n'] = -1;
LOOKUP_TABLE[0x0b] = -1;
LOOKUP_TABLE['\f'] = -1;
LOOKUP_TABLE[' '] = -1;
LOOKUP_TABLE[','] = -1;
LOOKUP_TABLE[':'] = -1;
LOOKUP_TABLE[';'] = -1;
LOOKUP_TABLE['='] = -1;
}
private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];
private final HeaderEntry head = new HeaderEntry();
protected final boolean validate;
public DefaultHttpHeaders() {
@ -47,407 +53,236 @@ public class DefaultHttpHeaders extends HttpHeaders {
public DefaultHttpHeaders(boolean validate) {
this.validate = validate;
head.before = head.after = head;
}
void validateHeaderName0(CharSequence headerName) {
validateHeaderName(headerName);
}
@Override
public HttpHeaders add(HttpHeaders headers) {
if (headers instanceof DefaultHttpHeaders) {
DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) headers;
HeaderEntry e = defaultHttpHeaders.head.after;
while (e != defaultHttpHeaders.head) {
add(e.key, e.value);
e = e.after;
}
return this;
} else {
return super.add(headers);
}
}
@Override
public HttpHeaders set(HttpHeaders headers) {
if (headers instanceof DefaultHttpHeaders) {
clear();
DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) headers;
HeaderEntry e = defaultHttpHeaders.head.after;
while (e != defaultHttpHeaders.head) {
add(e.key, e.value);
e = e.after;
}
return this;
} else {
return super.set(headers);
}
}
@Override
public HttpHeaders add(final CharSequence name, final Object value) {
CharSequence strVal;
protected CharSequence convertName(CharSequence name) {
name = super.convertName(name);
if (validate) {
validateHeaderName0(name);
strVal = toCharSequence(value);
validateHeaderValue(strVal);
} else {
strVal = toCharSequence(value);
if (name instanceof AsciiString) {
validateName((AsciiString) name);
} else {
validateName(name);
}
}
int h = hash(name);
int i = index(h);
add0(h, i, name, strVal);
return name;
}
private static void validateName(AsciiString name) {
// Go through each characters in the name
final int start = name.arrayOffset();
final int end = start + name.length();
final byte[] array = name.array();
for (int index = start; index < end; index ++) {
byte b = array[index];
// Check to see if the character is not an ASCII character
if (b < 0) {
throw new IllegalArgumentException(
"a header name cannot contain non-ASCII characters: " + name);
}
// Check for prohibited characters.
validateNameChar(name, b);
}
}
private static void validateName(CharSequence name) {
// Go through each characters in the name
for (int index = 0; index < name.length(); index ++) {
char character = name.charAt(index);
// Check to see if the character is not an ASCII character
if (character > 127) {
throw new IllegalArgumentException(
"a header name cannot contain non-ASCII characters: " + name);
}
// Check for prohibited characters.
validateNameChar(name, character);
}
}
private static void validateNameChar(CharSequence name, int character) {
if ((character & HIGHEST_INVALID_NAME_CHAR_MASK) == 0 && LOOKUP_TABLE[character] != 0) {
throw new IllegalArgumentException(
"a header name cannot contain the following prohibited characters: " +
"=,;: \\t\\r\\n\\v\\f: " + name);
}
}
@Override
protected CharSequence convertValue(Object value) {
if (value == null) {
throw new NullPointerException("value");
}
CharSequence seq;
if (value instanceof CharSequence) {
seq = (CharSequence) value;
} else if (value instanceof Number) {
seq = value.toString();
} else if (value instanceof Date) {
seq = HttpHeaderDateFormat.get().format((Date) value);
} else if (value instanceof Calendar) {
seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
} else {
seq = value.toString();
}
if (validate) {
if (value instanceof AsciiString) {
validateValue((AsciiString) seq);
} else {
validateValue(seq);
}
}
return seq;
}
private static void validateValue(AsciiString seq) {
int state = 0;
// Start looping through each of the character
final int start = seq.arrayOffset();
final int end = start + seq.length();
final byte[] array = seq.array();
for (int index = start; index < end; index ++) {
state = validateValueChar(seq, state, (char) (array[index] & 0xFF));
}
if (state != 0) {
throw new IllegalArgumentException(
"a header value must not end with '\\r' or '\\n':" + seq);
}
}
private static void validateValue(CharSequence seq) {
int state = 0;
// Start looping through each of the character
for (int index = 0; index < seq.length(); index ++) {
state = validateValueChar(seq, state, seq.charAt(index));
}
if (state != 0) {
throw new IllegalArgumentException(
"a header value must not end with '\\r' or '\\n':" + seq);
}
}
private static int validateValueChar(CharSequence seq, int state, char character) {
/*
* State:
*
* 0: Previous character was neither CR nor LF
* 1: The previous character was CR
* 2: The previous character was LF
*/
if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
// Check the absolutely prohibited characters.
switch (character) {
case 0x0b: // Vertical tab
throw new IllegalArgumentException(
"a header value contains a prohibited character '\\v': " + seq);
case '\f':
throw new IllegalArgumentException(
"a header value contains a prohibited character '\\f': " + seq);
}
}
// Check the CRLF (HT | SP) pattern
switch (state) {
case 0:
switch (character) {
case '\r':
state = 1;
break;
case '\n':
state = 2;
break;
}
break;
case 1:
switch (character) {
case '\n':
state = 2;
break;
default:
throw new IllegalArgumentException(
"only '\\n' is allowed after '\\r': " + seq);
}
break;
case 2:
switch (character) {
case '\t': case ' ':
state = 0;
break;
default:
throw new IllegalArgumentException(
"only ' ' and '\\t' are allowed after '\\n': " + seq);
}
}
return state;
}
@Override
public HttpHeaders add(CharSequence name, Object value) {
super.add(name, value);
return this;
}
@Override
public HttpHeaders add(CharSequence name, Iterable<?> values) {
if (validate) {
validateHeaderName0(name);
}
int h = hash(name);
int i = index(h);
for (Object v: values) {
CharSequence vstr = toCharSequence(v);
if (validate) {
validateHeaderValue(vstr);
}
add0(h, i, name, vstr);
}
return this;
}
private void add0(int h, int i, final CharSequence name, final CharSequence value) {
// Update the hash table.
HeaderEntry e = entries[i];
HeaderEntry newEntry;
entries[i] = newEntry = new HeaderEntry(h, name, value);
newEntry.next = e;
// Update the linked list.
newEntry.addBefore(head);
}
@Override
public HttpHeaders remove(final CharSequence name) {
if (name == null) {
throw new NullPointerException("name");
}
int h = hash(name);
int i = index(h);
remove0(h, i, name);
return this;
}
private void remove0(int h, int i, CharSequence name) {
HeaderEntry e = entries[i];
if (e == null) {
return;
}
for (;;) {
if (e.hash == h && equalsIgnoreCase(name, e.key)) {
e.remove();
HeaderEntry next = e.next;
if (next != null) {
entries[i] = next;
e = next;
} else {
entries[i] = null;
return;
}
} else {
break;
}
}
for (;;) {
HeaderEntry next = e.next;
if (next == null) {
break;
}
if (next.hash == h && equalsIgnoreCase(name, next.key)) {
e.next = next.next;
next.remove();
} else {
e = next;
}
}
}
@Override
public HttpHeaders set(final CharSequence name, final Object value) {
CharSequence strVal;
if (validate) {
validateHeaderName0(name);
strVal = toCharSequence(value);
validateHeaderValue(strVal);
} else {
strVal = toCharSequence(value);
}
int h = hash(name);
int i = index(h);
remove0(h, i, name);
add0(h, i, name, strVal);
super.add(name, values);
return this;
}
@Override
public HttpHeaders set(final CharSequence name, final Iterable<?> values) {
if (values == null) {
throw new NullPointerException("values");
}
if (validate) {
validateHeaderName0(name);
}
public HttpHeaders add(CharSequence name, Object... values) {
super.add(name, values);
return this;
}
int h = hash(name);
int i = index(h);
@Override
public HttpHeaders add(TextHeaders headers) {
super.add(headers);
return this;
}
remove0(h, i, name);
for (Object v: values) {
if (v == null) {
break;
}
CharSequence strVal = toCharSequence(v);
if (validate) {
validateHeaderValue(strVal);
}
add0(h, i, name, strVal);
}
@Override
public HttpHeaders set(CharSequence name, Object value) {
super.set(name, value);
return this;
}
@Override
public HttpHeaders set(CharSequence name, Object... values) {
super.set(name, values);
return this;
}
@Override
public HttpHeaders set(CharSequence name, Iterable<?> values) {
super.set(name, values);
return this;
}
@Override
public HttpHeaders set(TextHeaders headers) {
super.set(headers);
return this;
}
@Override
public HttpHeaders clear() {
Arrays.fill(entries, null);
head.before = head.after = head;
super.clear();
return this;
}
@Override
public String get(final CharSequence name) {
if (name == null) {
throw new NullPointerException("name");
}
int h = hash(name);
int i = index(h);
HeaderEntry e = entries[i];
CharSequence value = null;
// loop until the first header was found
while (e != null) {
if (e.hash == h && equalsIgnoreCase(name, e.key)) {
value = e.value;
}
e = e.next;
}
if (value != null) {
return value.toString();
}
return null;
}
@Override
public List<String> getAll(final CharSequence name) {
if (name == null) {
throw new NullPointerException("name");
}
LinkedList<String> values = new LinkedList<String>();
int h = hash(name);
int i = index(h);
HeaderEntry e = entries[i];
while (e != null) {
if (e.hash == h && equalsIgnoreCase(name, e.key)) {
values.addFirst(e.getValue());
}
e = e.next;
}
return values;
}
@Override
public List<Map.Entry<String, String>> entries() {
List<Map.Entry<String, String>> all =
new LinkedList<Map.Entry<String, String>>();
HeaderEntry e = head.after;
while (e != head) {
all.add(e);
e = e.after;
}
return all;
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
return new HeaderIterator();
}
@Override
public boolean contains(CharSequence name) {
return get(name) != null;
}
@Override
public boolean isEmpty() {
return head == head.after;
}
@Override
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCaseValue) {
if (name == null) {
throw new NullPointerException("name");
}
int h = hash(name);
int i = index(h);
HeaderEntry e = entries[i];
while (e != null) {
if (e.hash == h && equalsIgnoreCase(name, e.key)) {
if (ignoreCaseValue) {
if (equalsIgnoreCase(e.value, value)) {
return true;
}
} else {
if (e.value.equals(value)) {
return true;
}
}
}
e = e.next;
}
return false;
}
@Override
public Set<String> names() {
Set<String> names = new LinkedHashSet<String>();
HeaderEntry e = head.after;
while (e != head) {
names.add(e.getKey());
e = e.after;
}
return names;
}
void encode(ByteBuf buf) {
HeaderEntry e = head.after;
while (e != head) {
e.encode(buf);
e = e.after;
}
}
private static CharSequence toCharSequence(Object value) {
if (value == null) {
return null;
}
if (value instanceof CharSequence) {
return (CharSequence) value;
}
if (value instanceof Number) {
return value.toString();
}
if (value instanceof Date) {
return HttpHeaderDateFormat.get().format((Date) value);
}
if (value instanceof Calendar) {
return HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
}
return value.toString();
}
private final class HeaderIterator implements Iterator<Map.Entry<String, String>> {
private HeaderEntry current = head;
@Override
public boolean hasNext() {
return current.after != head;
}
@Override
public Entry<String, String> next() {
current = current.after;
if (current == head) {
throw new NoSuchElementException();
}
return current;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
private final class HeaderEntry implements Map.Entry<String, String> {
final int hash;
final CharSequence key;
CharSequence value;
HeaderEntry next;
HeaderEntry before, after;
HeaderEntry(int hash, CharSequence key, CharSequence value) {
this.hash = hash;
this.key = key;
this.value = value;
}
HeaderEntry() {
hash = -1;
key = null;
value = null;
}
void remove() {
before.after = after;
after.before = before;
}
void addBefore(HeaderEntry e) {
after = e;
before = e.before;
before.after = this;
after.before = this;
}
@Override
public String getKey() {
return key.toString();
}
@Override
public String getValue() {
return value.toString();
}
@Override
public String setValue(String value) {
if (value == null) {
throw new NullPointerException("value");
}
validateHeaderValue(value);
CharSequence oldValue = this.value;
this.value = value;
return oldValue.toString();
}
@Override
public String toString() {
return key.toString() + '=' + value.toString();
}
void encode(ByteBuf buf) {
HttpHeaders.encode(key, value, buf);
}
public HttpHeaders forEachEntry(TextHeaderProcessor processor) {
super.forEachEntry(processor);
return this;
}
}

View File

@ -62,7 +62,7 @@ public abstract class DefaultHttpMessage extends DefaultHttpObject implements Ht
buf.append("(version: ");
buf.append(getProtocolVersion().text());
buf.append(", keepAlive: ");
buf.append(HttpHeaders.isKeepAlive(this));
buf.append(HttpHeaderUtil.isKeepAlive(this));
buf.append(')');
buf.append(StringUtil.NEWLINE);
appendHeaders(buf);

View File

@ -17,6 +17,7 @@ package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.AsciiString;
import io.netty.util.internal.StringUtil;
import java.util.Map;
@ -39,7 +40,7 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
public DefaultLastHttpContent(ByteBuf content, boolean validateHeaders) {
super(content);
trailingHeaders = new TrailingHeaders(validateHeaders);
trailingHeaders = new TrailingHttpHeaders(validateHeaders);
this.validateHeaders = validateHeaders;
}
@ -106,20 +107,23 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
}
}
private static final class TrailingHeaders extends DefaultHttpHeaders {
TrailingHeaders(boolean validate) {
private static final class TrailingHttpHeaders extends DefaultHttpHeaders {
TrailingHttpHeaders(boolean validate) {
super(validate);
}
@Override
void validateHeaderName0(CharSequence name) {
super.validateHeaderName0(name);
if (equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) ||
equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) ||
equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) {
throw new IllegalArgumentException(
"prohibited trailing header: " + name);
protected CharSequence convertName(CharSequence name) {
name = super.convertName(name);
if (validate) {
if (AsciiString.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) ||
AsciiString.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) ||
AsciiString.equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) {
throw new IllegalArgumentException(
"prohibited trailing header: " + name);
}
}
return name;
}
}
}

View File

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

View File

@ -16,6 +16,7 @@
package io.netty.handler.codec.http;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.util.internal.StringUtil;
@ -96,7 +97,7 @@ public class HttpContentCompressor extends HttpContentEncoder {
protected Result beginEncode(HttpResponse headers, CharSequence acceptEncoding) throws Exception {
String contentEncoding = headers.headers().get(HttpHeaders.Names.CONTENT_ENCODING);
if (contentEncoding != null &&
!HttpHeaders.equalsIgnoreCase(HttpHeaders.Values.IDENTITY, contentEncoding)) {
!AsciiString.equalsIgnoreCase(HttpHeaders.Values.IDENTITY, contentEncoding)) {
return null;
}

View File

@ -1,60 +0,0 @@
/*
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
final class HttpHeaderEntity implements CharSequence {
private final String name;
private final int hash;
private final byte[] bytes;
public HttpHeaderEntity(String name) {
this.name = name;
hash = HttpHeaders.hash(name);
bytes = name.getBytes(CharsetUtil.US_ASCII);
}
int hash() {
return hash;
}
@Override
public int length() {
return bytes.length;
}
@Override
public char charAt(int index) {
return (char) bytes[index];
}
@Override
public CharSequence subSequence(int start, int end) {
return new HttpHeaderEntity(name.substring(start, end));
}
@Override
public String toString() {
return name;
}
void encode(ByteBuf buf) {
buf.writeBytes(bytes);
}
}

View File

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

View File

@ -0,0 +1,72 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.TextHeaderProcessor;
final class HttpHeadersEncoder implements TextHeaderProcessor {
private final ByteBuf buf;
HttpHeadersEncoder(ByteBuf buf) {
this.buf = buf;
}
@Override
public boolean process(CharSequence name, CharSequence value) throws Exception {
final ByteBuf buf = this.buf;
final int nameLen = name.length();
final int valueLen = value.length();
final int entryLen = nameLen + valueLen + 4;
int offset = buf.writerIndex();
buf.ensureWritable(entryLen);
writeAscii(buf, offset, name, nameLen);
offset += nameLen;
buf.setByte(offset ++, ':');
buf.setByte(offset ++, ' ');
writeAscii(buf, offset, value, valueLen);
offset += valueLen;
buf.setByte(offset ++, '\r');
buf.setByte(offset ++, '\n');
buf.writerIndex(offset);
return true;
}
private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) {
if (value instanceof AsciiString) {
writeAsciiString(buf, offset, (AsciiString) value, valueLen);
} else {
writeCharSequence(buf, offset, value, valueLen);
}
}
private static void writeAsciiString(ByteBuf buf, int offset, AsciiString value, int valueLen) {
value.copy(0, buf, offset, valueLen);
}
private static void writeCharSequence(ByteBuf buf, int offset, CharSequence value, int valueLen) {
for (int i = 0; i < valueLen; i ++) {
buf.setByte(offset ++, c2b(value.charAt(i)));
}
}
private static int c2b(char ch) {
return ch < 256? (byte) ch : '?';
}
}

View File

@ -197,7 +197,7 @@ public class HttpMethod implements Comparable<HttpMethod> {
void encode(ByteBuf buf) {
if (bytes == null) {
HttpHeaders.encodeAscii0(name, buf);
HttpHeaderUtil.encodeAscii0(name, buf);
} else {
buf.writeBytes(bytes);
}

View File

@ -95,17 +95,17 @@ public class HttpObjectAggregator
@Override
protected boolean hasContentLength(HttpMessage start) throws Exception {
return HttpHeaders.isContentLengthSet(start);
return HttpHeaderUtil.isContentLengthSet(start);
}
@Override
protected long contentLength(HttpMessage start) throws Exception {
return HttpHeaders.getContentLength(start);
return HttpHeaderUtil.getContentLength(start);
}
@Override
protected Object newContinueResponse(HttpMessage start) throws Exception {
if (HttpHeaders.is100ContinueExpected(start)) {
if (HttpHeaderUtil.is100ContinueExpected(start)) {
return CONTINUE;
} else {
return null;
@ -116,7 +116,7 @@ public class HttpObjectAggregator
protected FullHttpMessage beginAggregation(HttpMessage start, ByteBuf content) throws Exception {
assert !(start instanceof FullHttpMessage);
HttpHeaders.removeTransferEncodingChunked(start);
HttpHeaderUtil.setTransferEncodingChunked(start, false);
FullHttpMessage ret;
if (start instanceof HttpRequest) {
@ -169,7 +169,7 @@ public class HttpObjectAggregator
// If 'Expect: 100-continue' is missing, close becuase it's impossible to recover.
// If keep-alive is off, no need to leave the connection open.
if (oversized instanceof FullHttpMessage ||
!HttpHeaders.is100ContinueExpected(oversized) || !HttpHeaders.isKeepAlive(oversized)) {
!HttpHeaderUtil.is100ContinueExpected(oversized) || !HttpHeaderUtil.isKeepAlive(oversized)) {
future.addListener(ChannelFutureListener.CLOSE);
}

View File

@ -20,6 +20,7 @@ import io.netty.buffer.ByteBufProcessor;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.ReplayingDecoder;
import io.netty.handler.codec.TooLongFrameException;
@ -520,9 +521,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
State nextState;
if (isContentAlwaysEmpty(message)) {
HttpHeaders.removeTransferEncodingChunked(message);
HttpHeaderUtil.setTransferEncodingChunked(message, false);
nextState = State.SKIP_CONTROL_CHARS;
} else if (HttpHeaders.isTransferEncodingChunked(message)) {
} else if (HttpHeaderUtil.isTransferEncodingChunked(message)) {
nextState = State.READ_CHUNK_SIZE;
} else if (contentLength() >= 0) {
nextState = State.READ_FIXED_LENGTH_CONTENT;
@ -534,7 +535,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
private long contentLength() {
if (contentLength == Long.MIN_VALUE) {
contentLength = HttpHeaders.getContentLength(message, -1);
contentLength = HttpHeaderUtil.getContentLength(message, -1);
}
return contentLength;
}
@ -559,9 +560,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
} else {
String[] header = splitHeader(line);
String name = header[0];
if (!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.CONTENT_LENGTH) &&
!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.TRANSFER_ENCODING) &&
!HttpHeaders.equalsIgnoreCase(name, HttpHeaders.Names.TRAILER)) {
if (!AsciiString.equalsIgnoreCase(name, HttpHeaders.Names.CONTENT_LENGTH) &&
!AsciiString.equalsIgnoreCase(name, HttpHeaders.Names.TRANSFER_ENCODING) &&
!AsciiString.equalsIgnoreCase(name, HttpHeaders.Names.TRAILER)) {
trailer.trailingHeaders().add(name, header[1]);
}
lastHeader = name;

View File

@ -69,9 +69,9 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
buf = ctx.alloc().buffer();
// Encode the message.
encodeInitialLine(buf, m);
HttpHeaders.encode(m.headers(), buf);
m.headers().forEachEntry(new HttpHeadersEncoder(buf));
buf.writeBytes(CRLF);
state = HttpHeaders.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
state = HttpHeaderUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
}
if (msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion) {
if (state == ST_INIT) {
@ -137,7 +137,7 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
} else {
ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes(ZERO_CRLF);
HttpHeaders.encode(headers, buf);
headers.forEachEntry(new HttpHeadersEncoder(buf));
buf.writeBytes(CRLF);
out.add(buf);
}

View File

@ -18,7 +18,7 @@ package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
import static io.netty.handler.codec.http.HttpConstants.SP;
import static io.netty.handler.codec.http.HttpConstants.*;
/**
* The response code and its description of HTTP or its derived protocols, such as
@ -453,6 +453,36 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
return new HttpResponseStatus(code, reasonPhrase + " (" + code + ')');
}
/**
* Parses the specified HTTP status line into a {@link HttpResponseStatus}. The expected formats of the line are:
* <ul>
* <li>{@code statusCode} (e.g. 200)</li>
* <li>{@code statusCode} {@code reasonPhrase} (e.g. 404 Not Found)</li>
* </ul>
*
* @throws IllegalArgumentException if the specified status line is malformed
*/
public static HttpResponseStatus parseLine(CharSequence line) {
String status = line.toString();
try {
int space = status.indexOf(' ');
if (space == -1) {
return valueOf(Integer.parseInt(status));
} else {
int code = Integer.parseInt(status.substring(0, space));
String reasonPhrase = status.substring(space + 1);
HttpResponseStatus responseStatus = valueOf(code);
if (responseStatus.reasonPhrase().equals(reasonPhrase)) {
return responseStatus;
} else {
return new HttpResponseStatus(code, reasonPhrase);
}
}
} catch (Exception e) {
throw new IllegalArgumentException("malformed status line: " + status, e);
}
}
private final int code;
private final String reasonPhrase;
@ -548,9 +578,9 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
void encode(ByteBuf buf) {
if (bytes == null) {
HttpHeaders.encodeAscii0(String.valueOf(code()), buf);
HttpHeaderUtil.encodeAscii0(String.valueOf(code()), buf);
buf.writeByte(SP);
HttpHeaders.encodeAscii0(String.valueOf(reasonPhrase()), buf);
HttpHeaderUtil.encodeAscii0(String.valueOf(reasonPhrase()), buf);
} else {
buf.writeBytes(bytes);
}

View File

@ -264,7 +264,7 @@ public class HttpVersion implements Comparable<HttpVersion> {
void encode(ByteBuf buf) {
if (bytes == null) {
HttpHeaders.encodeAscii0(text, buf);
HttpHeaderUtil.encodeAscii0(text, buf);
} else {
buf.writeBytes(bytes);
}

View File

@ -46,7 +46,7 @@ public interface LastHttpContent extends HttpContent {
@Override
public HttpHeaders trailingHeaders() {
return HttpHeaders.EMPTY_HEADERS;
return EmptyHttpHeaders.INSTANCE;
}
@Override

View File

@ -16,6 +16,7 @@
package io.netty.handler.codec.http.cors;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.EmptyHttpHeaders;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpMethod;
@ -202,7 +203,7 @@ public final class CorsConfig {
*/
public HttpHeaders preflightResponseHeaders() {
if (preflightHeaders.isEmpty()) {
return HttpHeaders.EMPTY_HEADERS;
return EmptyHttpHeaders.INSTANCE;
}
final HttpHeaders preflightHeaders = new DefaultHttpHeaders();
for (Entry<CharSequence, Callable<?>> entry : this.preflightHeaders.entrySet()) {

View File

@ -17,12 +17,15 @@ package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.EmptyHttpHeaders;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
@ -741,14 +744,14 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
if (transferEncoding != null) {
headers.remove(HttpHeaders.Names.TRANSFER_ENCODING);
for (String v : transferEncoding) {
if (HttpHeaders.equalsIgnoreCase(v, HttpHeaders.Values.CHUNKED)) {
if (AsciiString.equalsIgnoreCase(v, HttpHeaders.Values.CHUNKED)) {
// ignore
} else {
headers.add(HttpHeaders.Names.TRANSFER_ENCODING, v);
}
}
}
HttpHeaders.setTransferEncodingChunked(request);
HttpHeaderUtil.setTransferEncodingChunked(request, true);
// wrap to hide the possible content
return new WrappedHttpRequest(request);
@ -1237,7 +1240,7 @@ public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
if (content instanceof LastHttpContent) {
return ((LastHttpContent) content).trailingHeaders();
} else {
return HttpHeaders.EMPTY_HEADERS;
return EmptyHttpHeaders.INSTANCE;
}
}

View File

@ -17,6 +17,7 @@ package io.netty.handler.codec.http.websocketx;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
@ -198,13 +199,13 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
HttpHeaders headers = response.headers();
String upgrade = headers.get(Names.UPGRADE);
if (!HttpHeaders.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
+ upgrade);
}
String connection = headers.get(Names.CONNECTION);
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, connection)) {
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) {
throw new WebSocketHandshakeException("Invalid handshake response connection: "
+ connection);
}

View File

@ -15,6 +15,7 @@
*/
package io.netty.handler.codec.http.websocketx;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
@ -40,7 +41,7 @@ import java.net.URI;
public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker07.class);
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private String expectedChallengeResponseString;
@ -173,12 +174,12 @@ public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker {
}
String upgrade = headers.get(Names.UPGRADE);
if (!HttpHeaders.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade);
}
String connection = headers.get(Names.CONNECTION);
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, connection)) {
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) {
throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection);
}

View File

@ -15,6 +15,7 @@
*/
package io.netty.handler.codec.http.websocketx;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
@ -40,7 +41,7 @@ import java.net.URI;
public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker08.class);
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@ -174,12 +175,12 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
}
String upgrade = headers.get(Names.UPGRADE);
if (!HttpHeaders.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade);
}
String connection = headers.get(Names.CONNECTION);
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, connection)) {
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) {
throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection);
}

View File

@ -15,6 +15,7 @@
*/
package io.netty.handler.codec.http.websocketx;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
@ -40,7 +41,7 @@ import java.net.URI;
public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.class);
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@ -184,12 +185,12 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
}
String upgrade = headers.get(Names.UPGRADE);
if (!HttpHeaders.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) {
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade);
}
String connection = headers.get(Names.CONNECTION);
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, connection)) {
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) {
throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection);
}

View File

@ -20,6 +20,7 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
@ -109,8 +110,8 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
protected FullHttpResponse newHandshakeResponse(FullHttpRequest req, HttpHeaders headers) {
// Serve the WebSocket handshake request.
if (!HttpHeaders.equalsIgnoreCase(Values.UPGRADE, req.headers().get(CONNECTION))
|| !HttpHeaders.equalsIgnoreCase(WEBSOCKET, req.headers().get(Names.UPGRADE))) {
if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, req.headers().get(CONNECTION))
|| !AsciiString.equalsIgnoreCase(WEBSOCKET, req.headers().get(Names.UPGRADE))) {
throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
}

View File

@ -15,6 +15,7 @@
*/
package io.netty.handler.codec.http.websocketx;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
@ -35,7 +36,7 @@ import static io.netty.handler.codec.http.HttpVersion.*;
*/
public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
public static final String WEBSOCKET_07_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

View File

@ -15,6 +15,7 @@
*/
package io.netty.handler.codec.http.websocketx;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
@ -35,7 +36,7 @@ import static io.netty.handler.codec.http.HttpVersion.*;
*/
public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

View File

@ -15,6 +15,7 @@
*/
package io.netty.handler.codec.http.websocketx;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
@ -34,7 +35,7 @@ import static io.netty.handler.codec.http.HttpVersion.*;
*/
public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toString().toLowerCase());
private static final CharSequence WEBSOCKET = new AsciiString(Values.WEBSOCKET.toString().toLowerCase());
public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

View File

@ -22,12 +22,12 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.ssl.SslHandler;
import static io.netty.handler.codec.http.HttpHeaders.*;
import static io.netty.handler.codec.http.HttpMethod.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.*;
@ -47,7 +47,7 @@ class WebSocketServerProtocolHandshakeHandler extends ChannelHandlerAdapter {
this.websocketPath = websocketPath;
this.subprotocols = subprotocols;
this.allowExtensions = allowExtensions;
this.maxFramePayloadSize = maxFrameSize;
maxFramePayloadSize = maxFrameSize;
}
@Override
@ -89,7 +89,7 @@ class WebSocketServerProtocolHandshakeHandler extends ChannelHandlerAdapter {
private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!isKeepAlive(req) || res.getStatus().code() != 200) {
if (!HttpHeaderUtil.isKeepAlive(req) || res.getStatus().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}

View File

@ -15,6 +15,7 @@
*/
package io.netty.handler.codec.rtsp;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.HttpHeaders;
@ -42,7 +43,7 @@ public final class RtspHeaders {
/**
* {@code "Allow"}
*/
public static final CharSequence ALLOW = HttpHeaders.newEntity("Allow");
public static final CharSequence ALLOW = new AsciiString("Allow");
/**
* {@code "Authorization"}
*/
@ -50,11 +51,11 @@ public final class RtspHeaders {
/**
* {@code "Bandwidth"}
*/
public static final CharSequence BANDWIDTH = HttpHeaders.newEntity("Bandwidth");
public static final CharSequence BANDWIDTH = new AsciiString("Bandwidth");
/**
* {@code "Blocksize"}
*/
public static final CharSequence BLOCKSIZE = HttpHeaders.newEntity("Blocksize");
public static final CharSequence BLOCKSIZE = new AsciiString("Blocksize");
/**
* {@code "Cache-Control"}
*/
@ -62,7 +63,7 @@ public final class RtspHeaders {
/**
* {@code "Conference"}
*/
public static final CharSequence CONFERENCE = HttpHeaders.newEntity("Conference");
public static final CharSequence CONFERENCE = new AsciiString("Conference");
/**
* {@code "Connection"}
*/
@ -94,7 +95,7 @@ public final class RtspHeaders {
/**
* {@code "CSeq"}
*/
public static final CharSequence CSEQ = HttpHeaders.newEntity("CSeq");
public static final CharSequence CSEQ = new AsciiString("CSeq");
/**
* {@code "Date"}
*/
@ -122,7 +123,7 @@ public final class RtspHeaders {
/**
* {@code "KeyMgmt"}
*/
public static final CharSequence KEYMGMT = HttpHeaders.newEntity("KeyMgmt");
public static final CharSequence KEYMGMT = new AsciiString("KeyMgmt");
/**
* {@code "Last-Modified"}
*/
@ -134,11 +135,11 @@ public final class RtspHeaders {
/**
* {@code "Proxy-Require"}
*/
public static final CharSequence PROXY_REQUIRE = HttpHeaders.newEntity("Proxy-Require");
public static final CharSequence PROXY_REQUIRE = new AsciiString("Proxy-Require");
/**
* {@code "Public"}
*/
public static final CharSequence PUBLIC = HttpHeaders.newEntity("Public");
public static final CharSequence PUBLIC = new AsciiString("Public");
/**
* {@code "Range"}
*/
@ -150,7 +151,7 @@ public final class RtspHeaders {
/**
* {@code "Require"}
*/
public static final CharSequence REQUIRE = HttpHeaders.newEntity("Require");
public static final CharSequence REQUIRE = new AsciiString("Require");
/**
* {@code "Retry-After"}
*/
@ -158,15 +159,15 @@ public final class RtspHeaders {
/**
* {@code "RTP-Info"}
*/
public static final CharSequence RTP_INFO = HttpHeaders.newEntity("RTP-Info");
public static final CharSequence RTP_INFO = new AsciiString("RTP-Info");
/**
* {@code "Scale"}
*/
public static final CharSequence SCALE = HttpHeaders.newEntity("Scale");
public static final CharSequence SCALE = new AsciiString("Scale");
/**
* {@code "Session"}
*/
public static final CharSequence SESSION = HttpHeaders.newEntity("Session");
public static final CharSequence SESSION = new AsciiString("Session");
/**
* {@code "Server"}
*/
@ -174,19 +175,19 @@ public final class RtspHeaders {
/**
* {@code "Speed"}
*/
public static final CharSequence SPEED = HttpHeaders.newEntity("Speed");
public static final CharSequence SPEED = new AsciiString("Speed");
/**
* {@code "Timestamp"}
*/
public static final CharSequence TIMESTAMP = HttpHeaders.newEntity("Timestamp");
public static final CharSequence TIMESTAMP = new AsciiString("Timestamp");
/**
* {@code "Transport"}
*/
public static final CharSequence TRANSPORT = HttpHeaders.newEntity("Transport");
public static final CharSequence TRANSPORT = new AsciiString("Transport");
/**
* {@code "Unsupported"}
*/
public static final CharSequence UNSUPPORTED = HttpHeaders.newEntity("Unsupported");
public static final CharSequence UNSUPPORTED = new AsciiString("Unsupported");
/**
* {@code "User-Agent"}
*/
@ -215,11 +216,11 @@ public final class RtspHeaders {
/**
* {@code "append"}
*/
public static final CharSequence APPEND = HttpHeaders.newEntity("append");
public static final CharSequence APPEND = new AsciiString("append");
/**
* {@code "AVP"}
*/
public static final CharSequence AVP = HttpHeaders.newEntity("AVP");
public static final CharSequence AVP = new AsciiString("AVP");
/**
* {@code "bytes"}
*/
@ -231,11 +232,11 @@ public final class RtspHeaders {
/**
* {@code "client_port"}
*/
public static final CharSequence CLIENT_PORT = HttpHeaders.newEntity("client_port");
public static final CharSequence CLIENT_PORT = new AsciiString("client_port");
/**
* {@code "clock"}
*/
public static final CharSequence CLOCK = HttpHeaders.newEntity("clock");
public static final CharSequence CLOCK = new AsciiString("clock");
/**
* {@code "close"}
*/
@ -255,7 +256,7 @@ public final class RtspHeaders {
/**
* {@code "destination"}
*/
public static final CharSequence DESTINATION = HttpHeaders.newEntity("destination");
public static final CharSequence DESTINATION = new AsciiString("destination");
/**
* {@code "gzip"}
*/
@ -267,7 +268,7 @@ public final class RtspHeaders {
/**
* {@code "interleaved"}
*/
public static final CharSequence INTERLEAVED = HttpHeaders.newEntity("interleaved");
public static final CharSequence INTERLEAVED = new AsciiString("interleaved");
/**
* {@code "keep-alive"}
*/
@ -275,7 +276,7 @@ public final class RtspHeaders {
/**
* {@code "layers"}
*/
public static final CharSequence LAYERS = HttpHeaders.newEntity("layers");
public static final CharSequence LAYERS = new AsciiString("layers");
/**
* {@code "max-age"}
*/
@ -291,11 +292,11 @@ public final class RtspHeaders {
/**
* {@code "mode"}
*/
public static final CharSequence MODE = HttpHeaders.newEntity("mode");
public static final CharSequence MODE = new AsciiString("mode");
/**
* {@code "multicast"}
*/
public static final CharSequence MULTICAST = HttpHeaders.newEntity("multicast");
public static final CharSequence MULTICAST = new AsciiString("multicast");
/**
* {@code "must-revalidate"}
*/
@ -319,7 +320,7 @@ public final class RtspHeaders {
/**
* {@code "port"}
*/
public static final CharSequence PORT = HttpHeaders.newEntity("port");
public static final CharSequence PORT = new AsciiString("port");
/**
* {@code "private"}
*/
@ -335,51 +336,51 @@ public final class RtspHeaders {
/**
* {@code "RTP"}
*/
public static final CharSequence RTP = HttpHeaders.newEntity("RTP");
public static final CharSequence RTP = new AsciiString("RTP");
/**
* {@code "rtptime"}
*/
public static final CharSequence RTPTIME = HttpHeaders.newEntity("rtptime");
public static final CharSequence RTPTIME = new AsciiString("rtptime");
/**
* {@code "seq"}
*/
public static final CharSequence SEQ = HttpHeaders.newEntity("seq");
public static final CharSequence SEQ = new AsciiString("seq");
/**
* {@code "server_port"}
*/
public static final CharSequence SERVER_PORT = HttpHeaders.newEntity("server_port");
public static final CharSequence SERVER_PORT = new AsciiString("server_port");
/**
* {@code "ssrc"}
*/
public static final CharSequence SSRC = HttpHeaders.newEntity("ssrc");
public static final CharSequence SSRC = new AsciiString("ssrc");
/**
* {@code "TCP"}
*/
public static final CharSequence TCP = HttpHeaders.newEntity("TCP");
public static final CharSequence TCP = new AsciiString("TCP");
/**
* {@code "time"}
*/
public static final CharSequence TIME = HttpHeaders.newEntity("time");
public static final CharSequence TIME = new AsciiString("time");
/**
* {@code "timeout"}
*/
public static final CharSequence TIMEOUT = HttpHeaders.newEntity("timeout");
public static final CharSequence TIMEOUT = new AsciiString("timeout");
/**
* {@code "ttl"}
*/
public static final CharSequence TTL = HttpHeaders.newEntity("ttl");
public static final CharSequence TTL = new AsciiString("ttl");
/**
* {@code "UDP"}
*/
public static final CharSequence UDP = HttpHeaders.newEntity("UDP");
public static final CharSequence UDP = new AsciiString("UDP");
/**
* {@code "unicast"}
*/
public static final CharSequence UNICAST = HttpHeaders.newEntity("unicast");
public static final CharSequence UNICAST = new AsciiString("unicast");
/**
* {@code "url"}
*/
public static final CharSequence URL = HttpHeaders.newEntity("url");
public static final CharSequence URL = new AsciiString("url");
private Values() { }
}

View File

@ -17,7 +17,6 @@ package io.netty.handler.codec.rtsp;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.util.CharsetUtil;
@ -37,13 +36,12 @@ public class RtspRequestEncoder extends RtspObjectEncoder<HttpRequest> {
}
@Override
protected void encodeInitialLine(ByteBuf buf, HttpRequest request)
throws Exception {
HttpHeaders.encodeAscii(request.getMethod().toString(), buf);
protected void encodeInitialLine(ByteBuf buf, HttpRequest request) throws Exception {
buf.writeBytes(request.getMethod().toString().getBytes(CharsetUtil.US_ASCII));
buf.writeByte(SP);
buf.writeBytes(request.getUri().getBytes(CharsetUtil.UTF_8));
buf.writeByte(SP);
HttpHeaders.encodeAscii(request.getProtocolVersion().toString(), buf);
buf.writeBytes(request.getProtocolVersion().toString().getBytes(CharsetUtil.US_ASCII));
buf.writeBytes(CRLF);
}
}

View File

@ -17,7 +17,6 @@ package io.netty.handler.codec.rtsp;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.util.CharsetUtil;
@ -37,13 +36,12 @@ public class RtspResponseEncoder extends RtspObjectEncoder<HttpResponse> {
}
@Override
protected void encodeInitialLine(ByteBuf buf, HttpResponse response)
throws Exception {
HttpHeaders.encodeAscii(response.getProtocolVersion().toString(), buf);
protected void encodeInitialLine(ByteBuf buf, HttpResponse response) throws Exception {
buf.writeBytes(response.getProtocolVersion().toString().getBytes(CharsetUtil.US_ASCII));
buf.writeByte(SP);
buf.writeBytes(String.valueOf(response.getStatus().code()).getBytes(CharsetUtil.US_ASCII));
buf.writeByte(SP);
HttpHeaders.encodeAscii(String.valueOf(response.getStatus().reasonPhrase()), buf);
buf.writeBytes(response.getStatus().reasonPhrase().getBytes(CharsetUtil.US_ASCII));
buf.writeBytes(CRLF);
}
}

View File

@ -15,365 +15,101 @@
*/
package io.netty.handler.codec.spdy;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.DefaultTextHeaders;
import io.netty.handler.codec.TextHeaderProcessor;
import io.netty.handler.codec.TextHeaders;
import java.util.Locale;
public class DefaultSpdyHeaders extends SpdyHeaders {
private static final int BUCKET_SIZE = 17;
private static int hash(String name) {
int h = 0;
for (int i = name.length() - 1; i >= 0; i --) {
char c = name.charAt(i);
if (c >= 'A' && c <= 'Z') {
c += 32;
}
h = 31 * h + c;
}
if (h > 0) {
return h;
} else if (h == Integer.MIN_VALUE) {
return Integer.MAX_VALUE;
public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeaders {
@Override
protected CharSequence convertName(CharSequence name) {
name = super.convertName(name);
if (name instanceof AsciiString) {
name = ((AsciiString) name).toLowerCase();
} else {
return -h;
name = name.toString().toLowerCase(Locale.US);
}
}
private static boolean eq(String name1, String name2) {
int nameLen = name1.length();
if (nameLen != name2.length()) {
return false;
}
for (int i = nameLen - 1; i >= 0; i --) {
char c1 = name1.charAt(i);
char c2 = name2.charAt(i);
if (c1 != c2) {
if (c1 >= 'A' && c1 <= 'Z') {
c1 += 32;
}
if (c2 >= 'A' && c2 <= 'Z') {
c2 += 32;
}
if (c1 != c2) {
return false;
}
}
}
return true;
}
private static int index(int hash) {
return hash % BUCKET_SIZE;
}
private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];
private final HeaderEntry head = new HeaderEntry(-1, null, null);
DefaultSpdyHeaders() {
head.before = head.after = head;
SpdyCodecUtil.validateHeaderName(name);
return name;
}
@Override
public SpdyHeaders add(final String name, final Object value) {
String lowerCaseName = name.toLowerCase();
SpdyCodecUtil.validateHeaderName(lowerCaseName);
String strVal = toString(value);
SpdyCodecUtil.validateHeaderValue(strVal);
int h = hash(lowerCaseName);
int i = index(h);
add0(h, i, lowerCaseName, strVal);
return this;
}
protected CharSequence convertValue(Object value) {
if (value == null) {
throw new NullPointerException("value");
}
private void add0(int h, int i, final String name, final String value) {
// Update the hash table.
HeaderEntry e = entries[i];
HeaderEntry newEntry;
entries[i] = newEntry = new HeaderEntry(h, name, value);
newEntry.next = e;
CharSequence seq;
if (value instanceof CharSequence) {
seq = (CharSequence) value;
} else {
seq = value.toString();
}
// Update the linked list.
newEntry.addBefore(head);
SpdyCodecUtil.validateHeaderValue(seq);
return seq;
}
@Override
public SpdyHeaders remove(final String name) {
if (name == null) {
throw new NullPointerException("name");
}
String lowerCaseName = name.toLowerCase();
int h = hash(lowerCaseName);
int i = index(h);
remove0(h, i, lowerCaseName);
return this;
}
private void remove0(int h, int i, String name) {
HeaderEntry e = entries[i];
if (e == null) {
return;
}
for (;;) {
if (e.hash == h && eq(name, e.key)) {
e.remove();
HeaderEntry next = e.next;
if (next != null) {
entries[i] = next;
e = next;
} else {
entries[i] = null;
return;
}
} else {
break;
}
}
for (;;) {
HeaderEntry next = e.next;
if (next == null) {
break;
}
if (next.hash == h && eq(name, next.key)) {
e.next = next.next;
next.remove();
} else {
e = next;
}
}
}
@Override
public SpdyHeaders set(final String name, final Object value) {
String lowerCaseName = name.toLowerCase();
SpdyCodecUtil.validateHeaderName(lowerCaseName);
String strVal = toString(value);
SpdyCodecUtil.validateHeaderValue(strVal);
int h = hash(lowerCaseName);
int i = index(h);
remove0(h, i, lowerCaseName);
add0(h, i, lowerCaseName, strVal);
public SpdyHeaders add(CharSequence name, Object value) {
super.add(name, value);
return this;
}
@Override
public SpdyHeaders set(final String name, final Iterable<?> values) {
if (values == null) {
throw new NullPointerException("values");
}
public SpdyHeaders add(CharSequence name, Iterable<?> values) {
super.add(name, values);
return this;
}
String lowerCaseName = name.toLowerCase();
SpdyCodecUtil.validateHeaderName(lowerCaseName);
@Override
public SpdyHeaders add(CharSequence name, Object... values) {
super.add(name, values);
return this;
}
int h = hash(lowerCaseName);
int i = index(h);
@Override
public SpdyHeaders add(TextHeaders headers) {
super.add(headers);
return this;
}
remove0(h, i, lowerCaseName);
for (Object v: values) {
if (v == null) {
break;
}
String strVal = toString(v);
SpdyCodecUtil.validateHeaderValue(strVal);
add0(h, i, lowerCaseName, strVal);
}
@Override
public SpdyHeaders set(CharSequence name, Object value) {
super.set(name, value);
return this;
}
@Override
public SpdyHeaders set(CharSequence name, Object... values) {
super.set(name, values);
return this;
}
@Override
public SpdyHeaders set(CharSequence name, Iterable<?> values) {
super.set(name, values);
return this;
}
@Override
public SpdyHeaders set(TextHeaders headers) {
super.set(headers);
return this;
}
@Override
public SpdyHeaders clear() {
for (int i = 0; i < entries.length; i ++) {
entries[i] = null;
}
head.before = head.after = head;
super.clear();
return this;
}
@Override
public String get(final String name) {
if (name == null) {
throw new NullPointerException("name");
}
int h = hash(name);
int i = index(h);
HeaderEntry e = entries[i];
while (e != null) {
if (e.hash == h && eq(name, e.key)) {
return e.value;
}
e = e.next;
}
return null;
}
@Override
public List<String> getAll(final String name) {
if (name == null) {
throw new NullPointerException("name");
}
LinkedList<String> values = new LinkedList<String>();
int h = hash(name);
int i = index(h);
HeaderEntry e = entries[i];
while (e != null) {
if (e.hash == h && eq(name, e.key)) {
values.addFirst(e.value);
}
e = e.next;
}
return values;
}
@Override
public List<Map.Entry<String, String>> entries() {
List<Map.Entry<String, String>> all =
new LinkedList<Map.Entry<String, String>>();
HeaderEntry e = head.after;
while (e != head) {
all.add(e);
e = e.after;
}
return all;
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
return new HeaderIterator();
}
@Override
public boolean contains(String name) {
return get(name) != null;
}
@Override
public Set<String> names() {
Set<String> names = new TreeSet<String>();
HeaderEntry e = head.after;
while (e != head) {
names.add(e.key);
e = e.after;
}
return names;
}
@Override
public SpdyHeaders add(String name, Iterable<?> values) {
SpdyCodecUtil.validateHeaderValue(name);
int h = hash(name);
int i = index(h);
for (Object v: values) {
String vstr = toString(v);
SpdyCodecUtil.validateHeaderValue(vstr);
add0(h, i, name, vstr);
}
public SpdyHeaders forEachEntry(TextHeaderProcessor processor) {
super.forEachEntry(processor);
return this;
}
@Override
public boolean isEmpty() {
return head == head.after;
}
private static String toString(Object value) {
if (value == null) {
return null;
}
return value.toString();
}
private final class HeaderIterator implements Iterator<Map.Entry<String, String>> {
private HeaderEntry current = head;
@Override
public boolean hasNext() {
return current.after != head;
}
@Override
public Entry<String, String> next() {
current = current.after;
if (current == head) {
throw new NoSuchElementException();
}
return current;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
private static final class HeaderEntry implements Map.Entry<String, String> {
final int hash;
final String key;
String value;
HeaderEntry next;
HeaderEntry before, after;
HeaderEntry(int hash, String key, String value) {
this.hash = hash;
this.key = key;
this.value = value;
}
void remove() {
before.after = after;
after.before = before;
}
void addBefore(HeaderEntry e) {
after = e;
before = e.before;
before.after = this;
after.before = this;
}
@Override
public String getKey() {
return key;
}
@Override
public String getValue() {
return value;
}
@Override
public String setValue(String value) {
if (value == null) {
throw new NullPointerException("value");
}
SpdyCodecUtil.validateHeaderValue(value);
String oldValue = this.value;
this.value = value;
return oldValue;
}
@Override
public String toString() {
return key + '=' + value;
}
}
}

View File

@ -285,11 +285,11 @@ final class SpdyCodecUtil {
/**
* Validate a SPDY header name.
*/
static void validateHeaderName(String name) {
static void validateHeaderName(CharSequence name) {
if (name == null) {
throw new NullPointerException("name");
}
if (name.isEmpty()) {
if (name.length() == 0) {
throw new IllegalArgumentException(
"name cannot be length zero");
}
@ -315,7 +315,7 @@ final class SpdyCodecUtil {
/**
* Validate a SPDY header value. Does not validate max length.
*/
static void validateHeaderValue(String value) {
static void validateHeaderValue(CharSequence value) {
if (value == null) {
throw new NullPointerException("value");
}

View File

@ -15,399 +15,75 @@
*/
package io.netty.handler.codec.spdy;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.TextHeaderProcessor;
import io.netty.handler.codec.TextHeaders;
/**
* Provides the constants for the standard SPDY HTTP header names and commonly
* used utility methods that access a {@link SpdyHeadersFrame}.
*/
public abstract class SpdyHeaders implements Iterable<Map.Entry<String, String>> {
public static final SpdyHeaders EMPTY_HEADERS = new SpdyHeaders() {
@Override
public List<String> getAll(String name) {
return Collections.emptyList();
}
@Override
public List<Map.Entry<String, String>> entries() {
return Collections.emptyList();
}
@Override
public boolean contains(String name) {
return false;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public Set<String> names() {
return Collections.emptySet();
}
@Override
public SpdyHeaders add(String name, Object value) {
throw new UnsupportedOperationException("read only");
}
@Override
public SpdyHeaders add(String name, Iterable<?> values) {
throw new UnsupportedOperationException("read only");
}
@Override
public SpdyHeaders set(String name, Object value) {
throw new UnsupportedOperationException("read only");
}
@Override
public SpdyHeaders set(String name, Iterable<?> values) {
throw new UnsupportedOperationException("read only");
}
@Override
public SpdyHeaders remove(String name) {
throw new UnsupportedOperationException("read only");
}
@Override
public SpdyHeaders clear() {
throw new UnsupportedOperationException("read only");
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
return entries().iterator();
}
@Override
public String get(String name) {
return null;
}
};
public interface SpdyHeaders extends TextHeaders {
/**
* SPDY HTTP header names
*/
public static final class HttpNames {
final class HttpNames {
/**
* {@code ":host"}
*/
public static final String HOST = ":host";
public static final AsciiString HOST = new AsciiString(":host");
/**
* {@code ":method"}
*/
public static final String METHOD = ":method";
public static final AsciiString METHOD = new AsciiString(":method");
/**
* {@code ":path"}
*/
public static final String PATH = ":path";
public static final AsciiString PATH = new AsciiString(":path");
/**
* {@code ":scheme"}
*/
public static final String SCHEME = ":scheme";
public static final AsciiString SCHEME = new AsciiString(":scheme");
/**
* {@code ":status"}
*/
public static final String STATUS = ":status";
public static final AsciiString STATUS = new AsciiString(":status");
/**
* {@code ":version"}
*/
public static final String VERSION = ":version";
public static final AsciiString VERSION = new AsciiString(":version");
private HttpNames() { }
}
/**
* Returns the header value with the specified header name. If there are
* more than one header value for the specified header name, the first
* value is returned.
*
* @return the header value or {@code null} if there is no such header
*/
public static String getHeader(SpdyHeadersFrame frame, String name) {
return frame.headers().get(name);
}
/**
* Returns the header value with the specified header name. If there are
* more than one header value for the specified header name, the first
* value is returned.
*
* @return the header value or the {@code defaultValue} if there is no such
* header
*/
public static String getHeader(SpdyHeadersFrame frame, String name, String defaultValue) {
String value = frame.headers().get(name);
if (value == null) {
return defaultValue;
}
return value;
}
/**
* Sets a new header with the specified name and value. If there is an
* existing header with the same name, the existing header is removed.
*/
public static void setHeader(SpdyHeadersFrame frame, String name, Object value) {
frame.headers().set(name, value);
}
/**
* Sets a new header with the specified name and values. If there is an
* existing header with the same name, the existing header is removed.
*/
public static void setHeader(SpdyHeadersFrame frame, String name, Iterable<?> values) {
frame.headers().set(name, values);
}
/**
* Adds a new header with the specified name and value.
*/
public static void addHeader(SpdyHeadersFrame frame, String name, Object value) {
frame.headers().add(name, value);
}
/**
* Removes the SPDY host header.
*/
public static void removeHost(SpdyHeadersFrame frame) {
frame.headers().remove(HttpNames.HOST);
}
/**
* Returns the SPDY host header.
*/
public static String getHost(SpdyHeadersFrame frame) {
return frame.headers().get(HttpNames.HOST);
}
/**
* Set the SPDY host header.
*/
public static void setHost(SpdyHeadersFrame frame, String host) {
frame.headers().set(HttpNames.HOST, host);
}
/**
* Removes the HTTP method header.
*/
public static void removeMethod(int spdyVersion, SpdyHeadersFrame frame) {
frame.headers().remove(HttpNames.METHOD);
}
/**
* Returns the {@link HttpMethod} represented by the HTTP method header.
*/
public static HttpMethod getMethod(int spdyVersion, SpdyHeadersFrame frame) {
try {
return HttpMethod.valueOf(frame.headers().get(HttpNames.METHOD));
} catch (Exception e) {
return null;
}
}
/**
* Sets the HTTP method header.
*/
public static void setMethod(int spdyVersion, SpdyHeadersFrame frame, HttpMethod method) {
frame.headers().set(HttpNames.METHOD, method.name());
}
/**
* Removes the URL scheme header.
*/
public static void removeScheme(int spdyVersion, SpdyHeadersFrame frame) {
frame.headers().remove(HttpNames.SCHEME);
}
/**
* Returns the value of the URL scheme header.
*/
public static String getScheme(int spdyVersion, SpdyHeadersFrame frame) {
return frame.headers().get(HttpNames.SCHEME);
}
/**
* Sets the URL scheme header.
*/
public static void setScheme(int spdyVersion, SpdyHeadersFrame frame, String scheme) {
frame.headers().set(HttpNames.SCHEME, scheme);
}
/**
* Removes the HTTP response status header.
*/
public static void removeStatus(int spdyVersion, SpdyHeadersFrame frame) {
frame.headers().remove(HttpNames.STATUS);
}
/**
* Returns the {@link HttpResponseStatus} represented by the HTTP response status header.
*/
public static HttpResponseStatus getStatus(int spdyVersion, SpdyHeadersFrame frame) {
try {
String status = frame.headers().get(HttpNames.STATUS);
int space = status.indexOf(' ');
if (space == -1) {
return HttpResponseStatus.valueOf(Integer.parseInt(status));
} else {
int code = Integer.parseInt(status.substring(0, space));
String reasonPhrase = status.substring(space + 1);
HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code);
if (responseStatus.reasonPhrase().equals(reasonPhrase)) {
return responseStatus;
} else {
return new HttpResponseStatus(code, reasonPhrase);
}
}
} catch (Exception e) {
return null;
}
}
/**
* Sets the HTTP response status header.
*/
public static void setStatus(int spdyVersion, SpdyHeadersFrame frame, HttpResponseStatus status) {
frame.headers().set(HttpNames.STATUS, status.toString());
}
/**
* Removes the URL path header.
*/
public static void removeUrl(int spdyVersion, SpdyHeadersFrame frame) {
frame.headers().remove(HttpNames.PATH);
}
/**
* Returns the value of the URL path header.
*/
public static String getUrl(int spdyVersion, SpdyHeadersFrame frame) {
return frame.headers().get(HttpNames.PATH);
}
/**
* Sets the URL path header.
*/
public static void setUrl(int spdyVersion, SpdyHeadersFrame frame, String path) {
frame.headers().set(HttpNames.PATH, path);
}
/**
* Removes the HTTP version header.
*/
public static void removeVersion(int spdyVersion, SpdyHeadersFrame frame) {
frame.headers().remove(HttpNames.VERSION);
}
/**
* Returns the {@link HttpVersion} represented by the HTTP version header.
*/
public static HttpVersion getVersion(int spdyVersion, SpdyHeadersFrame frame) {
try {
return HttpVersion.valueOf(frame.headers().get(HttpNames.VERSION));
} catch (Exception e) {
return null;
}
}
/**
* Sets the HTTP version header.
*/
public static void setVersion(int spdyVersion, SpdyHeadersFrame frame, HttpVersion httpVersion) {
frame.headers().set(HttpNames.VERSION, httpVersion.text());
}
@Override
SpdyHeaders add(CharSequence name, Object value);
@Override
public Iterator<Map.Entry<String, String>> iterator() {
return entries().iterator();
}
SpdyHeaders add(CharSequence name, Iterable<?> values);
/**
* Returns the header value with the specified header name. If there is
* more than one header value for the specified header name, the first
* value is returned.
*
* @return the header value or {@code null} if there is no such header
*/
public abstract String get(String name);
@Override
SpdyHeaders add(CharSequence name, Object... values);
/**
* Returns the header values with the specified header name.
*
* @return the {@link List} of header values. An empty list if there is no
* such header.
*/
public abstract List<String> getAll(String name);
@Override
SpdyHeaders add(TextHeaders headers);
/**
* Returns all header names and values that this frame contains.
*
* @return the {@link List} of the header name-value pairs. An empty list
* if there is no header in this message.
*/
public abstract List<Map.Entry<String, String>> entries();
@Override
SpdyHeaders set(CharSequence name, Object value);
/**
* Returns {@code true} if and only if there is a header with the specified
* header name.
*/
public abstract boolean contains(String name);
@Override
SpdyHeaders set(CharSequence name, Iterable<?> values);
/**
* Returns the {@link Set} of all header names that this frame contains.
*/
public abstract Set<String> names();
@Override
SpdyHeaders set(CharSequence name, Object... values);
/**
* Adds a new header with the specified name and value.
*/
public abstract SpdyHeaders add(String name, Object value);
@Override
SpdyHeaders set(TextHeaders headers);
/**
* Adds a new header with the specified name and values. If there is an
* existing header with the same name, the existing header is removed.
*/
public abstract SpdyHeaders add(String name, Iterable<?> values);
@Override
SpdyHeaders clear();
/**
* Sets a new header with the specified name and value. If there is an
* existing header with the same name, the existing header is removed.
*/
public abstract SpdyHeaders set(String name, Object value);
/**
* Sets a new header with the specified name and values. If there is an
* existing header with the same name, the existing header is removed.
*/
public abstract SpdyHeaders set(String name, Iterable<?> values);
/**
* Removes the header with the specified name.
*/
public abstract SpdyHeaders remove(String name);
/**
* Removes all headers from this frame.
*/
public abstract SpdyHeaders clear();
/**
* Checks if no header exists.
*/
public abstract boolean isEmpty();
@Override
SpdyHeaders forEachEntry(TextHeaderProcessor processor);
}

View File

@ -24,15 +24,19 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
/**
* Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s,
* and {@link SpdyDataFrame}s into {@link FullHttpRequest}s and {@link FullHttpResponse}s.
@ -111,7 +115,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
return;
}
String URL = SpdyHeaders.getUrl(spdyVersion, spdySynStreamFrame);
String URL = spdySynStreamFrame.headers().get(PATH);
// If a client receives a SYN_STREAM without a 'url' header
// it must reply with a RST_STREAM with error code PROTOCOL_ERROR
@ -136,19 +140,19 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
createHttpResponse(spdyVersion, spdySynStreamFrame);
// Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers
SpdyHttpHeaders.setStreamId(httpResponseWithEntity, streamId);
SpdyHttpHeaders.setAssociatedToStreamId(httpResponseWithEntity, associatedToStreamId);
SpdyHttpHeaders.setPriority(httpResponseWithEntity, spdySynStreamFrame.getPriority());
SpdyHttpHeaders.setUrl(httpResponseWithEntity, URL);
httpResponseWithEntity.headers().set(Names.STREAM_ID, streamId);
httpResponseWithEntity.headers().set(Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId);
httpResponseWithEntity.headers().set(Names.PRIORITY, spdySynStreamFrame.getPriority());
httpResponseWithEntity.headers().set(Names.URL, URL);
if (spdySynStreamFrame.isLast()) {
HttpHeaders.setContentLength(httpResponseWithEntity, 0);
HttpHeaderUtil.setContentLength(httpResponseWithEntity, 0);
out.add(httpResponseWithEntity);
} else {
// Response body will follow in a series of Data Frames
putMessage(streamId, httpResponseWithEntity);
}
} catch (Exception e) {
} catch (Exception ignored) {
SpdyRstStreamFrame spdyRstStreamFrame =
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
ctx.writeAndFlush(spdyRstStreamFrame);
@ -161,10 +165,9 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
if (spdySynStreamFrame.isTruncated()) {
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
spdySynReplyFrame.setLast(true);
SpdyHeaders.setStatus(spdyVersion,
spdySynReplyFrame,
HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE);
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
frameHeaders.set(STATUS, HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE);
frameHeaders.set(VERSION, HttpVersion.HTTP_1_0);
ctx.writeAndFlush(spdySynReplyFrame);
return;
}
@ -173,7 +176,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
FullHttpRequest httpRequestWithEntity = createHttpRequest(spdyVersion, spdySynStreamFrame);
// Set the Stream-ID as a header
SpdyHttpHeaders.setStreamId(httpRequestWithEntity, streamId);
httpRequestWithEntity.headers().set(Names.STREAM_ID, streamId);
if (spdySynStreamFrame.isLast()) {
out.add(httpRequestWithEntity);
@ -187,8 +190,9 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
// Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
spdySynReplyFrame.setLast(true);
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST);
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
frameHeaders.set(STATUS, HttpResponseStatus.BAD_REQUEST);
frameHeaders.set(VERSION, HttpVersion.HTTP_1_0);
ctx.writeAndFlush(spdySynReplyFrame);
}
}
@ -211,10 +215,10 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
FullHttpResponse httpResponseWithEntity = createHttpResponse(spdyVersion, spdySynReplyFrame);
// Set the Stream-ID as a header
SpdyHttpHeaders.setStreamId(httpResponseWithEntity, streamId);
httpResponseWithEntity.headers().set(Names.STREAM_ID, streamId);
if (spdySynReplyFrame.isLast()) {
HttpHeaders.setContentLength(httpResponseWithEntity, 0);
HttpHeaderUtil.setContentLength(httpResponseWithEntity, 0);
out.add(httpResponseWithEntity);
} else {
// Response body will follow in a series of Data Frames
@ -247,7 +251,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
}
if (spdyHeadersFrame.isLast()) {
HttpHeaders.setContentLength(fullHttpMessage, fullHttpMessage.content().readableBytes());
HttpHeaderUtil.setContentLength(fullHttpMessage, fullHttpMessage.content().readableBytes());
removeMessage(streamId);
out.add(fullHttpMessage);
}
@ -275,7 +279,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen);
if (spdyDataFrame.isLast()) {
HttpHeaders.setContentLength(fullHttpMessage, content.readableBytes());
HttpHeaderUtil.setContentLength(fullHttpMessage, content.readableBytes());
removeMessage(streamId);
out.add(fullHttpMessage);
}
@ -291,23 +295,24 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
private static FullHttpRequest createHttpRequest(int spdyVersion, SpdyHeadersFrame requestFrame)
throws Exception {
// Create the first line of the request from the name/value pairs
HttpMethod method = SpdyHeaders.getMethod(spdyVersion, requestFrame);
String url = SpdyHeaders.getUrl(spdyVersion, requestFrame);
HttpVersion httpVersion = SpdyHeaders.getVersion(spdyVersion, requestFrame);
SpdyHeaders.removeMethod(spdyVersion, requestFrame);
SpdyHeaders.removeUrl(spdyVersion, requestFrame);
SpdyHeaders.removeVersion(spdyVersion, requestFrame);
SpdyHeaders headers = requestFrame.headers();
HttpMethod method = HttpMethod.valueOf(headers.get(METHOD));
String url = headers.get(PATH);
HttpVersion httpVersion = HttpVersion.valueOf(headers.get(VERSION));
headers.remove(METHOD);
headers.remove(PATH);
headers.remove(VERSION);
FullHttpRequest req = new DefaultFullHttpRequest(httpVersion, method, url);
// Remove the scheme header
SpdyHeaders.removeScheme(spdyVersion, requestFrame);
headers.remove(SCHEME);
if (spdyVersion >= 3) {
// Replace the SPDY host header with the HTTP host header
String host = SpdyHeaders.getHost(requestFrame);
SpdyHeaders.removeHost(requestFrame);
HttpHeaders.setHost(req, host);
String host = headers.get(HOST);
headers.remove(HOST);
req.headers().set(HttpHeaders.Names.HOST, host);
}
for (Map.Entry<String, String> e: requestFrame.headers()) {
@ -315,7 +320,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
}
// The Connection and Keep-Alive headers are no longer valid
HttpHeaders.setKeepAlive(req, true);
HttpHeaderUtil.setKeepAlive(req, true);
// Transfer-Encoding header is not valid
req.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
@ -323,13 +328,15 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
return req;
}
private static FullHttpResponse createHttpResponse(int spdyVersion, SpdyHeadersFrame responseFrame)
throws Exception {
private static FullHttpResponse createHttpResponse(
int spdyVersion, SpdyHeadersFrame responseFrame) throws Exception {
// Create the first line of the response from the name/value pairs
HttpResponseStatus status = SpdyHeaders.getStatus(spdyVersion, responseFrame);
HttpVersion version = SpdyHeaders.getVersion(spdyVersion, responseFrame);
SpdyHeaders.removeStatus(spdyVersion, responseFrame);
SpdyHeaders.removeVersion(spdyVersion, responseFrame);
SpdyHeaders headers = responseFrame.headers();
HttpResponseStatus status = HttpResponseStatus.parseLine(headers.get(STATUS));
HttpVersion version = HttpVersion.valueOf(headers.get(VERSION));
headers.remove(STATUS);
headers.remove(VERSION);
FullHttpResponse res = new DefaultFullHttpResponse(version, status);
for (Map.Entry<String, String> e: responseFrame.headers()) {
@ -337,7 +344,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
}
// The Connection and Keep-Alive headers are no longer valid
HttpHeaders.setKeepAlive(res, true);
HttpHeaderUtil.setKeepAlive(res, true);
// Transfer-Encoding header is not valid
res.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);

View File

@ -27,10 +27,13 @@ import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
import java.util.List;
import java.util.Map;
import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
/**
* Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpContent}s
* into {@link SpdySynStreamFrame}s and {@link SpdySynReplyFrame}s.
@ -202,61 +205,62 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
}
}
private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage)
throws Exception {
private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage) throws Exception {
// Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers
int streamID = SpdyHttpHeaders.getStreamId(httpMessage);
int associatedToStreamId = SpdyHttpHeaders.getAssociatedToStreamId(httpMessage);
byte priority = SpdyHttpHeaders.getPriority(httpMessage);
String URL = SpdyHttpHeaders.getUrl(httpMessage);
String scheme = SpdyHttpHeaders.getScheme(httpMessage);
SpdyHttpHeaders.removeStreamId(httpMessage);
SpdyHttpHeaders.removeAssociatedToStreamId(httpMessage);
SpdyHttpHeaders.removePriority(httpMessage);
SpdyHttpHeaders.removeUrl(httpMessage);
SpdyHttpHeaders.removeScheme(httpMessage);
final HttpHeaders httpHeaders = httpMessage.headers();
int streamID = httpHeaders.getInt(Names.STREAM_ID);
int associatedToStreamId = httpHeaders.getInt(Names.ASSOCIATED_TO_STREAM_ID);
byte priority = (byte) httpHeaders.getInt(Names.PRIORITY, 0);
String URL = httpHeaders.get(Names.URL);
String scheme = httpHeaders.get(Names.SCHEME);
httpHeaders.remove(Names.STREAM_ID);
httpHeaders.remove(Names.ASSOCIATED_TO_STREAM_ID);
httpHeaders.remove(Names.PRIORITY);
httpHeaders.remove(Names.URL);
httpHeaders.remove(Names.SCHEME);
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
// headers are not valid and MUST not be sent.
httpMessage.headers().remove(HttpHeaders.Names.CONNECTION);
httpMessage.headers().remove("Keep-Alive");
httpMessage.headers().remove("Proxy-Connection");
httpMessage.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
httpHeaders.remove(HttpHeaders.Names.CONNECTION);
httpHeaders.remove("Keep-Alive");
httpHeaders.remove("Proxy-Connection");
httpHeaders.remove(HttpHeaders.Names.TRANSFER_ENCODING);
SpdySynStreamFrame spdySynStreamFrame =
new DefaultSpdySynStreamFrame(streamID, associatedToStreamId, priority);
// Unfold the first line of the message into name/value pairs
SpdyHeaders frameHeaders = spdySynStreamFrame.headers();
if (httpMessage instanceof FullHttpRequest) {
HttpRequest httpRequest = (HttpRequest) httpMessage;
SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod());
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri());
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
frameHeaders.set(METHOD, httpRequest.getMethod());
frameHeaders.set(PATH, httpRequest.getUri());
frameHeaders.set(VERSION, httpMessage.getProtocolVersion());
}
if (httpMessage instanceof HttpResponse) {
HttpResponse httpResponse = (HttpResponse) httpMessage;
SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus());
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL);
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
frameHeaders.set(STATUS, httpResponse.getStatus());
frameHeaders.set(PATH, URL);
frameHeaders.set(VERSION, httpMessage.getProtocolVersion());
spdySynStreamFrame.setUnidirectional(true);
}
// Replace the HTTP host header with the SPDY host header
if (spdyVersion >= 3) {
String host = HttpHeaders.getHost(httpMessage);
httpMessage.headers().remove(HttpHeaders.Names.HOST);
SpdyHeaders.setHost(spdySynStreamFrame, host);
CharSequence host = httpHeaders.getUnconverted(HttpHeaders.Names.HOST);
httpHeaders.remove(HttpHeaders.Names.HOST);
frameHeaders.set(HOST, host);
}
// Set the SPDY scheme header
if (scheme == null) {
scheme = "https";
}
SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme);
frameHeaders.set(SCHEME, scheme);
// Transfer the remaining HTTP headers
for (Map.Entry<String, String> entry: httpMessage.headers()) {
spdySynStreamFrame.headers().add(entry.getKey(), entry.getValue());
for (Map.Entry<String, String> entry: httpHeaders) {
frameHeaders.add(entry.getKey(), entry.getValue());
}
currentStreamId = spdySynStreamFrame.getStreamId();
spdySynStreamFrame.setLast(isLast(httpMessage));
@ -264,27 +268,27 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
return spdySynStreamFrame;
}
private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse)
throws Exception {
private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse) throws Exception {
// Get the Stream-ID from the headers
int streamID = SpdyHttpHeaders.getStreamId(httpResponse);
SpdyHttpHeaders.removeStreamId(httpResponse);
final HttpHeaders httpHeaders = httpResponse.headers();
int streamID = httpHeaders.getInt(Names.STREAM_ID);
httpHeaders.remove(Names.STREAM_ID);
// The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding
// headers are not valid and MUST not be sent.
httpResponse.headers().remove(HttpHeaders.Names.CONNECTION);
httpResponse.headers().remove("Keep-Alive");
httpResponse.headers().remove("Proxy-Connection");
httpResponse.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
httpHeaders.remove(HttpHeaders.Names.CONNECTION);
httpHeaders.remove("Keep-Alive");
httpHeaders.remove("Proxy-Connection");
httpHeaders.remove(HttpHeaders.Names.TRANSFER_ENCODING);
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
// Unfold the first line of the response into name/value pairs
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus());
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion());
frameHeaders.set(STATUS, httpResponse.getStatus());
frameHeaders.set(VERSION, httpResponse.getProtocolVersion());
// Transfer the remaining HTTP headers
for (Map.Entry<String, String> entry: httpResponse.headers()) {
for (Map.Entry<String, String> entry: httpHeaders) {
spdySynReplyFrame.headers().add(entry.getKey(), entry.getValue());
}

View File

@ -15,12 +15,10 @@
*/
package io.netty.handler.codec.spdy;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.AsciiString;
/**
* Provides the constants for the header names and the utility methods
* used by the {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}.
* Provides the constants for the header names used by the {@link SpdyHttpDecoder} and {@link SpdyHttpEncoder}.
*/
public final class SpdyHttpHeaders {
@ -31,138 +29,26 @@ public final class SpdyHttpHeaders {
/**
* {@code "X-SPDY-Stream-ID"}
*/
public static final String STREAM_ID = "X-SPDY-Stream-ID";
public static final AsciiString STREAM_ID = new AsciiString("X-SPDY-Stream-ID");
/**
* {@code "X-SPDY-Associated-To-Stream-ID"}
*/
public static final String ASSOCIATED_TO_STREAM_ID = "X-SPDY-Associated-To-Stream-ID";
public static final AsciiString ASSOCIATED_TO_STREAM_ID = new AsciiString("X-SPDY-Associated-To-Stream-ID");
/**
* {@code "X-SPDY-Priority"}
*/
public static final String PRIORITY = "X-SPDY-Priority";
public static final AsciiString PRIORITY = new AsciiString("X-SPDY-Priority");
/**
* {@code "X-SPDY-URL"}
*/
public static final String URL = "X-SPDY-URL";
public static final AsciiString URL = new AsciiString("X-SPDY-URL");
/**
* {@code "X-SPDY-Scheme"}
*/
public static final String SCHEME = "X-SPDY-Scheme";
public static final AsciiString SCHEME = new AsciiString("X-SPDY-Scheme");
private Names() { }
}
private SpdyHttpHeaders() {
}
/**
* Removes the {@code "X-SPDY-Stream-ID"} header.
*/
public static void removeStreamId(HttpMessage message) {
message.headers().remove(Names.STREAM_ID);
}
/**
* Returns the value of the {@code "X-SPDY-Stream-ID"} header.
*/
public static int getStreamId(HttpMessage message) {
return HttpHeaders.getIntHeader(message, Names.STREAM_ID);
}
/**
* Sets the {@code "X-SPDY-Stream-ID"} header.
*/
public static void setStreamId(HttpMessage message, int streamId) {
HttpHeaders.setIntHeader(message, Names.STREAM_ID, streamId);
}
/**
* Removes the {@code "X-SPDY-Associated-To-Stream-ID"} header.
*/
public static void removeAssociatedToStreamId(HttpMessage message) {
message.headers().remove(Names.ASSOCIATED_TO_STREAM_ID);
}
/**
* Returns the value of the {@code "X-SPDY-Associated-To-Stream-ID"} header.
*
* @return the header value or {@code 0} if there is no such header or
* if the header value is not a number
*/
public static int getAssociatedToStreamId(HttpMessage message) {
return HttpHeaders.getIntHeader(message, Names.ASSOCIATED_TO_STREAM_ID, 0);
}
/**
* Sets the {@code "X-SPDY-Associated-To-Stream-ID"} header.
*/
public static void setAssociatedToStreamId(HttpMessage message, int associatedToStreamId) {
HttpHeaders.setIntHeader(message, Names.ASSOCIATED_TO_STREAM_ID, associatedToStreamId);
}
/**
* Removes the {@code "X-SPDY-Priority"} header.
*/
public static void removePriority(HttpMessage message) {
message.headers().remove(Names.PRIORITY);
}
/**
* Returns the value of the {@code "X-SPDY-Priority"} header.
*
* @return the header value or {@code 0} if there is no such header or
* if the header value is not a number
*/
public static byte getPriority(HttpMessage message) {
return (byte) HttpHeaders.getIntHeader(message, Names.PRIORITY, 0);
}
/**
* Sets the {@code "X-SPDY-Priority"} header.
*/
public static void setPriority(HttpMessage message, byte priority) {
HttpHeaders.setIntHeader(message, Names.PRIORITY, priority);
}
/**
* Removes the {@code "X-SPDY-URL"} header.
*/
public static void removeUrl(HttpMessage message) {
message.headers().remove(Names.URL);
}
/**
* Returns the value of the {@code "X-SPDY-URL"} header.
*/
public static String getUrl(HttpMessage message) {
return message.headers().get(Names.URL);
}
/**
* Sets the {@code "X-SPDY-URL"} header.
*/
public static void setUrl(HttpMessage message, String url) {
message.headers().set(Names.URL, url);
}
/**
* Removes the {@code "X-SPDY-Scheme"} header.
*/
public static void removeScheme(HttpMessage message) {
message.headers().remove(Names.SCHEME);
}
/**
* Returns the value of the {@code "X-SPDY-Scheme"} header.
*/
public static String getScheme(HttpMessage message) {
return message.headers().get(Names.SCHEME);
}
/**
* Sets the {@code "X-SPDY-Scheme"} header.
*/
public static void setScheme(HttpMessage message, String scheme) {
message.headers().set(Names.SCHEME, scheme);
}
private SpdyHttpHeaders() { }
}

View File

@ -18,6 +18,7 @@ package io.netty.handler.codec.spdy;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
import io.netty.util.ReferenceCountUtil;
import java.util.LinkedList;
@ -43,7 +44,7 @@ public class SpdyHttpResponseStreamIdHandler extends
protected void encode(ChannelHandlerContext ctx, HttpMessage msg, List<Object> out) throws Exception {
Integer id = ids.poll();
if (id != null && id.intValue() != NO_ID && !msg.headers().contains(SpdyHttpHeaders.Names.STREAM_ID)) {
SpdyHttpHeaders.setStreamId(msg, id);
msg.headers().set(Names.STREAM_ID, id);
}
out.add(ReferenceCountUtil.retain(msg));
@ -56,7 +57,7 @@ public class SpdyHttpResponseStreamIdHandler extends
if (!contains) {
ids.add(NO_ID);
} else {
ids.add(SpdyHttpHeaders.getStreamId((HttpMessage) msg));
ids.add(((HttpMessage) msg).headers().getInt(Names.STREAM_ID));
}
} else if (msg instanceof SpdyRstStreamFrame) {
ids.remove(((SpdyRstStreamFrame) msg).getStreamId());

View File

@ -15,22 +15,23 @@
*/
package io.netty.handler.codec.http;
import org.junit.Assert;
import io.netty.handler.codec.AsciiString;
import org.junit.Test;
import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
public class HttpHeadersTest {
public class HttpHeaderUtilTest {
@Test
public void testRemoveTransferEncodingIgnoreCase() {
HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
message.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, "Chunked");
HttpHeaders.removeTransferEncodingChunked(message);
Assert.assertTrue(message.headers().isEmpty());
assertFalse(message.headers().isEmpty());
HttpHeaderUtil.setTransferEncodingChunked(message, false);
assertTrue(message.headers().isEmpty());
}
// Test for https://github.com/netty/netty/issues/1690
@ -40,19 +41,19 @@ public class HttpHeadersTest {
headers.add("Foo", "1");
headers.add("Foo", "2");
Assert.assertEquals("1", headers.get("Foo"));
assertEquals("1", headers.get("Foo"));
List<String> values = headers.getAll("Foo");
Assert.assertEquals(2, values.size());
Assert.assertEquals("1", values.get(0));
Assert.assertEquals("2", values.get(1));
assertEquals(2, values.size());
assertEquals("1", values.get(0));
assertEquals("2", values.get(1));
}
@Test
public void testEquansIgnoreCase() {
assertThat(HttpHeaders.equalsIgnoreCase(null, null), is(true));
assertThat(HttpHeaders.equalsIgnoreCase(null, "foo"), is(false));
assertThat(HttpHeaders.equalsIgnoreCase("bar", null), is(false));
assertThat(HttpHeaders.equalsIgnoreCase("FoO", "fOo"), is(true));
assertThat(AsciiString.equalsIgnoreCase(null, null), is(true));
assertThat(AsciiString.equalsIgnoreCase(null, "foo"), is(false));
assertThat(AsciiString.equalsIgnoreCase("bar", null), is(false));
assertThat(AsciiString.equalsIgnoreCase("FoO", "fOo"), is(true));
}
}

View File

@ -41,7 +41,7 @@ public class HttpObjectAggregatorTest {
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost");
HttpHeaders.setHeader(message, "X-Test", true);
message.headers().set("X-Test", true);
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
HttpContent chunk3 = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
@ -56,7 +56,7 @@ public class HttpObjectAggregatorTest {
assertNotNull(aggratedMessage);
assertEquals(chunk1.content().readableBytes() + chunk2.content().readableBytes(),
HttpHeaders.getContentLength(aggratedMessage));
HttpHeaderUtil.getContentLength(aggratedMessage));
assertEquals(aggratedMessage.headers().get("X-Test"), Boolean.TRUE.toString());
checkContentBuffer(aggratedMessage);
assertNull(embedder.readInbound());
@ -79,8 +79,8 @@ public class HttpObjectAggregatorTest {
HttpObjectAggregator aggr = new HttpObjectAggregator(1024 * 1024);
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "http://localhost");
HttpHeaders.setHeader(message, "X-Test", true);
HttpHeaders.setTransferEncodingChunked(message);
message.headers().set("X-Test", true);
HttpHeaderUtil.setTransferEncodingChunked(message, true);
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
LastHttpContent trailer = new DefaultLastHttpContent();
@ -97,7 +97,7 @@ public class HttpObjectAggregatorTest {
assertNotNull(aggratedMessage);
assertEquals(chunk1.content().readableBytes() + chunk2.content().readableBytes(),
HttpHeaders.getContentLength(aggratedMessage));
HttpHeaderUtil.getContentLength(aggratedMessage));
assertEquals(aggratedMessage.headers().get("X-Test"), Boolean.TRUE.toString());
assertEquals(aggratedMessage.headers().get("X-Trailer"), Boolean.TRUE.toString());
checkContentBuffer(aggratedMessage);
@ -135,14 +135,14 @@ public class HttpObjectAggregatorTest {
public void testOversizedRequestWithoutKeepAlive() {
// send a HTTP/1.0 request with no keep-alive header
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.PUT, "http://localhost");
HttpHeaders.setContentLength(message, 5);
HttpHeaderUtil.setContentLength(message, 5);
checkOversizedRequest(message);
}
@Test
public void testOversizedRequestWithContentLength() {
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
HttpHeaders.setContentLength(message, 5);
HttpHeaderUtil.setContentLength(message, 5);
checkOversizedRequest(message);
}
@ -152,8 +152,8 @@ public class HttpObjectAggregatorTest {
// send an oversized request with 100 continue
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
HttpHeaders.set100ContinueExpected(message);
HttpHeaders.setContentLength(message, 16);
HttpHeaderUtil.set100ContinueExpected(message, true);
HttpHeaderUtil.setContentLength(message, 16);
HttpContent chunk1 = releaseLater(new DefaultHttpContent(Unpooled.copiedBuffer("some", CharsetUtil.US_ASCII)));
HttpContent chunk2 = releaseLater(new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII)));
@ -185,9 +185,9 @@ public class HttpObjectAggregatorTest {
assertEquals(
chunk2.content().readableBytes() + chunk3.content().readableBytes(),
HttpHeaders.getContentLength(fullMsg));
HttpHeaderUtil.getContentLength(fullMsg));
assertEquals(HttpHeaders.getContentLength(fullMsg), fullMsg.content().readableBytes());
assertEquals(HttpHeaderUtil.getContentLength(fullMsg), fullMsg.content().readableBytes());
fullMsg.release();
assertFalse(embedder.finish());
@ -280,8 +280,8 @@ public class HttpObjectAggregatorTest {
EmbeddedChannel embedder = new EmbeddedChannel(aggr);
HttpRequest message = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "http://localhost");
HttpHeaders.setHeader(message, "X-Test", true);
HttpHeaders.setHeader(message, "Transfer-Encoding", "Chunked");
message.headers().set("X-Test", true);
message.headers().set("Transfer-Encoding", "Chunked");
HttpContent chunk1 = new DefaultHttpContent(Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII));
HttpContent chunk2 = new DefaultHttpContent(Unpooled.copiedBuffer("test2", CharsetUtil.US_ASCII));
HttpContent chunk3 = LastHttpContent.EMPTY_LAST_CONTENT;
@ -296,7 +296,7 @@ public class HttpObjectAggregatorTest {
assertNotNull(aggratedMessage);
assertEquals(chunk1.content().readableBytes() + chunk2.content().readableBytes(),
HttpHeaders.getContentLength(aggratedMessage));
HttpHeaderUtil.getContentLength(aggratedMessage));
assertEquals(aggratedMessage.headers().get("X-Test"), Boolean.TRUE.toString());
checkContentBuffer(aggratedMessage);
assertNull(embedder.readInbound());

View File

@ -15,7 +15,6 @@
*/
package io.netty.handler.codec.http.cors;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpMethod;
import org.junit.Test;
@ -115,7 +114,7 @@ public class CorsConfigTest {
@Test
public void emptyPreflightResponseHeaders() {
final CorsConfig cors = withAnyOrigin().noPreflightResponseHeaders().build();
assertThat(cors.preflightResponseHeaders(), equalTo(HttpHeaders.EMPTY_HEADERS));
assertThat(cors.preflightResponseHeaders().size(), equalTo(0));
}
@Test (expected = IllegalArgumentException.class)

View File

@ -0,0 +1,84 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.stomp;
import io.netty.handler.codec.DefaultTextHeaders;
import io.netty.handler.codec.TextHeaderProcessor;
import io.netty.handler.codec.TextHeaders;
public class DefaultStompHeaders extends DefaultTextHeaders implements StompHeaders {
@Override
public StompHeaders add(CharSequence name, Object value) {
super.add(name, value);
return this;
}
@Override
public StompHeaders add(CharSequence name, Iterable<?> values) {
super.add(name, values);
return this;
}
@Override
public StompHeaders add(CharSequence name, Object... values) {
super.add(name, values);
return this;
}
@Override
public StompHeaders add(TextHeaders headers) {
super.add(headers);
return this;
}
@Override
public StompHeaders set(CharSequence name, Object value) {
super.set(name, value);
return this;
}
@Override
public StompHeaders set(CharSequence name, Object... values) {
super.set(name, values);
return this;
}
@Override
public StompHeaders set(CharSequence name, Iterable<?> values) {
super.set(name, values);
return this;
}
@Override
public StompHeaders set(TextHeaders headers) {
super.set(headers);
return this;
}
@Override
public StompHeaders clear() {
super.clear();
return this;
}
@Override
public StompHeaders forEachEntry(TextHeaderProcessor processor) {
super.forEachEntry(processor);
return this;
}
}

View File

@ -24,7 +24,7 @@ public class DefaultStompHeadersSubframe implements StompHeadersSubframe {
protected final StompCommand command;
protected DecoderResult decoderResult = DecoderResult.SUCCESS;
protected final StompHeaders headers = new StompHeaders();
protected final StompHeaders headers = new DefaultStompHeaders();
public DefaultStompHeadersSubframe(StompCommand command) {
if (command == null) {

View File

@ -15,93 +15,63 @@
*/
package io.netty.handler.codec.stomp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.TextHeaderProcessor;
import io.netty.handler.codec.TextHeaders;
/**
* Provides the constants for the standard STOMP header names and values and
* commonly used utility methods that accesses an {@link StompHeadersSubframe}.
* The multimap data structure for the STOMP header names and values. It also provides the constants for the standard
* STOMP header names and values.
*/
public class StompHeaders {
public interface StompHeaders extends TextHeaders {
public static final String ACCEPT_VERSION = "accept-version";
public static final String HOST = "host";
public static final String LOGIN = "login";
public static final String PASSCODE = "passcode";
public static final String HEART_BEAT = "heart-beat";
public static final String VERSION = "version";
public static final String SESSION = "session";
public static final String SERVER = "server";
public static final String DESTINATION = "destination";
public static final String ID = "id";
public static final String ACK = "ack";
public static final String TRANSACTION = "transaction";
public static final String RECEIPT = "receipt";
public static final String MESSAGE_ID = "message-id";
public static final String SUBSCRIPTION = "subscription";
public static final String RECEIPT_ID = "receipt-id";
public static final String MESSAGE = "message";
public static final String CONTENT_LENGTH = "content-length";
public static final String CONTENT_TYPE = "content-type";
private final Map<String, List<String>> headers = new LinkedHashMap<String, List<String>>();
public boolean has(String key) {
List<String> values = headers.get(key);
return values != null && !values.isEmpty();
}
public String get(String key) {
List<String> values = headers.get(key);
if (values != null && !values.isEmpty()) {
return values.get(0);
} else {
return null;
}
}
public void add(String key, String value) {
List<String> values = headers.get(key);
if (values == null) {
values = new ArrayList<String>();
headers.put(key, values);
}
values.add(value);
}
@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
public void set(String key, String value) {
headers.put(key, Arrays.asList(value));
}
public List<String> getAll(String key) {
List<String> values = headers.get(key);
if (values != null) {
return new ArrayList<String>(values);
} else {
return new ArrayList<String>();
}
}
public Set<String> keySet() {
return headers.keySet();
}
AsciiString ACCEPT_VERSION = new AsciiString("accept-version");
AsciiString HOST = new AsciiString("host");
AsciiString LOGIN = new AsciiString("login");
AsciiString PASSCODE = new AsciiString("passcode");
AsciiString HEART_BEAT = new AsciiString("heart-beat");
AsciiString VERSION = new AsciiString("version");
AsciiString SESSION = new AsciiString("session");
AsciiString SERVER = new AsciiString("server");
AsciiString DESTINATION = new AsciiString("destination");
AsciiString ID = new AsciiString("id");
AsciiString ACK = new AsciiString("ack");
AsciiString TRANSACTION = new AsciiString("transaction");
AsciiString RECEIPT = new AsciiString("receipt");
AsciiString MESSAGE_ID = new AsciiString("message-id");
AsciiString SUBSCRIPTION = new AsciiString("subscription");
AsciiString RECEIPT_ID = new AsciiString("receipt-id");
AsciiString MESSAGE = new AsciiString("message");
AsciiString CONTENT_LENGTH = new AsciiString("content-length");
AsciiString CONTENT_TYPE = new AsciiString("content-type");
@Override
public String toString() {
return "StompHeaders{" +
headers +
'}';
}
StompHeaders add(CharSequence name, Object value);
public void set(StompHeaders headers) {
for (String key: headers.keySet()) {
List<String> values = headers.getAll(key);
this.headers.put(key, values);
}
}
@Override
StompHeaders add(CharSequence name, Iterable<?> values);
@Override
StompHeaders add(CharSequence name, Object... values);
@Override
StompHeaders add(TextHeaders headers);
@Override
StompHeaders set(CharSequence name, Object value);
@Override
StompHeaders set(CharSequence name, Iterable<?> values);
@Override
StompHeaders set(CharSequence name, Object... values);
@Override
StompHeaders set(TextHeaders headers);
@Override
StompHeaders clear();
@Override
StompHeaders forEachEntry(TextHeaderProcessor processor);
}

View File

@ -18,6 +18,7 @@ package io.netty.handler.codec.stomp;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.MessageAggregator;
import io.netty.handler.codec.TooLongFrameException;
@ -64,12 +65,17 @@ public class StompSubframeAggregator
@Override
protected boolean hasContentLength(StompHeadersSubframe start) throws Exception {
return start.headers().has(StompHeaders.CONTENT_LENGTH);
return start.headers().contains(StompHeaders.CONTENT_LENGTH);
}
@Override
protected long contentLength(StompHeadersSubframe start) throws Exception {
return Long.parseLong(start.headers().get(StompHeaders.CONTENT_LENGTH));
CharSequence value = start.headers().get(StompHeaders.CONTENT_LENGTH);
if (value instanceof AsciiString) {
return ((AsciiString) value).parseLong();
}
return Long.parseLong(value.toString());
}
@Override

View File

@ -18,6 +18,7 @@ package io.netty.handler.codec.stomp;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.ReplayingDecoder;
@ -199,7 +200,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
}
} else {
long contentLength = -1;
if (headers.has(StompHeaders.CONTENT_LENGTH)) {
if (headers.contains(StompHeaders.CONTENT_LENGTH)) {
contentLength = getContentLength(headers, 0);
} else {
int globalIndex = indexOf(buffer, buffer.readerIndex(),
@ -219,10 +220,14 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
}
private static long getContentLength(StompHeaders headers, long defaultValue) {
String contentLength = headers.get(StompHeaders.CONTENT_LENGTH);
CharSequence contentLength = headers.get(StompHeaders.CONTENT_LENGTH);
if (contentLength != null) {
try {
return Long.parseLong(contentLength);
if (contentLength instanceof AsciiString) {
return ((AsciiString) contentLength).parseLong();
} else {
return Long.parseLong(contentLength.toString());
}
} catch (NumberFormatException ignored) {
return defaultValue;
}

View File

@ -17,6 +17,9 @@ package io.netty.handler.codec.stomp;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.AsciiHeadersEncoder;
import io.netty.handler.codec.AsciiHeadersEncoder.NewlineType;
import io.netty.handler.codec.AsciiHeadersEncoder.SeparatorType;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.util.CharsetUtil;
@ -61,18 +64,9 @@ public class StompSubframeEncoder extends MessageToMessageEncoder<StompSubframe>
ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII));
buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF);
StompHeaders headers = frame.headers();
for (String k: headers.keySet()) {
List<String> values = headers.getAll(k);
for (String v: values) {
buf.writeBytes(k.getBytes(CharsetUtil.US_ASCII)).
writeByte(StompConstants.COLON).writeBytes(v.getBytes(CharsetUtil.US_ASCII));
buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF);
}
}
buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF);
buf.writeByte(StompConstants.LF);
frame.headers().forEachEntry(new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF));
buf.writeByte(StompConstants.LF);
return buf;
}
}

View File

@ -17,29 +17,29 @@ package io.netty.handler.codec.stomp;
public final class StompTestConstants {
public static final String CONNECT_FRAME =
"CONNECT\r\n" +
"host:stomp.github.org\r\n" +
"accept-version:1.1,1.2\r\n" +
"\r\n" +
"CONNECT\n" +
"host:stomp.github.org\n" +
"accept-version:1.1,1.2\n" +
'\n' +
'\0';
public static final String CONNECTED_FRAME =
"CONNECTED\r\n" +
"CONNECTED\n" +
"version:1.2\n" +
"\r\n" +
'\n' +
"\0\n";
public static final String SEND_FRAME_1 =
"SEND\r\n" +
"SEND\n" +
"destination:/queue/a\n" +
"content-type:text/plain\n" +
"\r\n" +
'\n' +
"hello, queue a!" +
"\0\n";
public static final String SEND_FRAME_2 =
"SEND\r\n" +
"SEND\n" +
"destination:/queue/a\n" +
"content-type:text/plain\n" +
"content-length:17\n" +
"\r\n" +
'\n' +
"hello, queue a!!!" +
"\0\n";

View File

@ -0,0 +1,139 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec;
import io.netty.buffer.ByteBuf;
public final class AsciiHeadersEncoder implements TextHeaderProcessor {
/**
* The separator characters to insert between a header name and a header value.
*/
public enum SeparatorType {
/**
* {@code ':'}
*/
COLON,
/**
* {@code ': '}
*/
COLON_SPACE,
}
/**
* The newline characters to insert between header entries.
*/
public enum NewlineType {
/**
* {@code '\n'}
*/
LF,
/**
* {@code '\r\n'}
*/
CRLF
}
private final ByteBuf buf;
private final SeparatorType separatorType;
private final NewlineType newlineType;
public AsciiHeadersEncoder(ByteBuf buf) {
this(buf, SeparatorType.COLON_SPACE, NewlineType.CRLF);
}
public AsciiHeadersEncoder(ByteBuf buf, SeparatorType separatorType, NewlineType newlineType) {
if (buf == null) {
throw new NullPointerException("buf");
}
if (separatorType == null) {
throw new NullPointerException("separatorType");
}
if (newlineType == null) {
throw new NullPointerException("newlineType");
}
this.buf = buf;
this.separatorType = separatorType;
this.newlineType = newlineType;
}
@Override
public boolean process(CharSequence name, CharSequence value) throws Exception {
final ByteBuf buf = this.buf;
final int nameLen = name.length();
final int valueLen = value.length();
final int entryLen = nameLen + valueLen + 4;
int offset = buf.writerIndex();
buf.ensureWritable(entryLen);
writeAscii(buf, offset, name, nameLen);
offset += nameLen;
switch (separatorType) {
case COLON:
buf.setByte(offset ++, ':');
break;
case COLON_SPACE:
buf.setByte(offset ++, ':');
buf.setByte(offset ++, ' ');
break;
default:
throw new Error();
}
writeAscii(buf, offset, value, valueLen);
offset += valueLen;
switch (newlineType) {
case LF:
buf.setByte(offset ++, '\n');
break;
case CRLF:
buf.setByte(offset ++, '\r');
buf.setByte(offset ++, '\n');
break;
default:
throw new Error();
}
buf.writerIndex(offset);
return true;
}
private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) {
if (value instanceof AsciiString) {
writeAsciiString(buf, offset, (AsciiString) value, valueLen);
} else {
writeCharSequence(buf, offset, value, valueLen);
}
}
private static void writeAsciiString(ByteBuf buf, int offset, AsciiString value, int valueLen) {
value.copy(0, buf, offset, valueLen);
}
private static void writeCharSequence(ByteBuf buf, int offset, CharSequence value, int valueLen) {
for (int i = 0; i < valueLen; i ++) {
buf.setByte(offset ++, c2b(value.charAt(i)));
}
}
private static int c2b(char ch) {
return ch < 256? (byte) ch : '?';
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,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();
}
}
}

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

View File

@ -0,0 +1,21 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec;
public interface TextHeaderProcessor {
boolean process(CharSequence name, CharSequence value) throws Exception;
}

View File

@ -0,0 +1,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);
}

View File

@ -22,6 +22,7 @@ import java.security.cert.X509Certificate;
public final class EmptyArrays {
public static final byte[] EMPTY_BYTES = new byte[0];
public static final char[] EMPTY_CHARS = new char[0];
public static final boolean[] EMPTY_BOOLEANS = new boolean[0];
public static final double[] EMPTY_DOUBLES = new double[0];
public static final float[] EMPTY_FLOATS = new float[0];

View File

@ -28,6 +28,7 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
@ -52,7 +53,6 @@ import java.util.TimeZone;
import java.util.regex.Pattern;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpHeaders.*;
import static io.netty.handler.codec.http.HttpMethod.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.*;
@ -174,10 +174,10 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
long fileLength = raf.length();
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
setContentLength(response, fileLength);
HttpHeaderUtil.setContentLength(response, fileLength);
setContentTypeHeader(response, file);
setDateAndCacheHeaders(response, file);
if (isKeepAlive(request)) {
if (HttpHeaderUtil.isKeepAlive(request)) {
response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}
@ -214,7 +214,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<Ful
ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
// Decide whether to close the connection or not.
if (!isKeepAlive(request)) {
if (!HttpHeaderUtil.isKeepAlive(request)) {
// Close the connection when the whole content is written out.
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
}

View File

@ -21,13 +21,15 @@ import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.HttpRequest;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpHeaders.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.*;
public class HttpHelloWorldServerHandler extends ChannelHandlerAdapter {
private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' };
@ -41,10 +43,10 @@ public class HttpHelloWorldServerHandler extends ChannelHandlerAdapter {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
if (is100ContinueExpected(req)) {
if (HttpHeaderUtil.is100ContinueExpected(req)) {
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
}
boolean keepAlive = isKeepAlive(req);
boolean keepAlive = HttpHeaderUtil.isKeepAlive(req);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT));
response.headers().set(CONTENT_TYPE, "text/plain");
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());

View File

@ -18,7 +18,7 @@ package io.netty.example.http.snoop;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
@ -44,7 +44,7 @@ public class HttpSnoopClientHandler extends SimpleChannelInboundHandler<HttpObje
System.err.println();
}
if (HttpHeaders.isTransferEncodingChunked(response)) {
if (HttpHeaderUtil.isTransferEncodingChunked(response)) {
System.err.println("CHUNKED CONTENT {");
} else {
System.err.println("CONTENT {");

View File

@ -26,6 +26,7 @@ import io.netty.handler.codec.http.CookieDecoder;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
@ -40,7 +41,6 @@ import java.util.Map.Entry;
import java.util.Set;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpHeaders.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.*;
@ -60,7 +60,7 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object>
if (msg instanceof HttpRequest) {
HttpRequest request = this.request = (HttpRequest) msg;
if (is100ContinueExpected(request)) {
if (HttpHeaderUtil.is100ContinueExpected(request)) {
send100Continue(ctx);
}
@ -69,7 +69,7 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object>
buf.append("===================================\r\n");
buf.append("VERSION: ").append(request.getProtocolVersion()).append("\r\n");
buf.append("HOSTNAME: ").append(getHost(request, "unknown")).append("\r\n");
buf.append("HOSTNAME: ").append(request.headers().get(HOST, "unknown")).append("\r\n");
buf.append("REQUEST_URI: ").append(request.getUri()).append("\r\n\r\n");
HttpHeaders headers = request.headers();
@ -145,7 +145,7 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler<Object>
private boolean writeResponse(HttpObject currentObj, ChannelHandlerContext ctx) {
// Decide whether to close the connection or not.
boolean keepAlive = isKeepAlive(request);
boolean keepAlive = HttpHeaderUtil.isKeepAlive(request);
// Build the response object.
FullHttpResponse response = new DefaultFullHttpResponse(
HTTP_1_1, currentObj.decoderResult().isSuccess()? OK : BAD_REQUEST,

View File

@ -18,7 +18,7 @@ package io.netty.example.http.upload;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
@ -47,7 +47,7 @@ public class HttpUploadClientHandler extends SimpleChannelInboundHandler<HttpObj
}
}
if (response.getStatus().code() == 200 && HttpHeaders.isTransferEncodingChunked(response)) {
if (response.getStatus().code() == 200 && HttpHeaderUtil.isTransferEncodingChunked(response)) {
readingChunks = true;
System.err.println("CHUNKED CONTENT {");
} else {

View File

@ -26,6 +26,7 @@ import io.netty.handler.codec.http.CookieDecoder;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
@ -157,7 +158,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObj
return;
}
readingChunks = HttpHeaders.isTransferEncodingChunked(request);
readingChunks = HttpHeaderUtil.isTransferEncodingChunked(request);
responseContent.append("Is Chunked: " + readingChunks + "\r\n");
responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n");
if (readingChunks) {

View File

@ -24,6 +24,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
@ -34,7 +35,6 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpHeaders.*;
import static io.netty.handler.codec.http.HttpMethod.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.*;
@ -81,7 +81,7 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content);
res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
setContentLength(res, content.readableBytes());
HttpHeaderUtil.setContentLength(res, content.readableBytes());
sendHttpResponse(ctx, req, res);
return;
@ -132,12 +132,12 @@ public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>
ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
setContentLength(res, res.content().readableBytes());
HttpHeaderUtil.setContentLength(res, res.content().readableBytes());
}
// Send the response and close the connection if necessary.
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!isKeepAlive(req) || res.getStatus().code() != 200) {
if (!HttpHeaderUtil.isKeepAlive(req) || res.getStatus().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}

View File

@ -21,6 +21,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpRequest;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
@ -35,10 +36,10 @@ public class HelloWorldHttp1Handler extends SimpleChannelInboundHandler<HttpRequ
@Override
public void messageReceived(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
if (is100ContinueExpected(req)) {
if (HttpHeaderUtil.is100ContinueExpected(req)) {
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
}
boolean keepAlive = isKeepAlive(req);
boolean keepAlive = HttpHeaderUtil.isKeepAlive(req);
ByteBuf content = ctx.alloc().buffer();
content.writeBytes(HelloWorldHttp2Handler.RESPONSE_BYTES);

View File

@ -20,7 +20,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.example.http.snoop.HttpSnoopClientHandler;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
@ -55,7 +55,7 @@ public class HttpResponseClientHandler extends SimpleChannelInboundHandler<HttpO
System.out.println();
}
if (HttpHeaders.isTransferEncodingChunked(response)) {
if (HttpHeaderUtil.isTransferEncodingChunked(response)) {
System.out.println("CHUNKED CONTENT {");
} else {
System.out.println("CONTENT {");

View File

@ -20,6 +20,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.spdy.SpdyHttpHeaders;
import io.netty.handler.codec.spdy.SpdyHttpHeaders.Names;
/**
* Adds a unique client stream ID to the SPDY header. Client stream IDs MUST be odd.
@ -37,7 +38,7 @@ public class SpdyClientStreamIdHandler extends ChannelHandlerAdapter {
if (acceptOutboundMessage(msg)) {
HttpMessage httpMsg = (HttpMessage) msg;
if (!httpMsg.headers().contains(SpdyHttpHeaders.Names.STREAM_ID)) {
SpdyHttpHeaders.setStreamId(httpMsg, currentStreamId);
httpMsg.headers().set(Names.STREAM_ID, currentStreamId);
// Client stream IDs are always odd
currentStreamId += 2;
}

View File

@ -22,6 +22,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.util.CharsetUtil;
@ -42,10 +43,10 @@ public class SpdyServerHandler extends SimpleChannelInboundHandler<Object> {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
if (is100ContinueExpected(req)) {
if (HttpHeaderUtil.is100ContinueExpected(req)) {
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
}
boolean keepAlive = isKeepAlive(req);
boolean keepAlive = HttpHeaderUtil.isKeepAlive(req);
ByteBuf content = Unpooled.copiedBuffer("Hello World " + new Date(), CharsetUtil.UTF_8);

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

@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -24,6 +24,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
@ -126,12 +127,12 @@ public class AutobahnServerHandler extends ChannelHandlerAdapter {
ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
setContentLength(res, res.content().readableBytes());
HttpHeaderUtil.setContentLength(res, res.content().readableBytes());
}
// Send the response and close the connection if necessary.
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!isKeepAlive(req) || res.getStatus().code() != 200) {
if (!HttpHeaderUtil.isKeepAlive(req) || res.getStatus().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}