Backport HTTP encoding / decoding optimizations which were introduced by #2007.

The backport is partly done to keep backward compatibility
This commit is contained in:
Norman Maurer 2013-11-28 08:15:14 +01:00
parent 14327706a3
commit 7f57c5ed05
10 changed files with 694 additions and 220 deletions

View File

@ -15,6 +15,8 @@
*/ */
package io.netty.handler.codec.http; package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -31,31 +33,12 @@ public class DefaultHttpHeaders extends HttpHeaders {
private static final int BUCKET_SIZE = 17; 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;
} else {
return -h;
}
}
private static int index(int hash) { private static int index(int hash) {
return hash % BUCKET_SIZE; return hash % BUCKET_SIZE;
} }
private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE]; private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];
private final HeaderEntry head = new HeaderEntry(-1, null, null); private final HeaderEntry head = new HeaderEntry();
protected final boolean validate; protected final boolean validate;
public DefaultHttpHeaders() { public DefaultHttpHeaders() {
@ -67,19 +50,55 @@ public class DefaultHttpHeaders extends HttpHeaders {
head.before = head.after = head; head.before = head.after = head;
} }
void validateHeaderName0(String headerName) { void validateHeaderName0(CharSequence headerName) {
validateHeaderName(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 @Override
public HttpHeaders add(final String name, final Object value) { public HttpHeaders add(final String name, final Object value) {
String strVal; return add((CharSequence) name, value);
}
@Override
public HttpHeaders add(final CharSequence name, final Object value) {
CharSequence strVal;
if (validate) { if (validate) {
validateHeaderName0(name); validateHeaderName0(name);
strVal = toString(value); strVal = toCharSequence(value);
validateHeaderValue(strVal); validateHeaderValue(strVal);
} else { } else {
strVal = toString(value); strVal = toCharSequence(value);
} }
int h = hash(name); int h = hash(name);
int i = index(h); int i = index(h);
@ -89,13 +108,18 @@ public class DefaultHttpHeaders extends HttpHeaders {
@Override @Override
public HttpHeaders add(String name, Iterable<?> values) { public HttpHeaders add(String name, Iterable<?> values) {
return add((CharSequence) name, values);
}
@Override
public HttpHeaders add(CharSequence name, Iterable<?> values) {
if (validate) { if (validate) {
validateHeaderName0(name); validateHeaderName0(name);
} }
int h = hash(name); int h = hash(name);
int i = index(h); int i = index(h);
for (Object v: values) { for (Object v: values) {
String vstr = toString(v); CharSequence vstr = toCharSequence(v);
if (validate) { if (validate) {
validateHeaderValue(vstr); validateHeaderValue(vstr);
} }
@ -104,7 +128,7 @@ public class DefaultHttpHeaders extends HttpHeaders {
return this; return this;
} }
private void add0(int h, int i, final String name, final String value) { private void add0(int h, int i, final CharSequence name, final CharSequence value) {
// Update the hash table. // Update the hash table.
HeaderEntry e = entries[i]; HeaderEntry e = entries[i];
HeaderEntry newEntry; HeaderEntry newEntry;
@ -117,6 +141,11 @@ public class DefaultHttpHeaders extends HttpHeaders {
@Override @Override
public HttpHeaders remove(final String name) { public HttpHeaders remove(final String name) {
return remove((CharSequence) name);
}
@Override
public HttpHeaders remove(final CharSequence name) {
if (name == null) { if (name == null) {
throw new NullPointerException("name"); throw new NullPointerException("name");
} }
@ -126,7 +155,7 @@ public class DefaultHttpHeaders extends HttpHeaders {
return this; return this;
} }
private void remove0(int h, int i, String name) { private void remove0(int h, int i, CharSequence name) {
HeaderEntry e = entries[i]; HeaderEntry e = entries[i];
if (e == null) { if (e == null) {
return; return;
@ -164,13 +193,18 @@ public class DefaultHttpHeaders extends HttpHeaders {
@Override @Override
public HttpHeaders set(final String name, final Object value) { public HttpHeaders set(final String name, final Object value) {
String strVal; return set((CharSequence) name, value);
}
@Override
public HttpHeaders set(final CharSequence name, final Object value) {
CharSequence strVal;
if (validate) { if (validate) {
validateHeaderName0(name); validateHeaderName0(name);
strVal = toString(value); strVal = toCharSequence(value);
validateHeaderValue(strVal); validateHeaderValue(strVal);
} else { } else {
strVal = toString(value); strVal = toCharSequence(value);
} }
int h = hash(name); int h = hash(name);
int i = index(h); int i = index(h);
@ -181,6 +215,11 @@ public class DefaultHttpHeaders extends HttpHeaders {
@Override @Override
public HttpHeaders set(final String name, final Iterable<?> values) { public HttpHeaders set(final String name, final Iterable<?> values) {
return set((CharSequence) name, values);
}
@Override
public HttpHeaders set(final CharSequence name, final Iterable<?> values) {
if (values == null) { if (values == null) {
throw new NullPointerException("values"); throw new NullPointerException("values");
} }
@ -196,7 +235,7 @@ public class DefaultHttpHeaders extends HttpHeaders {
if (v == null) { if (v == null) {
break; break;
} }
String strVal = toString(v); CharSequence strVal = toCharSequence(v);
if (validate) { if (validate) {
validateHeaderValue(strVal); validateHeaderValue(strVal);
} }
@ -215,6 +254,11 @@ public class DefaultHttpHeaders extends HttpHeaders {
@Override @Override
public String get(final String name) { public String get(final String name) {
return get((CharSequence) name);
}
@Override
public String get(final CharSequence name) {
if (name == null) { if (name == null) {
throw new NullPointerException("name"); throw new NullPointerException("name");
} }
@ -222,7 +266,7 @@ public class DefaultHttpHeaders extends HttpHeaders {
int h = hash(name); int h = hash(name);
int i = index(h); int i = index(h);
HeaderEntry e = entries[i]; HeaderEntry e = entries[i];
String value = null; CharSequence value = null;
// loop until the first header was found // loop until the first header was found
while (e != null) { while (e != null) {
if (e.hash == h && equalsIgnoreCase(name, e.key)) { if (e.hash == h && equalsIgnoreCase(name, e.key)) {
@ -231,11 +275,19 @@ public class DefaultHttpHeaders extends HttpHeaders {
e = e.next; e = e.next;
} }
return value; if (value == null) {
return null;
}
return value.toString();
} }
@Override @Override
public List<String> getAll(final String name) { public List<String> getAll(final String name) {
return getAll((CharSequence) name);
}
@Override
public List<String> getAll(final CharSequence name) {
if (name == null) { if (name == null) {
throw new NullPointerException("name"); throw new NullPointerException("name");
} }
@ -247,7 +299,7 @@ public class DefaultHttpHeaders extends HttpHeaders {
HeaderEntry e = entries[i]; HeaderEntry e = entries[i];
while (e != null) { while (e != null) {
if (e.hash == h && equalsIgnoreCase(name, e.key)) { if (e.hash == h && equalsIgnoreCase(name, e.key)) {
values.addFirst(e.value); values.addFirst(e.getValue());
} }
e = e.next; e = e.next;
} }
@ -277,6 +329,11 @@ public class DefaultHttpHeaders extends HttpHeaders {
return get(name) != null; return get(name) != null;
} }
@Override
public boolean contains(CharSequence name) {
return get(name) != null;
}
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return head == head.after; return head == head.after;
@ -284,6 +341,11 @@ public class DefaultHttpHeaders extends HttpHeaders {
@Override @Override
public boolean contains(String name, String value, boolean ignoreCaseValue) { public boolean contains(String name, String value, boolean ignoreCaseValue) {
return contains((CharSequence) name, (CharSequence) value, ignoreCaseValue);
}
@Override
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCaseValue) {
if (name == null) { if (name == null) {
throw new NullPointerException("name"); throw new NullPointerException("name");
} }
@ -315,18 +377,18 @@ public class DefaultHttpHeaders extends HttpHeaders {
HeaderEntry e = head.after; HeaderEntry e = head.after;
while (e != head) { while (e != head) {
names.add(e.key); names.add(e.getKey());
e = e.after; e = e.after;
} }
return names; return names;
} }
private static String toString(Object value) { private static CharSequence toCharSequence(Object value) {
if (value == null) { if (value == null) {
return null; return null;
} }
if (value instanceof String) { if (value instanceof CharSequence) {
return (String) value; return (CharSequence) value;
} }
if (value instanceof Number) { if (value instanceof Number) {
return value.toString(); return value.toString();
@ -340,6 +402,14 @@ public class DefaultHttpHeaders extends HttpHeaders {
return value.toString(); return value.toString();
} }
void encode(ByteBuf buf) {
HeaderEntry e = head.after;
while (e != head) {
e.encode(buf);
e = e.after;
}
}
private final class HeaderIterator implements Iterator<Map.Entry<String, String>> { private final class HeaderIterator implements Iterator<Map.Entry<String, String>> {
private HeaderEntry current = head; private HeaderEntry current = head;
@ -368,17 +438,23 @@ public class DefaultHttpHeaders extends HttpHeaders {
private final class HeaderEntry implements Map.Entry<String, String> { private final class HeaderEntry implements Map.Entry<String, String> {
final int hash; final int hash;
final String key; final CharSequence key;
String value; CharSequence value;
HeaderEntry next; HeaderEntry next;
HeaderEntry before, after; HeaderEntry before, after;
HeaderEntry(int hash, String key, String value) { HeaderEntry(int hash, CharSequence key, CharSequence value) {
this.hash = hash; this.hash = hash;
this.key = key; this.key = key;
this.value = value; this.value = value;
} }
HeaderEntry() {
hash = -1;
key = null;
value = null;
}
void remove() { void remove() {
before.after = after; before.after = after;
after.before = before; after.before = before;
@ -393,12 +469,12 @@ public class DefaultHttpHeaders extends HttpHeaders {
@Override @Override
public String getKey() { public String getKey() {
return key; return key.toString();
} }
@Override @Override
public String getValue() { public String getValue() {
return value; return value.toString();
} }
@Override @Override
@ -407,14 +483,18 @@ public class DefaultHttpHeaders extends HttpHeaders {
throw new NullPointerException("value"); throw new NullPointerException("value");
} }
validateHeaderValue(value); validateHeaderValue(value);
String oldValue = this.value; CharSequence oldValue = this.value;
this.value = value; this.value = value;
return oldValue; return oldValue.toString();
} }
@Override @Override
public String toString() { public String toString() {
return key + '=' + value; return key.toString() + '=' + value.toString();
}
void encode(ByteBuf buf) {
HttpHeaders.encode(key, value, buf);
} }
} }
} }

View File

@ -100,11 +100,11 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
} }
@Override @Override
void validateHeaderName0(String name) { void validateHeaderName0(CharSequence name) {
super.validateHeaderName0(name); super.validateHeaderName0(name);
if (name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) || if (HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) ||
name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) || HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) ||
name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) { HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"prohibited trailing header: " + name); "prohibited trailing header: " + name);
} }

View File

@ -0,0 +1,60 @@
/*
* 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

@ -15,6 +15,8 @@
*/ */
package io.netty.handler.codec.http; package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import java.text.ParseException; import java.text.ParseException;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
@ -26,12 +28,32 @@ import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import static io.netty.handler.codec.http.HttpConstants.CR;
import static io.netty.handler.codec.http.HttpConstants.LF;
/** /**
* Provides the constants for the standard HTTP header names and values and * Provides the constants for the standard HTTP header names and values and
* commonly used utility methods that accesses an {@link HttpMessage}. * commonly used utility methods that accesses an {@link HttpMessage}.
*/ */
public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>> { public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>> {
private static final byte[] HEADER_SEPERATOR = { HttpConstants.COLON, HttpConstants.SP };
private static final byte[] CRLF = { CR, LF };
private static final CharSequence CONTENT_LENGTH_ENTITY = newEntity(Names.CONTENT_LENGTH);
private static final CharSequence CONNECTION_ENTITY = newEntity(Names.CONNECTION);
private static final CharSequence CLOSE_ENTITY = newEntity(Values.CLOSE);
private static final CharSequence KEEP_ALIVE_ENTITY = newEntity(Values.KEEP_ALIVE);
private static final CharSequence HOST_ENTITY = newEntity(Names.HOST);
private static final CharSequence DATE_ENTITY = newEntity(Names.DATE);
private static final CharSequence EXPECT_ENTITY = newEntity(Names.EXPECT);
private static final CharSequence CONTINUE_ENTITY = newEntity(Values.CONTINUE);
private static final CharSequence TRANSFER_ENCODING_ENTITY = newEntity(Names.TRANSFER_ENCODING);
private static final CharSequence CHUNKED_ENTITY = newEntity(Values.CHUNKED);
private static final CharSequence SEC_WEBSOCKET_KEY1_ENTITY = newEntity(Names.SEC_WEBSOCKET_KEY1);
private static final CharSequence SEC_WEBSOCKET_KEY2_ENTITY = newEntity(Names.SEC_WEBSOCKET_KEY2);
private static final CharSequence SEC_WEBSOCKET_ORIGIN_ENTITY = newEntity(Names.SEC_WEBSOCKET_ORIGIN);
private static final CharSequence SEC_WEBSOCKET_LOCATION_ENTITY = newEntity(Names.SEC_WEBSOCKET_LOCATION);
public static final HttpHeaders EMPTY_HEADERS = new HttpHeaders() { public static final HttpHeaders EMPTY_HEADERS = new HttpHeaders() {
@Override @Override
public String get(String name) { public String get(String name) {
@ -545,15 +567,15 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* {@link HttpVersion#isKeepAliveDefault()}. * {@link HttpVersion#isKeepAliveDefault()}.
*/ */
public static boolean isKeepAlive(HttpMessage message) { public static boolean isKeepAlive(HttpMessage message) {
String connection = message.headers().get(Names.CONNECTION); String connection = message.headers().get(CONNECTION_ENTITY);
if (connection != null && equalsIgnoreCase(Values.CLOSE, connection)) { if (connection != null && equalsIgnoreCase(CLOSE_ENTITY, connection)) {
return false; return false;
} }
if (message.getProtocolVersion().isKeepAliveDefault()) { if (message.getProtocolVersion().isKeepAliveDefault()) {
return !equalsIgnoreCase(Values.CLOSE, connection); return !equalsIgnoreCase(CLOSE_ENTITY, connection);
} else { } else {
return equalsIgnoreCase(Values.KEEP_ALIVE, connection); return equalsIgnoreCase(KEEP_ALIVE_ENTITY, connection);
} }
} }
@ -580,30 +602,44 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
HttpHeaders h = message.headers(); HttpHeaders h = message.headers();
if (message.getProtocolVersion().isKeepAliveDefault()) { if (message.getProtocolVersion().isKeepAliveDefault()) {
if (keepAlive) { if (keepAlive) {
h.remove(Names.CONNECTION); h.remove(CONNECTION_ENTITY);
} else { } else {
h.set(Names.CONNECTION, Values.CLOSE); h.set(CONNECTION_ENTITY, CLOSE_ENTITY);
} }
} else { } else {
if (keepAlive) { if (keepAlive) {
h.set(Names.CONNECTION, Values.KEEP_ALIVE); h.set(CONNECTION_ENTITY, KEEP_ALIVE_ENTITY);
} else { } else {
h.remove(Names.CONNECTION); h.remove(CONNECTION_ENTITY);
} }
} }
} }
/** /**
* Returns the header value with the specified header name. If there are * @see {@link #getHeader(HttpMessage, CharSequence)}
* 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(HttpMessage message, String name) { public static String getHeader(HttpMessage message, String name) {
return message.headers().get(name); return message.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 {@code null} if there is no such header
*/
public static String getHeader(HttpMessage message, CharSequence name) {
return message.headers().get(name);
}
/**
* @see {@link #getHeader(HttpMessage, CharSequence, String)}
*/
public static String getHeader(HttpMessage message, String name, String defaultValue) {
return getHeader(message, (CharSequence) name, defaultValue);
}
/** /**
* Returns the header value with the specified header name. If there are * Returns the header value with the specified header name. If there are
* more than one header value for the specified header name, the first * more than one header value for the specified header name, the first
@ -612,7 +648,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @return the header value or the {@code defaultValue} if there is no such * @return the header value or the {@code defaultValue} if there is no such
* header * header
*/ */
public static String getHeader(HttpMessage message, String name, String defaultValue) { public static String getHeader(HttpMessage message, CharSequence name, String defaultValue) {
String value = message.headers().get(name); String value = message.headers().get(name);
if (value == null) { if (value == null) {
return defaultValue; return defaultValue;
@ -620,6 +656,13 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
return value; return value;
} }
/**
* @see {@link #setHeader(HttpMessage, CharSequence, Object)}
*/
public static void setHeader(HttpMessage message, String name, Object value) {
message.headers().set(name, value);
}
/** /**
* Sets a new header with the specified name and value. If there is an * 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. * existing header with the same name, the existing header is removed.
@ -628,10 +671,18 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* and {@link Calendar} which are formatted to the date format defined in * and {@link 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>. * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
*/ */
public static void setHeader(HttpMessage message, String name, Object value) { public static void setHeader(HttpMessage message, CharSequence name, Object value) {
message.headers().set(name, value); message.headers().set(name, value);
} }
/**
*
* @see {@link #setHeader(HttpMessage, CharSequence, Iterable)}
*/
public static void setHeader(HttpMessage message, String name, Iterable<?> values) {
message.headers().set(name, values);
}
/** /**
* Sets a new header with the specified name and values. If there is an * 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. * existing header with the same name, the existing header is removed.
@ -646,10 +697,17 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* } * }
* </pre> * </pre>
*/ */
public static void setHeader(HttpMessage message, String name, Iterable<?> values) { public static void setHeader(HttpMessage message, CharSequence name, Iterable<?> values) {
message.headers().set(name, values); message.headers().set(name, values);
} }
/**
* @see {@link #addHeader(HttpMessage, CharSequence, Object)}
*/
public static void addHeader(HttpMessage message, String name, Object value) {
message.headers().add(name, value);
}
/** /**
* Adds a new header with the specified name and value. * Adds a new header with the specified name and value.
* If the specified value is not a {@link String}, it is converted into a * If the specified value is not a {@link String}, it is converted into a
@ -657,14 +715,21 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* and {@link Calendar} which are formatted to the date format defined in * and {@link 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>. * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
*/ */
public static void addHeader(HttpMessage message, String name, Object value) { public static void addHeader(HttpMessage message, CharSequence name, Object value) {
message.headers().add(name, value); message.headers().add(name, value);
} }
/**
* @see {@link #removeHeader(HttpMessage, CharSequence)}
*/
public static void removeHeader(HttpMessage message, String name) {
message.headers().remove(name);
}
/** /**
* Removes the header with the specified name. * Removes the header with the specified name.
*/ */
public static void removeHeader(HttpMessage message, String name) { public static void removeHeader(HttpMessage message, CharSequence name) {
message.headers().remove(name); message.headers().remove(name);
} }
@ -675,6 +740,13 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
message.headers().clear(); message.headers().clear();
} }
/**
* @see {@link #getIntHeader(HttpMessage, CharSequence)}
*/
public static int getIntHeader(HttpMessage message, String name) {
return getIntHeader(message, (CharSequence) name);
}
/** /**
* Returns the integer header value with the specified header name. If * Returns the integer header value with the specified header name. If
* there are more than one header value for the specified header name, the * there are more than one header value for the specified header name, the
@ -684,7 +756,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @throws NumberFormatException * @throws NumberFormatException
* if there is no such header or the header value is not a number * if there is no such header or the header value is not a number
*/ */
public static int getIntHeader(HttpMessage message, String name) { public static int getIntHeader(HttpMessage message, CharSequence name) {
String value = getHeader(message, name); String value = getHeader(message, name);
if (value == null) { if (value == null) {
throw new NumberFormatException("header not found: " + name); throw new NumberFormatException("header not found: " + name);
@ -692,6 +764,13 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
return Integer.parseInt(value); return Integer.parseInt(value);
} }
/**
* @see {@link #getIntHeader(HttpMessage, CharSequence, int)}
*/
public static int getIntHeader(HttpMessage message, String name, int defaultValue) {
return getIntHeader(message, (CharSequence) name, defaultValue);
}
/** /**
* Returns the integer header value with the specified header name. If * Returns the integer header value with the specified header name. If
* there are more than one header value for the specified header name, the * there are more than one header value for the specified header name, the
@ -700,7 +779,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @return the header value or the {@code defaultValue} if there is no such * @return the header value or the {@code defaultValue} if there is no such
* header or the header value is not a number * header or the header value is not a number
*/ */
public static int getIntHeader(HttpMessage message, String name, int defaultValue) { public static int getIntHeader(HttpMessage message, CharSequence name, int defaultValue) {
String value = getHeader(message, name); String value = getHeader(message, name);
if (value == null) { if (value == null) {
return defaultValue; return defaultValue;
@ -714,28 +793,57 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
} }
/** /**
* Sets a new integer header with the specified name and value. If there * @see {@link #setIntHeader(HttpMessage, CharSequence, int)}
* is an existing header with the same name, the existing header is removed.
*/ */
public static void setIntHeader(HttpMessage message, String name, int value) { public static void setIntHeader(HttpMessage message, String name, int value) {
message.headers().set(name, value); message.headers().set(name, value);
} }
/** /**
* Sets a new integer header with the specified name and values. If there * Sets a new integer header with the specified name and value. If there
* is an existing header with the same name, the existing header is removed. * is an existing header with the same name, the existing header is removed.
*/ */
public static void setIntHeader(HttpMessage message, CharSequence name, int value) {
message.headers().set(name, value);
}
/**
* @see {@link #setIntHeader(HttpMessage, CharSequence, Iterable)}
*/
public static void setIntHeader(HttpMessage message, String name, Iterable<Integer> values) { public static void setIntHeader(HttpMessage message, String name, Iterable<Integer> values) {
message.headers().set(name, values); message.headers().set(name, values);
} }
/** /**
* Adds a new integer header with the specified name and value. * Sets a new integer 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 setIntHeader(HttpMessage message, CharSequence name, Iterable<Integer> values) {
message.headers().set(name, values);
}
/**
*
* @see {@link #addIntHeader(HttpMessage, CharSequence, int)}
*/ */
public static void addIntHeader(HttpMessage message, String name, int value) { public static void addIntHeader(HttpMessage message, String name, int value) {
message.headers().add(name, value); message.headers().add(name, value);
} }
/**
* Adds a new integer header with the specified name and value.
*/
public static void addIntHeader(HttpMessage message, CharSequence name, int value) {
message.headers().add(name, value);
}
/**
* @see {@link #getDateHeader(HttpMessage, CharSequence)}
*/
public static Date getDateHeader(HttpMessage message, String name) throws ParseException {
return getDateHeader(message, (CharSequence) name);
}
/** /**
* Returns the date header value with the specified header name. If * Returns the date header value with the specified header name. If
* there are more than one header value for the specified header name, the * there are more than one header value for the specified header name, the
@ -745,7 +853,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @throws ParseException * @throws ParseException
* if there is no such header or the header value is not a formatted date * if there is no such header or the header value is not a formatted date
*/ */
public static Date getDateHeader(HttpMessage message, String name) throws ParseException { public static Date getDateHeader(HttpMessage message, CharSequence name) throws ParseException {
String value = getHeader(message, name); String value = getHeader(message, name);
if (value == null) { if (value == null) {
throw new ParseException("header not found: " + name, 0); throw new ParseException("header not found: " + name, 0);
@ -753,6 +861,13 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
return HttpHeaderDateFormat.get().parse(value); return HttpHeaderDateFormat.get().parse(value);
} }
/**
* @see {@link #getDateHeader(HttpMessage, CharSequence, Date)}
*/
public static Date getDateHeader(HttpMessage message, String name, Date defaultValue) {
return getDateHeader(message, (CharSequence) name, defaultValue);
}
/** /**
* Returns the date header value with the specified header name. If * Returns the date header value with the specified header name. If
* there are more than one header value for the specified header name, the * there are more than one header value for the specified header name, the
@ -761,7 +876,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @return the header value or the {@code defaultValue} if there is no such * @return the header value or the {@code defaultValue} if there is no such
* header or the header value is not a formatted date * header or the header value is not a formatted date
*/ */
public static Date getDateHeader(HttpMessage message, String name, Date defaultValue) { public static Date getDateHeader(HttpMessage message, CharSequence name, Date defaultValue) {
final String value = getHeader(message, name); final String value = getHeader(message, name);
if (value == null) { if (value == null) {
return defaultValue; return defaultValue;
@ -774,13 +889,20 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
} }
} }
/**
* @see {@link #setDateHeader(HttpMessage, CharSequence, Date)}
*/
public static void setDateHeader(HttpMessage message, String name, Date value) {
setDateHeader(message, (CharSequence) name, value);
}
/** /**
* Sets a new date header with the specified name and value. If there * Sets a new date header with the specified name and value. If there
* is an existing header with the same name, the existing header is removed. * is an existing header with the same name, the existing header is removed.
* The specified value is formatted as defined in * The specified value is formatted as defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a> * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>
*/ */
public static void setDateHeader(HttpMessage message, String name, Date value) { public static void setDateHeader(HttpMessage message, CharSequence name, Date value) {
if (value != null) { if (value != null) {
message.headers().set(name, HttpHeaderDateFormat.get().format(value)); message.headers().set(name, HttpHeaderDateFormat.get().format(value));
} else { } else {
@ -788,22 +910,36 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
} }
} }
/**
* @see {@link #setDateHeader(HttpMessage, CharSequence, Iterable)}
*/
public static void setDateHeader(HttpMessage message, String name, Iterable<Date> values) {
message.headers().set(name, values);
}
/** /**
* Sets a new date header with the specified name and values. If there * Sets a new date header with the specified name and values. If there
* is an existing header with the same name, the existing header is removed. * is an existing header with the same name, the existing header is removed.
* The specified values are formatted as defined in * The specified values are formatted as defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a> * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>
*/ */
public static void setDateHeader(HttpMessage message, String name, Iterable<Date> values) { public static void setDateHeader(HttpMessage message, CharSequence name, Iterable<Date> values) {
message.headers().set(name, values); message.headers().set(name, values);
} }
/**
* @see {@link #addDateHeader(HttpMessage, CharSequence, Date)}
*/
public static void addDateHeader(HttpMessage message, String name, Date value) {
message.headers().add(name, value);
}
/** /**
* Adds a new date header with the specified name and value. The specified * Adds a new date header with the specified name and value. The specified
* value is formatted as defined in * value is formatted as defined in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a> * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>
*/ */
public static void addDateHeader(HttpMessage message, String name, Date value) { public static void addDateHeader(HttpMessage message, CharSequence name, Date value) {
message.headers().add(name, value); message.headers().add(name, value);
} }
@ -820,7 +956,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* or its value is not a number * or its value is not a number
*/ */
public static long getContentLength(HttpMessage message) { public static long getContentLength(HttpMessage message) {
String value = getHeader(message, Names.CONTENT_LENGTH); String value = getHeader(message, CONTENT_LENGTH_ENTITY);
if (value != null) { if (value != null) {
return Long.parseLong(value); return Long.parseLong(value);
} }
@ -847,7 +983,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* a number * a number
*/ */
public static long getContentLength(HttpMessage message, long defaultValue) { public static long getContentLength(HttpMessage message, long defaultValue) {
String contentLength = message.headers().get(Names.CONTENT_LENGTH); String contentLength = message.headers().get(CONTENT_LENGTH_ENTITY);
if (contentLength != null) { if (contentLength != null) {
try { try {
return Long.parseLong(contentLength); return Long.parseLong(contentLength);
@ -877,15 +1013,15 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
if (message instanceof HttpRequest) { if (message instanceof HttpRequest) {
HttpRequest req = (HttpRequest) message; HttpRequest req = (HttpRequest) message;
if (HttpMethod.GET.equals(req.getMethod()) && if (HttpMethod.GET.equals(req.getMethod()) &&
h.contains(Names.SEC_WEBSOCKET_KEY1) && h.contains(SEC_WEBSOCKET_KEY1_ENTITY) &&
h.contains(Names.SEC_WEBSOCKET_KEY2)) { h.contains(SEC_WEBSOCKET_KEY2_ENTITY)) {
return 8; return 8;
} }
} else if (message instanceof HttpResponse) { } else if (message instanceof HttpResponse) {
HttpResponse res = (HttpResponse) message; HttpResponse res = (HttpResponse) message;
if (res.getStatus().code() == 101 && if (res.getStatus().code() == 101 &&
h.contains(Names.SEC_WEBSOCKET_ORIGIN) && h.contains(SEC_WEBSOCKET_ORIGIN_ENTITY) &&
h.contains(Names.SEC_WEBSOCKET_LOCATION)) { h.contains(SEC_WEBSOCKET_LOCATION_ENTITY)) {
return 16; return 16;
} }
} }
@ -898,14 +1034,14 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* Sets the {@code "Content-Length"} header. * Sets the {@code "Content-Length"} header.
*/ */
public static void setContentLength(HttpMessage message, long length) { public static void setContentLength(HttpMessage message, long length) {
message.headers().set(Names.CONTENT_LENGTH, length); message.headers().set(CONTENT_LENGTH_ENTITY, length);
} }
/** /**
* Returns the value of the {@code "Host"} header. * Returns the value of the {@code "Host"} header.
*/ */
public static String getHost(HttpMessage message) { public static String getHost(HttpMessage message) {
return message.headers().get(Names.HOST); return message.headers().get(HOST_ENTITY);
} }
/** /**
@ -913,14 +1049,21 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* header, the {@code defaultValue} is returned. * header, the {@code defaultValue} is returned.
*/ */
public static String getHost(HttpMessage message, String defaultValue) { public static String getHost(HttpMessage message, String defaultValue) {
return getHeader(message, Names.HOST, defaultValue); return getHeader(message, HOST_ENTITY, defaultValue);
}
/**
* @see {@link #setHost(HttpMessage, CharSequence)}
*/
public static void setHost(HttpMessage message, String value) {
message.headers().set(HOST_ENTITY, value);
} }
/** /**
* Sets the {@code "Host"} header. * Sets the {@code "Host"} header.
*/ */
public static void setHost(HttpMessage message, String value) { public static void setHost(HttpMessage message, CharSequence value) {
message.headers().set(Names.HOST, value); message.headers().set(HOST_ENTITY, value);
} }
/** /**
@ -930,7 +1073,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* if there is no such header or the header value is not a formatted date * if there is no such header or the header value is not a formatted date
*/ */
public static Date getDate(HttpMessage message) throws ParseException { public static Date getDate(HttpMessage message) throws ParseException {
return getDateHeader(message, Names.DATE); return getDateHeader(message, DATE_ENTITY);
} }
/** /**
@ -939,7 +1082,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* is returned. * is returned.
*/ */
public static Date getDate(HttpMessage message, Date defaultValue) { public static Date getDate(HttpMessage message, Date defaultValue) {
return getDateHeader(message, Names.DATE, defaultValue); return getDateHeader(message, DATE_ENTITY, defaultValue);
} }
/** /**
@ -947,9 +1090,9 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
*/ */
public static void setDate(HttpMessage message, Date value) { public static void setDate(HttpMessage message, Date value) {
if (value != null) { if (value != null) {
message.headers().set(Names.DATE, HttpHeaderDateFormat.get().format(value)); message.headers().set(DATE_ENTITY, HttpHeaderDateFormat.get().format(value));
} else { } else {
message.headers().set(Names.DATE, null); message.headers().set(DATE_ENTITY, null);
} }
} }
@ -969,16 +1112,16 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
} }
// In most cases, there will be one or zero 'Expect' header. // In most cases, there will be one or zero 'Expect' header.
String value = message.headers().get(Names.EXPECT); String value = message.headers().get(EXPECT_ENTITY);
if (value == null) { if (value == null) {
return false; return false;
} }
if (equalsIgnoreCase(Values.CONTINUE, value)) { if (equalsIgnoreCase(CONTINUE_ENTITY, value)) {
return true; return true;
} }
// Multiple 'Expect' headers. Search through them. // Multiple 'Expect' headers. Search through them.
return message.headers().contains(Names.EXPECT, Values.CONTINUE, true); return message.headers().contains(EXPECT_ENTITY, CONTINUE_ENTITY, true);
} }
/** /**
@ -999,9 +1142,9 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
*/ */
public static void set100ContinueExpected(HttpMessage message, boolean set) { public static void set100ContinueExpected(HttpMessage message, boolean set) {
if (set) { if (set) {
message.headers().set(Names.EXPECT, Values.CONTINUE); message.headers().set(EXPECT_ENTITY, CONTINUE_ENTITY);
} else { } else {
message.headers().remove(Names.EXPECT); message.headers().remove(EXPECT_ENTITY);
} }
} }
@ -1010,7 +1153,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* *
* @param headerName The header name being validated * @param headerName The header name being validated
*/ */
static void validateHeaderName(String headerName) { static void validateHeaderName(CharSequence headerName) {
//Check to see if the name is null //Check to see if the name is null
if (headerName == null) { if (headerName == null) {
throw new NullPointerException("Header names cannot be null"); throw new NullPointerException("Header names cannot be null");
@ -1042,7 +1185,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* *
* @param headerValue The value being validated * @param headerValue The value being validated
*/ */
static void validateHeaderValue(String headerValue) { static void validateHeaderValue(CharSequence headerValue) {
//Check to see if the value is null //Check to see if the value is null
if (headerValue == null) { if (headerValue == null) {
throw new NullPointerException("Header values cannot be null"); throw new NullPointerException("Header values cannot be null");
@ -1121,35 +1264,35 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @return True if transfer encoding is chunked, otherwise false * @return True if transfer encoding is chunked, otherwise false
*/ */
public static boolean isTransferEncodingChunked(HttpMessage message) { public static boolean isTransferEncodingChunked(HttpMessage message) {
return message.headers().contains(Names.TRANSFER_ENCODING, Values.CHUNKED, true); return message.headers().contains(TRANSFER_ENCODING_ENTITY, CHUNKED_ENTITY, true);
} }
public static void removeTransferEncodingChunked(HttpMessage m) { public static void removeTransferEncodingChunked(HttpMessage m) {
List<String> values = m.headers().getAll(Names.TRANSFER_ENCODING); List<String> values = m.headers().getAll(TRANSFER_ENCODING_ENTITY);
if (values.isEmpty()) { if (values.isEmpty()) {
return; return;
} }
Iterator<String> valuesIt = values.iterator(); Iterator<String> valuesIt = values.iterator();
while (valuesIt.hasNext()) { while (valuesIt.hasNext()) {
String value = valuesIt.next(); String value = valuesIt.next();
if (equalsIgnoreCase(value, Values.CHUNKED)) { if (equalsIgnoreCase(value, CHUNKED_ENTITY)) {
valuesIt.remove(); valuesIt.remove();
} }
} }
if (values.isEmpty()) { if (values.isEmpty()) {
m.headers().remove(Names.TRANSFER_ENCODING); m.headers().remove(TRANSFER_ENCODING_ENTITY);
} else { } else {
m.headers().set(Names.TRANSFER_ENCODING, values); m.headers().set(TRANSFER_ENCODING_ENTITY, values);
} }
} }
public static void setTransferEncodingChunked(HttpMessage m) { public static void setTransferEncodingChunked(HttpMessage m) {
addHeader(m, Names.TRANSFER_ENCODING, Values.CHUNKED); addHeader(m, TRANSFER_ENCODING_ENTITY, CHUNKED_ENTITY);
removeHeader(m, Names.CONTENT_LENGTH); removeHeader(m, CONTENT_LENGTH_ENTITY);
} }
public static boolean isContentLengthSet(HttpMessage m) { public static boolean isContentLengthSet(HttpMessage m) {
return m.headers().contains(Names.CONTENT_LENGTH); return m.headers().contains(CONTENT_LENGTH_ENTITY);
} }
/** /**
@ -1187,8 +1330,79 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
} }
return true; return true;
} }
static int hash(CharSequence name) {
if (name instanceof HttpHeaderEntity) {
return ((HttpHeaderEntity) name).hash();
}
int h = 0;
for (int i = name.length() - 1; i >= 0; i --) {
char c = name.charAt(i);
if (c >= 'A' && c <= 'Z') {
c += 32;
}
h = 31 * h + c;
}
if (h > 0) {
return h;
} else if (h == Integer.MIN_VALUE) {
return Integer.MAX_VALUE;
} else {
return -h;
}
}
static void encode(HttpHeaders headers, ByteBuf buf) {
if (headers instanceof DefaultHttpHeaders) {
((DefaultHttpHeaders) headers).encode(buf);
} else {
for (Entry<String, String> header: headers) {
encode(header.getKey(), header.getValue(), buf);
}
}
}
static void encode(CharSequence key, CharSequence value, ByteBuf buf) {
encodeAscii(key, buf);
buf.writeBytes(HEADER_SEPERATOR);
encodeAscii(value, buf);
buf.writeBytes(CRLF);
}
public static void encodeAscii(CharSequence seq, ByteBuf buf) {
if (seq instanceof HttpHeaderEntity) {
((HttpHeaderEntity) seq).encode(buf);
} else {
encodeAscii0(seq, buf);
}
}
static void encodeAscii0(CharSequence seq, ByteBuf buf) {
int length = seq.length();
for (int i = 0 ; i < length; i++) {
buf.writeByte((byte) seq.charAt(i));
}
}
/**
* Create a new {@link CharSequence} which is optimized for reuse as {@link HttpHeaders} name or value.
* So if you have a Header name or value that you want to reuse you should make use of this.
*/
public static CharSequence newEntity(String name) {
if (name == null) {
throw new NullPointerException("name");
}
return new HttpHeaderEntity(name);
}
protected HttpHeaders() { } protected HttpHeaders() { }
/**
* @see {@link #get(CharSequence)}
*/
public abstract String get(String name);
/** /**
* Returns the value of a header with the specified name. If there are * 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. * more than one values for the specified name, the first value is returned.
@ -1196,7 +1410,14 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @param name The name of the header to search * @param name The name of the header to search
* @return The first header value or {@code null} if there is no such header * @return The first header value or {@code null} if there is no such header
*/ */
public abstract String get(String name); public String get(CharSequence name) {
return get(name.toString());
}
/**
* @see {@link #getAll(CharSequence)}
*/
public abstract List<String> getAll(String name);
/** /**
* Returns the values of headers with the specified name * Returns the values of headers with the specified name
@ -1205,7 +1426,9 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @return A {@link List} of header values which will be empty if no values * @return A {@link List} of header values which will be empty if no values
* are found * are found
*/ */
public abstract List<String> getAll(String name); public List<String> getAll(CharSequence name) {
return getAll(name.toString());
}
/** /**
* Returns the all headers that this message contains. * Returns the all headers that this message contains.
@ -1215,13 +1438,20 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
*/ */
public abstract List<Map.Entry<String, String>> entries(); public abstract List<Map.Entry<String, String>> entries();
/**
* @see {@link #contains(CharSequence)}
*/
public abstract boolean contains(String name);
/** /**
* Checks to see if there is a header with the specified name * Checks to see if there is a header with the specified name
* *
* @param name The name of the header to search for * @param name The name of the header to search for
* @return True if at least one header is found * @return True if at least one header is found
*/ */
public abstract boolean contains(String name); public boolean contains(CharSequence name) {
return contains(name.toString());
}
/** /**
* Checks if no header exists. * Checks if no header exists.
@ -1235,6 +1465,11 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
*/ */
public abstract Set<String> names(); public abstract Set<String> names();
/**
* @see {@link #add(CharSequence, Object)}
*/
public abstract HttpHeaders add(String name, Object value);
/** /**
* Adds a new header with the specified name and value. * Adds a new header with the specified name and value.
* *
@ -1248,7 +1483,14 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* *
* @return {@code this} * @return {@code this}
*/ */
public abstract HttpHeaders add(String name, Object value); public HttpHeaders add(CharSequence name, Object value) {
return add(name.toString(), value);
}
/**
* @see {@link #add(CharSequence, Iterable)}
*/
public abstract HttpHeaders add(String name, Iterable<?> values);
/** /**
* Adds a new header with the specified name and values. * Adds a new header with the specified name and values.
@ -1267,7 +1509,9 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @param values The values of the headers being set * @param values The values of the headers being set
* @return {@code this} * @return {@code this}
*/ */
public abstract HttpHeaders add(String name, Iterable<?> values); public HttpHeaders add(CharSequence name, Iterable<?> values) {
return add(name.toString(), values);
}
/** /**
* Adds all header entries of the specified {@code headers}. * Adds all header entries of the specified {@code headers}.
@ -1284,6 +1528,11 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
return this; return this;
} }
/**
* @see {@link #set(CharSequence, Object)}
*/
public abstract HttpHeaders set(String name, Object value);
/** /**
* Sets a header with the specified name and value. * Sets a header with the specified name and value.
* *
@ -1297,7 +1546,14 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @param value The value of the header being set * @param value The value of the header being set
* @return {@code this} * @return {@code this}
*/ */
public abstract HttpHeaders set(String name, Object value); public HttpHeaders set(CharSequence name, Object value) {
return set(name.toString(), value);
}
/**
* @see {@link #set(CharSequence, Iterable)}
*/
public abstract HttpHeaders set(String name, Iterable<?> values);
/** /**
* Sets a header with the specified name and values. * Sets a header with the specified name and values.
@ -1318,7 +1574,9 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @param values The values of the headers being set * @param values The values of the headers being set
* @return {@code this} * @return {@code this}
*/ */
public abstract HttpHeaders set(String name, Iterable<?> values); public HttpHeaders set(CharSequence name, Iterable<?> values) {
return set(name.toString(), values);
}
/** /**
* Cleans the current header entries and copies all header entries of the specified {@code headers}. * Cleans the current header entries and copies all header entries of the specified {@code headers}.
@ -1336,13 +1594,20 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
return this; return this;
} }
/**
* @see {@link #remove(CharSequence)}
*/
public abstract HttpHeaders remove(String name);
/** /**
* Removes the header with the specified name. * Removes the header with the specified name.
* *
* @param name The name of the header to remove * @param name The name of the header to remove
* @return {@code this} * @return {@code this}
*/ */
public abstract HttpHeaders remove(String name); public HttpHeaders remove(CharSequence name) {
return remove(name.toString());
}
/** /**
* Removes all headers from this {@link HttpMessage}. * Removes all headers from this {@link HttpMessage}.
@ -1352,12 +1617,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
public abstract HttpHeaders clear(); public abstract HttpHeaders clear();
/** /**
* Returns {@code true} if a header with the name and value exists. * @see {@link #contains(CharSequence, CharSequence, boolean)}
*
* @param name the headername
* @param value the value
* @param ignoreCaseValue {@code true} if case should be ignored
* @return contains {@code true} if it contains it {@code false} otherwise
*/ */
public boolean contains(String name, String value, boolean ignoreCaseValue) { public boolean contains(String name, String value, boolean ignoreCaseValue) {
List<String> values = getAll(name); List<String> values = getAll(name);
@ -1378,4 +1638,16 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
} }
return false; return false;
} }
/**
* Returns {@code true} if a header with the name and value exists.
*
* @param name the headername
* @param value the value
* @param ignoreCaseValue {@code true} if case should be ignored
* @return contains {@code true} if it contains it {@code false} otherwise
*/
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCaseValue) {
return contains(name.toString(), value.toString(), ignoreCaseValue);
}
} }

View File

@ -15,6 +15,9 @@
*/ */
package io.netty.handler.codec.http; package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -31,7 +34,7 @@ public class HttpMethod implements Comparable<HttpMethod> {
* capabilities of a server, without implying a resource action or initiating a resource * capabilities of a server, without implying a resource action or initiating a resource
* retrieval. * retrieval.
*/ */
public static final HttpMethod OPTIONS = new HttpMethod("OPTIONS"); public static final HttpMethod OPTIONS = new HttpMethod("OPTIONS", true);
/** /**
* The GET getMethod means retrieve whatever information (in the form of an entity) is identified * The GET getMethod means retrieve whatever information (in the form of an entity) is identified
@ -39,49 +42,49 @@ public class HttpMethod implements Comparable<HttpMethod> {
* produced data which shall be returned as the entity in the response and not the source text * produced data which shall be returned as the entity in the response and not the source text
* of the process, unless that text happens to be the output of the process. * of the process, unless that text happens to be the output of the process.
*/ */
public static final HttpMethod GET = new HttpMethod("GET"); public static final HttpMethod GET = new HttpMethod("GET", true);
/** /**
* The HEAD getMethod is identical to GET except that the server MUST NOT return a message-body * The HEAD getMethod is identical to GET except that the server MUST NOT return a message-body
* in the response. * in the response.
*/ */
public static final HttpMethod HEAD = new HttpMethod("HEAD"); public static final HttpMethod HEAD = new HttpMethod("HEAD", true);
/** /**
* The POST getMethod is used to request that the origin server accept the entity enclosed in the * The POST getMethod is used to request that the origin server accept the entity enclosed in the
* request as a new subordinate of the resource identified by the Request-URI in the * request as a new subordinate of the resource identified by the Request-URI in the
* Request-Line. * Request-Line.
*/ */
public static final HttpMethod POST = new HttpMethod("POST"); public static final HttpMethod POST = new HttpMethod("POST", true);
/** /**
* The PUT getMethod requests that the enclosed entity be stored under the supplied Request-URI. * The PUT getMethod requests that the enclosed entity be stored under the supplied Request-URI.
*/ */
public static final HttpMethod PUT = new HttpMethod("PUT"); public static final HttpMethod PUT = new HttpMethod("PUT", true);
/** /**
* The PATCH getMethod requests that a set of changes described in the * The PATCH getMethod requests that a set of changes described in the
* request entity be applied to the resource identified by the Request-URI. * request entity be applied to the resource identified by the Request-URI.
*/ */
public static final HttpMethod PATCH = new HttpMethod("PATCH"); public static final HttpMethod PATCH = new HttpMethod("PATCH", true);
/** /**
* The DELETE getMethod requests that the origin server delete the resource identified by the * The DELETE getMethod requests that the origin server delete the resource identified by the
* Request-URI. * Request-URI.
*/ */
public static final HttpMethod DELETE = new HttpMethod("DELETE"); public static final HttpMethod DELETE = new HttpMethod("DELETE", true);
/** /**
* The TRACE getMethod is used to invoke a remote, application-layer loop- back of the request * The TRACE getMethod is used to invoke a remote, application-layer loop- back of the request
* message. * message.
*/ */
public static final HttpMethod TRACE = new HttpMethod("TRACE"); public static final HttpMethod TRACE = new HttpMethod("TRACE", true);
/** /**
* This specification reserves the getMethod name CONNECT for use with a proxy that can dynamically * This specification reserves the getMethod name CONNECT for use with a proxy that can dynamically
* switch to being a tunnel * switch to being a tunnel
*/ */
public static final HttpMethod CONNECT = new HttpMethod("CONNECT"); public static final HttpMethod CONNECT = new HttpMethod("CONNECT", true);
private static final Map<String, HttpMethod> methodMap = private static final Map<String, HttpMethod> methodMap =
new HashMap<String, HttpMethod>(); new HashMap<String, HttpMethod>();
@ -122,6 +125,7 @@ public class HttpMethod implements Comparable<HttpMethod> {
} }
private final String name; private final String name;
private final byte[] bytes;
/** /**
* Creates a new HTTP getMethod with the specified name. You will not need to * Creates a new HTTP getMethod with the specified name. You will not need to
@ -131,6 +135,10 @@ public class HttpMethod implements Comparable<HttpMethod> {
* <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a> * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>
*/ */
public HttpMethod(String name) { public HttpMethod(String name) {
this(name, false);
}
private HttpMethod(String name, boolean bytes) {
if (name == null) { if (name == null) {
throw new NullPointerException("name"); throw new NullPointerException("name");
} }
@ -148,6 +156,11 @@ public class HttpMethod implements Comparable<HttpMethod> {
} }
this.name = name; this.name = name;
if (bytes) {
this.bytes = name.getBytes(CharsetUtil.US_ASCII);
} else {
this.bytes = null;
}
} }
/** /**
@ -181,4 +194,12 @@ public class HttpMethod implements Comparable<HttpMethod> {
public int compareTo(HttpMethod o) { public int compareTo(HttpMethod o) {
return name().compareTo(o.name()); return name().compareTo(o.name());
} }
void encode(ByteBuf buf) {
if (bytes == null) {
HttpHeaders.encodeAscii0(name, buf);
} else {
buf.writeBytes(bytes);
}
}
} }

View File

@ -23,7 +23,6 @@ import io.netty.util.CharsetUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import java.util.List; import java.util.List;
import java.util.Map;
import static io.netty.buffer.Unpooled.*; import static io.netty.buffer.Unpooled.*;
import static io.netty.handler.codec.http.HttpConstants.*; import static io.netty.handler.codec.http.HttpConstants.*;
@ -45,7 +44,6 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
private static final byte[] CRLF = { CR, LF }; private static final byte[] CRLF = { CR, LF };
private static final byte[] ZERO_CRLF = { '0', CR, LF }; private static final byte[] ZERO_CRLF = { '0', CR, LF };
private static final byte[] ZERO_CRLF_CRLF = { '0', CR, LF, CR, LF }; private static final byte[] ZERO_CRLF_CRLF = { '0', CR, LF, CR, LF };
private static final byte[] HEADER_SEPARATOR = { COLON, SP };
private static final ByteBuf CRLF_BUF = unreleasableBuffer(directBuffer(CRLF.length).writeBytes(CRLF)); private static final ByteBuf CRLF_BUF = unreleasableBuffer(directBuffer(CRLF.length).writeBytes(CRLF));
private static final ByteBuf ZERO_CRLF_CRLF_BUF = unreleasableBuffer(directBuffer(ZERO_CRLF_CRLF.length) private static final ByteBuf ZERO_CRLF_CRLF_BUF = unreleasableBuffer(directBuffer(ZERO_CRLF_CRLF.length)
.writeBytes(ZERO_CRLF_CRLF)); .writeBytes(ZERO_CRLF_CRLF));
@ -70,7 +68,7 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
ByteBuf buf = ctx.alloc().buffer(); ByteBuf buf = ctx.alloc().buffer();
// Encode the message. // Encode the message.
encodeInitialLine(buf, m); encodeInitialLine(buf, m);
encodeHeaders(buf, m.headers()); HttpHeaders.encode(m.headers(), buf);
buf.writeBytes(CRLF); buf.writeBytes(CRLF);
out.add(buf); out.add(buf);
state = HttpHeaders.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK; state = HttpHeaders.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
@ -119,7 +117,7 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
} else { } else {
ByteBuf buf = ctx.alloc().buffer(); ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes(ZERO_CRLF); buf.writeBytes(ZERO_CRLF);
encodeHeaders(buf, headers); HttpHeaders.encode(headers, buf);
buf.writeBytes(CRLF); buf.writeBytes(CRLF);
out.add(buf); out.add(buf);
} }
@ -165,23 +163,9 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg)); throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
} }
private static void encodeHeaders(ByteBuf buf, HttpHeaders headers) { @Deprecated
for (Map.Entry<String, String> h: headers) {
encodeHeader(buf, h.getKey(), h.getValue());
}
}
private static void encodeHeader(ByteBuf buf, String header, String value) {
encodeAscii(header, buf);
buf.writeBytes(HEADER_SEPARATOR);
encodeAscii(value, buf);
buf.writeBytes(CRLF);
}
protected static void encodeAscii(String s, ByteBuf buf) { protected static void encodeAscii(String s, ByteBuf buf) {
for (int i = 0; i < s.length(); i++) { HttpHeaders.encodeAscii0(s, buf);
buf.writeByte(s.charAt(i));
}
} }
protected abstract void encodeInitialLine(ByteBuf buf, H message) throws Exception; protected abstract void encodeInitialLine(ByteBuf buf, H message) throws Exception;

View File

@ -35,7 +35,7 @@ public class HttpRequestEncoder extends HttpObjectEncoder<HttpRequest> {
@Override @Override
protected void encodeInitialLine(ByteBuf buf, HttpRequest request) throws Exception { protected void encodeInitialLine(ByteBuf buf, HttpRequest request) throws Exception {
encodeAscii(request.getMethod().toString(), buf); request.getMethod().encode(buf);
buf.writeByte(SP); buf.writeByte(SP);
// Add / as absolute path if no is present. // Add / as absolute path if no is present.
@ -57,7 +57,7 @@ public class HttpRequestEncoder extends HttpObjectEncoder<HttpRequest> {
buf.writeBytes(uri.getBytes(CharsetUtil.UTF_8)); buf.writeBytes(uri.getBytes(CharsetUtil.UTF_8));
buf.writeByte(SP); buf.writeByte(SP);
encodeAscii(request.getProtocolVersion().toString(), buf); request.getProtocolVersion().encode(buf);
buf.writeBytes(CRLF); buf.writeBytes(CRLF);
} }
} }

View File

@ -16,7 +16,6 @@
package io.netty.handler.codec.http; package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
import static io.netty.handler.codec.http.HttpConstants.*; import static io.netty.handler.codec.http.HttpConstants.*;
@ -34,11 +33,9 @@ public class HttpResponseEncoder extends HttpObjectEncoder<HttpResponse> {
@Override @Override
protected void encodeInitialLine(ByteBuf buf, HttpResponse response) throws Exception { protected void encodeInitialLine(ByteBuf buf, HttpResponse response) throws Exception {
encodeAscii(response.getProtocolVersion().toString(), buf); response.getProtocolVersion().encode(buf);
buf.writeByte(SP); buf.writeByte(SP);
encodeAscii(String.valueOf(response.getStatus().code()), buf); response.getStatus().encode(buf);
buf.writeByte(SP);
encodeAscii(String.valueOf(response.getStatus().reasonPhrase()), buf);
buf.writeBytes(CRLF); buf.writeBytes(CRLF);
} }
} }

View File

@ -15,6 +15,11 @@
*/ */
package io.netty.handler.codec.http; 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;
/** /**
* The response code and its description of HTTP or its derived protocols, such as * The response code and its description of HTTP or its derived protocols, such as
* <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
@ -25,282 +30,292 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
/** /**
* 100 Continue * 100 Continue
*/ */
public static final HttpResponseStatus CONTINUE = new HttpResponseStatus(100, "Continue"); public static final HttpResponseStatus CONTINUE = new HttpResponseStatus(100, "Continue", true);
/** /**
* 101 Switching Protocols * 101 Switching Protocols
*/ */
public static final HttpResponseStatus SWITCHING_PROTOCOLS = new HttpResponseStatus(101, "Switching Protocols"); public static final HttpResponseStatus SWITCHING_PROTOCOLS =
new HttpResponseStatus(101, "Switching Protocols", true);
/** /**
* 102 Processing (WebDAV, RFC2518) * 102 Processing (WebDAV, RFC2518)
*/ */
public static final HttpResponseStatus PROCESSING = new HttpResponseStatus(102, "Processing"); public static final HttpResponseStatus PROCESSING = new HttpResponseStatus(102, "Processing", true);
/** /**
* 200 OK * 200 OK
*/ */
public static final HttpResponseStatus OK = new HttpResponseStatus(200, "OK"); public static final HttpResponseStatus OK = new HttpResponseStatus(200, "OK", true);
/** /**
* 201 Created * 201 Created
*/ */
public static final HttpResponseStatus CREATED = new HttpResponseStatus(201, "Created"); public static final HttpResponseStatus CREATED = new HttpResponseStatus(201, "Created", true);
/** /**
* 202 Accepted * 202 Accepted
*/ */
public static final HttpResponseStatus ACCEPTED = new HttpResponseStatus(202, "Accepted"); public static final HttpResponseStatus ACCEPTED = new HttpResponseStatus(202, "Accepted", true);
/** /**
* 203 Non-Authoritative Information (since HTTP/1.1) * 203 Non-Authoritative Information (since HTTP/1.1)
*/ */
public static final HttpResponseStatus NON_AUTHORITATIVE_INFORMATION = public static final HttpResponseStatus NON_AUTHORITATIVE_INFORMATION =
new HttpResponseStatus(203, "Non-Authoritative Information"); new HttpResponseStatus(203, "Non-Authoritative Information", true);
/** /**
* 204 No Content * 204 No Content
*/ */
public static final HttpResponseStatus NO_CONTENT = new HttpResponseStatus(204, "No Content"); public static final HttpResponseStatus NO_CONTENT = new HttpResponseStatus(204, "No Content", true);
/** /**
* 205 Reset Content * 205 Reset Content
*/ */
public static final HttpResponseStatus RESET_CONTENT = new HttpResponseStatus(205, "Reset Content"); public static final HttpResponseStatus RESET_CONTENT = new HttpResponseStatus(205, "Reset Content", true);
/** /**
* 206 Partial Content * 206 Partial Content
*/ */
public static final HttpResponseStatus PARTIAL_CONTENT = new HttpResponseStatus(206, "Partial Content"); public static final HttpResponseStatus PARTIAL_CONTENT = new HttpResponseStatus(206, "Partial Content", true);
/** /**
* 207 Multi-Status (WebDAV, RFC2518) * 207 Multi-Status (WebDAV, RFC2518)
*/ */
public static final HttpResponseStatus MULTI_STATUS = new HttpResponseStatus(207, "Multi-Status"); public static final HttpResponseStatus MULTI_STATUS = new HttpResponseStatus(207, "Multi-Status", true);
/** /**
* 300 Multiple Choices * 300 Multiple Choices
*/ */
public static final HttpResponseStatus MULTIPLE_CHOICES = new HttpResponseStatus(300, "Multiple Choices"); public static final HttpResponseStatus MULTIPLE_CHOICES = new HttpResponseStatus(300, "Multiple Choices", true);
/** /**
* 301 Moved Permanently * 301 Moved Permanently
*/ */
public static final HttpResponseStatus MOVED_PERMANENTLY = new HttpResponseStatus(301, "Moved Permanently"); public static final HttpResponseStatus MOVED_PERMANENTLY = new HttpResponseStatus(301, "Moved Permanently", true);
/** /**
* 302 Found * 302 Found
*/ */
public static final HttpResponseStatus FOUND = new HttpResponseStatus(302, "Found"); public static final HttpResponseStatus FOUND = new HttpResponseStatus(302, "Found", true);
/** /**
* 303 See Other (since HTTP/1.1) * 303 See Other (since HTTP/1.1)
*/ */
public static final HttpResponseStatus SEE_OTHER = new HttpResponseStatus(303, "See Other"); public static final HttpResponseStatus SEE_OTHER = new HttpResponseStatus(303, "See Other", true);
/** /**
* 304 Not Modified * 304 Not Modified
*/ */
public static final HttpResponseStatus NOT_MODIFIED = new HttpResponseStatus(304, "Not Modified"); public static final HttpResponseStatus NOT_MODIFIED = new HttpResponseStatus(304, "Not Modified", true);
/** /**
* 305 Use Proxy (since HTTP/1.1) * 305 Use Proxy (since HTTP/1.1)
*/ */
public static final HttpResponseStatus USE_PROXY = new HttpResponseStatus(305, "Use Proxy"); public static final HttpResponseStatus USE_PROXY = new HttpResponseStatus(305, "Use Proxy", true);
/** /**
* 307 Temporary Redirect (since HTTP/1.1) * 307 Temporary Redirect (since HTTP/1.1)
*/ */
public static final HttpResponseStatus TEMPORARY_REDIRECT = new HttpResponseStatus(307, "Temporary Redirect"); public static final HttpResponseStatus TEMPORARY_REDIRECT = new HttpResponseStatus(307, "Temporary Redirect", true);
/** /**
* 400 Bad Request * 400 Bad Request
*/ */
public static final HttpResponseStatus BAD_REQUEST = new HttpResponseStatus(400, "Bad Request"); public static final HttpResponseStatus BAD_REQUEST = new HttpResponseStatus(400, "Bad Request", true);
/** /**
* 401 Unauthorized * 401 Unauthorized
*/ */
public static final HttpResponseStatus UNAUTHORIZED = new HttpResponseStatus(401, "Unauthorized"); public static final HttpResponseStatus UNAUTHORIZED = new HttpResponseStatus(401, "Unauthorized", true);
/** /**
* 402 Payment Required * 402 Payment Required
*/ */
public static final HttpResponseStatus PAYMENT_REQUIRED = new HttpResponseStatus(402, "Payment Required"); public static final HttpResponseStatus PAYMENT_REQUIRED = new HttpResponseStatus(402, "Payment Required", true);
/** /**
* 403 Forbidden * 403 Forbidden
*/ */
public static final HttpResponseStatus FORBIDDEN = new HttpResponseStatus(403, "Forbidden"); public static final HttpResponseStatus FORBIDDEN = new HttpResponseStatus(403, "Forbidden", true);
/** /**
* 404 Not Found * 404 Not Found
*/ */
public static final HttpResponseStatus NOT_FOUND = new HttpResponseStatus(404, "Not Found"); public static final HttpResponseStatus NOT_FOUND = new HttpResponseStatus(404, "Not Found", true);
/** /**
* 405 Method Not Allowed * 405 Method Not Allowed
*/ */
public static final HttpResponseStatus METHOD_NOT_ALLOWED = new HttpResponseStatus(405, "Method Not Allowed"); public static final HttpResponseStatus METHOD_NOT_ALLOWED = new HttpResponseStatus(405, "Method Not Allowed", true);
/** /**
* 406 Not Acceptable * 406 Not Acceptable
*/ */
public static final HttpResponseStatus NOT_ACCEPTABLE = new HttpResponseStatus(406, "Not Acceptable"); public static final HttpResponseStatus NOT_ACCEPTABLE = new HttpResponseStatus(406, "Not Acceptable", true);
/** /**
* 407 Proxy Authentication Required * 407 Proxy Authentication Required
*/ */
public static final HttpResponseStatus PROXY_AUTHENTICATION_REQUIRED = public static final HttpResponseStatus PROXY_AUTHENTICATION_REQUIRED =
new HttpResponseStatus(407, "Proxy Authentication Required"); new HttpResponseStatus(407, "Proxy Authentication Required", true);
/** /**
* 408 Request Timeout * 408 Request Timeout
*/ */
public static final HttpResponseStatus REQUEST_TIMEOUT = new HttpResponseStatus(408, "Request Timeout"); public static final HttpResponseStatus REQUEST_TIMEOUT = new HttpResponseStatus(408, "Request Timeout", true);
/** /**
* 409 Conflict * 409 Conflict
*/ */
public static final HttpResponseStatus CONFLICT = new HttpResponseStatus(409, "Conflict"); public static final HttpResponseStatus CONFLICT = new HttpResponseStatus(409, "Conflict", true);
/** /**
* 410 Gone * 410 Gone
*/ */
public static final HttpResponseStatus GONE = new HttpResponseStatus(410, "Gone"); public static final HttpResponseStatus GONE = new HttpResponseStatus(410, "Gone", true);
/** /**
* 411 Length Required * 411 Length Required
*/ */
public static final HttpResponseStatus LENGTH_REQUIRED = new HttpResponseStatus(411, "Length Required"); public static final HttpResponseStatus LENGTH_REQUIRED = new HttpResponseStatus(411, "Length Required", true);
/** /**
* 412 Precondition Failed * 412 Precondition Failed
*/ */
public static final HttpResponseStatus PRECONDITION_FAILED = new HttpResponseStatus(412, "Precondition Failed"); public static final HttpResponseStatus PRECONDITION_FAILED =
new HttpResponseStatus(412, "Precondition Failed", true);
/** /**
* 413 Request Entity Too Large * 413 Request Entity Too Large
*/ */
public static final HttpResponseStatus REQUEST_ENTITY_TOO_LARGE = public static final HttpResponseStatus REQUEST_ENTITY_TOO_LARGE =
new HttpResponseStatus(413, "Request Entity Too Large"); new HttpResponseStatus(413, "Request Entity Too Large", true);
/** /**
* 414 Request-URI Too Long * 414 Request-URI Too Long
*/ */
public static final HttpResponseStatus REQUEST_URI_TOO_LONG = new HttpResponseStatus(414, "Request-URI Too Long"); public static final HttpResponseStatus REQUEST_URI_TOO_LONG =
new HttpResponseStatus(414, "Request-URI Too Long", true);
/** /**
* 415 Unsupported Media Type * 415 Unsupported Media Type
*/ */
public static final HttpResponseStatus UNSUPPORTED_MEDIA_TYPE = public static final HttpResponseStatus UNSUPPORTED_MEDIA_TYPE =
new HttpResponseStatus(415, "Unsupported Media Type"); new HttpResponseStatus(415, "Unsupported Media Type", true);
/** /**
* 416 Requested Range Not Satisfiable * 416 Requested Range Not Satisfiable
*/ */
public static final HttpResponseStatus REQUESTED_RANGE_NOT_SATISFIABLE = public static final HttpResponseStatus REQUESTED_RANGE_NOT_SATISFIABLE =
new HttpResponseStatus(416, "Requested Range Not Satisfiable"); new HttpResponseStatus(416, "Requested Range Not Satisfiable", true);
/** /**
* 417 Expectation Failed * 417 Expectation Failed
*/ */
public static final HttpResponseStatus EXPECTATION_FAILED = new HttpResponseStatus(417, "Expectation Failed"); public static final HttpResponseStatus EXPECTATION_FAILED =
new HttpResponseStatus(417, "Expectation Failed", true);
/** /**
* 422 Unprocessable Entity (WebDAV, RFC4918) * 422 Unprocessable Entity (WebDAV, RFC4918)
*/ */
public static final HttpResponseStatus UNPROCESSABLE_ENTITY = new HttpResponseStatus(422, "Unprocessable Entity"); public static final HttpResponseStatus UNPROCESSABLE_ENTITY =
new HttpResponseStatus(422, "Unprocessable Entity", true);
/** /**
* 423 Locked (WebDAV, RFC4918) * 423 Locked (WebDAV, RFC4918)
*/ */
public static final HttpResponseStatus LOCKED = new HttpResponseStatus(423, "Locked"); public static final HttpResponseStatus LOCKED =
new HttpResponseStatus(423, "Locked", true);
/** /**
* 424 Failed Dependency (WebDAV, RFC4918) * 424 Failed Dependency (WebDAV, RFC4918)
*/ */
public static final HttpResponseStatus FAILED_DEPENDENCY = new HttpResponseStatus(424, "Failed Dependency"); public static final HttpResponseStatus FAILED_DEPENDENCY = new HttpResponseStatus(424, "Failed Dependency", true);
/** /**
* 425 Unordered Collection (WebDAV, RFC3648) * 425 Unordered Collection (WebDAV, RFC3648)
*/ */
public static final HttpResponseStatus UNORDERED_COLLECTION = new HttpResponseStatus(425, "Unordered Collection"); public static final HttpResponseStatus UNORDERED_COLLECTION =
new HttpResponseStatus(425, "Unordered Collection", true);
/** /**
* 426 Upgrade Required (RFC2817) * 426 Upgrade Required (RFC2817)
*/ */
public static final HttpResponseStatus UPGRADE_REQUIRED = new HttpResponseStatus(426, "Upgrade Required"); public static final HttpResponseStatus UPGRADE_REQUIRED = new HttpResponseStatus(426, "Upgrade Required", true);
/** /**
* 428 Precondition Required (RFC6585) * 428 Precondition Required (RFC6585)
*/ */
public static final HttpResponseStatus PRECONDITION_REQUIRED = new HttpResponseStatus(428, "Precondition Required"); public static final HttpResponseStatus PRECONDITION_REQUIRED =
new HttpResponseStatus(428, "Precondition Required", true);
/** /**
* 429 Too Many Requests (RFC6585) * 429 Too Many Requests (RFC6585)
*/ */
public static final HttpResponseStatus TOO_MANY_REQUESTS = new HttpResponseStatus(429, "Too Many Requests"); public static final HttpResponseStatus TOO_MANY_REQUESTS = new HttpResponseStatus(429, "Too Many Requests", true);
/** /**
* 431 Request Header Fields Too Large (RFC6585) * 431 Request Header Fields Too Large (RFC6585)
*/ */
public static final HttpResponseStatus REQUEST_HEADER_FIELDS_TOO_LARGE = public static final HttpResponseStatus REQUEST_HEADER_FIELDS_TOO_LARGE =
new HttpResponseStatus(431, "Request Header Fields Too Large"); new HttpResponseStatus(431, "Request Header Fields Too Large", true);
/** /**
* 500 Internal Server Error * 500 Internal Server Error
*/ */
public static final HttpResponseStatus INTERNAL_SERVER_ERROR = public static final HttpResponseStatus INTERNAL_SERVER_ERROR =
new HttpResponseStatus(500, "Internal Server Error"); new HttpResponseStatus(500, "Internal Server Error", true);
/** /**
* 501 Not Implemented * 501 Not Implemented
*/ */
public static final HttpResponseStatus NOT_IMPLEMENTED = new HttpResponseStatus(501, "Not Implemented"); public static final HttpResponseStatus NOT_IMPLEMENTED = new HttpResponseStatus(501, "Not Implemented", true);
/** /**
* 502 Bad Gateway * 502 Bad Gateway
*/ */
public static final HttpResponseStatus BAD_GATEWAY = new HttpResponseStatus(502, "Bad Gateway"); public static final HttpResponseStatus BAD_GATEWAY = new HttpResponseStatus(502, "Bad Gateway", true);
/** /**
* 503 Service Unavailable * 503 Service Unavailable
*/ */
public static final HttpResponseStatus SERVICE_UNAVAILABLE = new HttpResponseStatus(503, "Service Unavailable"); public static final HttpResponseStatus SERVICE_UNAVAILABLE =
new HttpResponseStatus(503, "Service Unavailable", true);
/** /**
* 504 Gateway Timeout * 504 Gateway Timeout
*/ */
public static final HttpResponseStatus GATEWAY_TIMEOUT = new HttpResponseStatus(504, "Gateway Timeout"); public static final HttpResponseStatus GATEWAY_TIMEOUT = new HttpResponseStatus(504, "Gateway Timeout", true);
/** /**
* 505 HTTP Version Not Supported * 505 HTTP Version Not Supported
*/ */
public static final HttpResponseStatus HTTP_VERSION_NOT_SUPPORTED = public static final HttpResponseStatus HTTP_VERSION_NOT_SUPPORTED =
new HttpResponseStatus(505, "HTTP Version Not Supported"); new HttpResponseStatus(505, "HTTP Version Not Supported", true);
/** /**
* 506 Variant Also Negotiates (RFC2295) * 506 Variant Also Negotiates (RFC2295)
*/ */
public static final HttpResponseStatus VARIANT_ALSO_NEGOTIATES = public static final HttpResponseStatus VARIANT_ALSO_NEGOTIATES =
new HttpResponseStatus(506, "Variant Also Negotiates"); new HttpResponseStatus(506, "Variant Also Negotiates", true);
/** /**
* 507 Insufficient Storage (WebDAV, RFC4918) * 507 Insufficient Storage (WebDAV, RFC4918)
*/ */
public static final HttpResponseStatus INSUFFICIENT_STORAGE = new HttpResponseStatus(507, "Insufficient Storage"); public static final HttpResponseStatus INSUFFICIENT_STORAGE =
new HttpResponseStatus(507, "Insufficient Storage", true);
/** /**
* 510 Not Extended (RFC2774) * 510 Not Extended (RFC2774)
*/ */
public static final HttpResponseStatus NOT_EXTENDED = new HttpResponseStatus(510, "Not Extended"); public static final HttpResponseStatus NOT_EXTENDED = new HttpResponseStatus(510, "Not Extended", true);
/** /**
* 511 Network Authentication Required (RFC6585) * 511 Network Authentication Required (RFC6585)
*/ */
public static final HttpResponseStatus NETWORK_AUTHENTICATION_REQUIRED = public static final HttpResponseStatus NETWORK_AUTHENTICATION_REQUIRED =
new HttpResponseStatus(511, "Network Authentication Required"); new HttpResponseStatus(511, "Network Authentication Required", true);
/** /**
* Returns the {@link HttpResponseStatus} represented by the specified code. * Returns the {@link HttpResponseStatus} represented by the specified code.
@ -443,12 +458,17 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
private final int code; private final int code;
private final String reasonPhrase; private final String reasonPhrase;
private final byte[] bytes;
/** /**
* Creates a new instance with the specified {@code code} and its * Creates a new instance with the specified {@code code} and its
* {@code reasonPhrase}. * {@code reasonPhrase}.
*/ */
public HttpResponseStatus(int code, String reasonPhrase) { public HttpResponseStatus(int code, String reasonPhrase) {
this(code, reasonPhrase, false);
}
private HttpResponseStatus(int code, String reasonPhrase, boolean bytes) {
if (code < 0) { if (code < 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"code: " + code + " (expected: 0+)"); "code: " + code + " (expected: 0+)");
@ -462,15 +482,20 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
char c = reasonPhrase.charAt(i); char c = reasonPhrase.charAt(i);
// Check prohibited characters. // Check prohibited characters.
switch (c) { switch (c) {
case '\n': case '\r': case '\n': case '\r':
throw new IllegalArgumentException( throw new IllegalArgumentException(
"reasonPhrase contains one of the following prohibited characters: " + "reasonPhrase contains one of the following prohibited characters: " +
"\\r\\n: " + reasonPhrase); "\\r\\n: " + reasonPhrase);
} }
} }
this.code = code; this.code = code;
this.reasonPhrase = reasonPhrase; this.reasonPhrase = reasonPhrase;
if (bytes) {
this.bytes = (code + " " + reasonPhrase).getBytes(CharsetUtil.US_ASCII);
} else {
this.bytes = null;
}
} }
/** /**
@ -514,4 +539,14 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
buf.append(reasonPhrase); buf.append(reasonPhrase);
return buf.toString(); return buf.toString();
} }
void encode(ByteBuf buf) {
if (bytes == null) {
HttpHeaders.encodeAscii0(String.valueOf(code()), buf);
buf.writeByte(SP);
HttpHeaders.encodeAscii0(String.valueOf(reasonPhrase()), buf);
} else {
buf.writeBytes(bytes);
}
}
} }

View File

@ -15,6 +15,9 @@
*/ */
package io.netty.handler.codec.http; package io.netty.handler.codec.http;
import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -34,12 +37,12 @@ public class HttpVersion implements Comparable<HttpVersion> {
/** /**
* HTTP/1.0 * HTTP/1.0
*/ */
public static final HttpVersion HTTP_1_0 = new HttpVersion("HTTP", 1, 0, false); public static final HttpVersion HTTP_1_0 = new HttpVersion("HTTP", 1, 0, false, true);
/** /**
* HTTP/1.1 * HTTP/1.1
*/ */
public static final HttpVersion HTTP_1_1 = new HttpVersion("HTTP", 1, 1, true); public static final HttpVersion HTTP_1_1 = new HttpVersion("HTTP", 1, 1, true, true);
/** /**
* Returns an existing or new {@link HttpVersion} instance which matches to * Returns an existing or new {@link HttpVersion} instance which matches to
@ -99,6 +102,7 @@ public class HttpVersion implements Comparable<HttpVersion> {
private final int minorVersion; private final int minorVersion;
private final String text; private final String text;
private final boolean keepAliveDefault; private final boolean keepAliveDefault;
private final byte[] bytes;
/** /**
* Creates a new HTTP version with the specified version string. You will * Creates a new HTTP version with the specified version string. You will
@ -131,6 +135,7 @@ public class HttpVersion implements Comparable<HttpVersion> {
minorVersion = Integer.parseInt(m.group(3)); minorVersion = Integer.parseInt(m.group(3));
this.text = protocolName + '/' + majorVersion + '.' + minorVersion; this.text = protocolName + '/' + majorVersion + '.' + minorVersion;
this.keepAliveDefault = keepAliveDefault; this.keepAliveDefault = keepAliveDefault;
bytes = null;
} }
/** /**
@ -147,6 +152,12 @@ public class HttpVersion implements Comparable<HttpVersion> {
public HttpVersion( public HttpVersion(
String protocolName, int majorVersion, int minorVersion, String protocolName, int majorVersion, int minorVersion,
boolean keepAliveDefault) { boolean keepAliveDefault) {
this(protocolName, majorVersion, minorVersion, keepAliveDefault, false);
}
private HttpVersion(
String protocolName, int majorVersion, int minorVersion,
boolean keepAliveDefault, boolean bytes) {
if (protocolName == null) { if (protocolName == null) {
throw new NullPointerException("protocolName"); throw new NullPointerException("protocolName");
} }
@ -158,7 +169,7 @@ public class HttpVersion implements Comparable<HttpVersion> {
for (int i = 0; i < protocolName.length(); i ++) { for (int i = 0; i < protocolName.length(); i ++) {
if (Character.isISOControl(protocolName.charAt(i)) || if (Character.isISOControl(protocolName.charAt(i)) ||
Character.isWhitespace(protocolName.charAt(i))) { Character.isWhitespace(protocolName.charAt(i))) {
throw new IllegalArgumentException("invalid character in protocolName"); throw new IllegalArgumentException("invalid character in protocolName");
} }
} }
@ -175,6 +186,12 @@ public class HttpVersion implements Comparable<HttpVersion> {
this.minorVersion = minorVersion; this.minorVersion = minorVersion;
text = protocolName + '/' + majorVersion + '.' + minorVersion; text = protocolName + '/' + majorVersion + '.' + minorVersion;
this.keepAliveDefault = keepAliveDefault; this.keepAliveDefault = keepAliveDefault;
if (bytes) {
this.bytes = text.getBytes(CharsetUtil.US_ASCII);
} else {
this.bytes = null;
}
} }
/** /**
@ -253,4 +270,12 @@ public class HttpVersion implements Comparable<HttpVersion> {
return minorVersion() - o.minorVersion(); return minorVersion() - o.minorVersion();
} }
void encode(ByteBuf buf) {
if (bytes == null) {
HttpHeaders.encodeAscii0(text, buf);
} else {
buf.writeBytes(bytes);
}
}
} }