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

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;
import io.netty.buffer.ByteBuf;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Collections;
@ -26,12 +28,32 @@ import java.util.Map.Entry;
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
* commonly used utility methods that accesses an {@link HttpMessage}.
*/
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() {
@Override
public String get(String name) {
@ -545,15 +567,15 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* {@link HttpVersion#isKeepAliveDefault()}.
*/
public static boolean isKeepAlive(HttpMessage message) {
String connection = message.headers().get(Names.CONNECTION);
if (connection != null && equalsIgnoreCase(Values.CLOSE, connection)) {
String connection = message.headers().get(CONNECTION_ENTITY);
if (connection != null && equalsIgnoreCase(CLOSE_ENTITY, connection)) {
return false;
}
if (message.getProtocolVersion().isKeepAliveDefault()) {
return !equalsIgnoreCase(Values.CLOSE, connection);
return !equalsIgnoreCase(CLOSE_ENTITY, connection);
} 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();
if (message.getProtocolVersion().isKeepAliveDefault()) {
if (keepAlive) {
h.remove(Names.CONNECTION);
h.remove(CONNECTION_ENTITY);
} else {
h.set(Names.CONNECTION, Values.CLOSE);
h.set(CONNECTION_ENTITY, CLOSE_ENTITY);
}
} else {
if (keepAlive) {
h.set(Names.CONNECTION, Values.KEEP_ALIVE);
h.set(CONNECTION_ENTITY, KEEP_ALIVE_ENTITY);
} else {
h.remove(Names.CONNECTION);
h.remove(CONNECTION_ENTITY);
}
}
}
/**
* 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
* @see {@link #getHeader(HttpMessage, CharSequence)}
*/
public static String getHeader(HttpMessage message, String 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
* 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
* 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);
if (value == null) {
return defaultValue;
@ -620,6 +656,13 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
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
* 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
* <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);
}
/**
*
* @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
* 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>
*/
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);
}
/**
* @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.
* 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
* <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);
}
/**
* @see {@link #removeHeader(HttpMessage, CharSequence)}
*/
public static void removeHeader(HttpMessage message, String name) {
message.headers().remove(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);
}
@ -675,6 +740,13 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
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
* 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
* 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);
if (value == null) {
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);
}
/**
* @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
* 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
* 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);
if (value == null) {
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
* is an existing header with the same name, the existing header is removed.
* @see {@link #setIntHeader(HttpMessage, CharSequence, int)}
*/
public static void setIntHeader(HttpMessage message, String name, int 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.
*/
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) {
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) {
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
* 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
* 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);
if (value == null) {
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);
}
/**
* @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
* 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
* 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);
if (value == null) {
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
* is an existing header with the same name, the existing header is removed.
* The specified value is formatted as defined in
* <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) {
message.headers().set(name, HttpHeaderDateFormat.get().format(value));
} 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
* is an existing header with the same name, the existing header is removed.
* The specified values are formatted as defined in
* <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);
}
/**
* @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
* value is formatted as defined in
* <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);
}
@ -820,7 +956,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* or its value is not a number
*/
public static long getContentLength(HttpMessage message) {
String value = getHeader(message, Names.CONTENT_LENGTH);
String value = getHeader(message, CONTENT_LENGTH_ENTITY);
if (value != null) {
return Long.parseLong(value);
}
@ -847,7 +983,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* a number
*/
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) {
try {
return Long.parseLong(contentLength);
@ -877,15 +1013,15 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
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)) {
h.contains(SEC_WEBSOCKET_KEY1_ENTITY) &&
h.contains(SEC_WEBSOCKET_KEY2_ENTITY)) {
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)) {
h.contains(SEC_WEBSOCKET_ORIGIN_ENTITY) &&
h.contains(SEC_WEBSOCKET_LOCATION_ENTITY)) {
return 16;
}
}
@ -898,14 +1034,14 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* Sets the {@code "Content-Length"} header.
*/
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.
*/
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.
*/
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.
*/
public static void setHost(HttpMessage message, String value) {
message.headers().set(Names.HOST, value);
public static void setHost(HttpMessage message, CharSequence 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
*/
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.
*/
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) {
if (value != null) {
message.headers().set(Names.DATE, HttpHeaderDateFormat.get().format(value));
message.headers().set(DATE_ENTITY, HttpHeaderDateFormat.get().format(value));
} 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.
String value = message.headers().get(Names.EXPECT);
String value = message.headers().get(EXPECT_ENTITY);
if (value == null) {
return false;
}
if (equalsIgnoreCase(Values.CONTINUE, value)) {
if (equalsIgnoreCase(CONTINUE_ENTITY, value)) {
return true;
}
// 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) {
if (set) {
message.headers().set(Names.EXPECT, Values.CONTINUE);
message.headers().set(EXPECT_ENTITY, CONTINUE_ENTITY);
} 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
*/
static void validateHeaderName(String headerName) {
static void validateHeaderName(CharSequence headerName) {
//Check to see if the name is null
if (headerName == 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
*/
static void validateHeaderValue(String headerValue) {
static void validateHeaderValue(CharSequence headerValue) {
//Check to see if the value is null
if (headerValue == 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
*/
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) {
List<String> values = m.headers().getAll(Names.TRANSFER_ENCODING);
List<String> values = m.headers().getAll(TRANSFER_ENCODING_ENTITY);
if (values.isEmpty()) {
return;
}
Iterator<String> valuesIt = values.iterator();
while (valuesIt.hasNext()) {
String value = valuesIt.next();
if (equalsIgnoreCase(value, Values.CHUNKED)) {
if (equalsIgnoreCase(value, CHUNKED_ENTITY)) {
valuesIt.remove();
}
}
if (values.isEmpty()) {
m.headers().remove(Names.TRANSFER_ENCODING);
m.headers().remove(TRANSFER_ENCODING_ENTITY);
} else {
m.headers().set(Names.TRANSFER_ENCODING, values);
m.headers().set(TRANSFER_ENCODING_ENTITY, values);
}
}
public static void setTransferEncodingChunked(HttpMessage m) {
addHeader(m, Names.TRANSFER_ENCODING, Values.CHUNKED);
removeHeader(m, Names.CONTENT_LENGTH);
addHeader(m, TRANSFER_ENCODING_ENTITY, CHUNKED_ENTITY);
removeHeader(m, CONTENT_LENGTH_ENTITY);
}
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;
}
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() { }
/**
* @see {@link #get(CharSequence)}
*/
public abstract String get(String name);
/**
* 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.
@ -1196,7 +1410,14 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @param name The name of the header to search
* @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
@ -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
* 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.
@ -1215,13 +1438,20 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
*/
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
*
* @param name The name of the header to search for
* @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.
@ -1235,6 +1465,11 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
*/
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.
*
@ -1248,7 +1483,14 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
*
* @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.
@ -1267,7 +1509,9 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @param values The values of the headers being set
* @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}.
@ -1284,6 +1528,11 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
return this;
}
/**
* @see {@link #set(CharSequence, Object)}
*/
public abstract HttpHeaders set(String name, Object 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
* @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.
@ -1318,7 +1574,9 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
* @param values The values of the headers being set
* @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}.
@ -1336,13 +1594,20 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
return this;
}
/**
* @see {@link #remove(CharSequence)}
*/
public abstract HttpHeaders remove(String name);
/**
* Removes the header with the specified name.
*
* @param name The name of the header to remove
* @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}.
@ -1352,12 +1617,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
public abstract HttpHeaders clear();
/**
* 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
* @see {@link #contains(CharSequence, CharSequence, boolean)}
*/
public boolean contains(String name, String value, boolean ignoreCaseValue) {
List<String> values = getAll(name);
@ -1378,4 +1638,16 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
}
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;
import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
import java.util.HashMap;
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
* 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
@ -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
* 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
* 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
* request as a new subordinate of the resource identified by the Request-URI in the
* 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.
*/
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
* 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
* 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
* 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
* 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 =
new HashMap<String, HttpMethod>();
@ -122,6 +125,7 @@ public class HttpMethod implements Comparable<HttpMethod> {
}
private final String name;
private final byte[] bytes;
/**
* 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>
*/
public HttpMethod(String name) {
this(name, false);
}
private HttpMethod(String name, boolean bytes) {
if (name == null) {
throw new NullPointerException("name");
}
@ -148,6 +156,11 @@ public class HttpMethod implements Comparable<HttpMethod> {
}
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) {
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 java.util.List;
import java.util.Map;
import static io.netty.buffer.Unpooled.*;
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[] ZERO_CRLF = { '0', 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 ZERO_CRLF_CRLF_BUF = unreleasableBuffer(directBuffer(ZERO_CRLF_CRLF.length)
.writeBytes(ZERO_CRLF_CRLF));
@ -70,7 +68,7 @@ public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageTo
ByteBuf buf = ctx.alloc().buffer();
// Encode the message.
encodeInitialLine(buf, m);
encodeHeaders(buf, m.headers());
HttpHeaders.encode(m.headers(), buf);
buf.writeBytes(CRLF);
out.add(buf);
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 {
ByteBuf buf = ctx.alloc().buffer();
buf.writeBytes(ZERO_CRLF);
encodeHeaders(buf, headers);
HttpHeaders.encode(headers, buf);
buf.writeBytes(CRLF);
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));
}
private static void encodeHeaders(ByteBuf buf, HttpHeaders headers) {
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);
}
@Deprecated
protected static void encodeAscii(String s, ByteBuf buf) {
for (int i = 0; i < s.length(); i++) {
buf.writeByte(s.charAt(i));
}
HttpHeaders.encodeAscii0(s, buf);
}
protected abstract void encodeInitialLine(ByteBuf buf, H message) throws Exception;

View File

@ -35,7 +35,7 @@ public class HttpRequestEncoder extends HttpObjectEncoder<HttpRequest> {
@Override
protected void encodeInitialLine(ByteBuf buf, HttpRequest request) throws Exception {
encodeAscii(request.getMethod().toString(), buf);
request.getMethod().encode(buf);
buf.writeByte(SP);
// 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.writeByte(SP);
encodeAscii(request.getProtocolVersion().toString(), buf);
request.getProtocolVersion().encode(buf);
buf.writeBytes(CRLF);
}
}

View File

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

View File

@ -15,6 +15,11 @@
*/
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
* <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
*/
public static final HttpResponseStatus CONTINUE = new HttpResponseStatus(100, "Continue");
public static final HttpResponseStatus CONTINUE = new HttpResponseStatus(100, "Continue", true);
/**
* 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)
*/
public static final HttpResponseStatus PROCESSING = new HttpResponseStatus(102, "Processing");
public static final HttpResponseStatus PROCESSING = new HttpResponseStatus(102, "Processing", true);
/**
* 200 OK
*/
public static final HttpResponseStatus OK = new HttpResponseStatus(200, "OK");
public static final HttpResponseStatus OK = new HttpResponseStatus(200, "OK", true);
/**
* 201 Created
*/
public static final HttpResponseStatus CREATED = new HttpResponseStatus(201, "Created");
public static final HttpResponseStatus CREATED = new HttpResponseStatus(201, "Created", true);
/**
* 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)
*/
public static final HttpResponseStatus NON_AUTHORITATIVE_INFORMATION =
new HttpResponseStatus(203, "Non-Authoritative Information");
new HttpResponseStatus(203, "Non-Authoritative Information", true);
/**
* 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
*/
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
*/
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)
*/
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
*/
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
*/
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
*/
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)
*/
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
*/
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)
*/
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)
*/
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
*/
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
*/
public static final HttpResponseStatus UNAUTHORIZED = new HttpResponseStatus(401, "Unauthorized");
public static final HttpResponseStatus UNAUTHORIZED = new HttpResponseStatus(401, "Unauthorized", true);
/**
* 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
*/
public static final HttpResponseStatus FORBIDDEN = new HttpResponseStatus(403, "Forbidden");
public static final HttpResponseStatus FORBIDDEN = new HttpResponseStatus(403, "Forbidden", true);
/**
* 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
*/
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
*/
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
*/
public static final HttpResponseStatus PROXY_AUTHENTICATION_REQUIRED =
new HttpResponseStatus(407, "Proxy Authentication Required");
new HttpResponseStatus(407, "Proxy Authentication Required", true);
/**
* 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
*/
public static final HttpResponseStatus CONFLICT = new HttpResponseStatus(409, "Conflict");
public static final HttpResponseStatus CONFLICT = new HttpResponseStatus(409, "Conflict", true);
/**
* 410 Gone
*/
public static final HttpResponseStatus GONE = new HttpResponseStatus(410, "Gone");
public static final HttpResponseStatus GONE = new HttpResponseStatus(410, "Gone", true);
/**
* 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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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)
*/
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)
*/
public static final HttpResponseStatus LOCKED = new HttpResponseStatus(423, "Locked");
public static final HttpResponseStatus LOCKED =
new HttpResponseStatus(423, "Locked", true);
/**
* 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)
*/
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)
*/
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)
*/
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)
*/
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)
*/
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
*/
public static final HttpResponseStatus INTERNAL_SERVER_ERROR =
new HttpResponseStatus(500, "Internal Server Error");
new HttpResponseStatus(500, "Internal Server Error", true);
/**
* 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
*/
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
*/
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
*/
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
*/
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)
*/
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)
*/
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)
*/
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)
*/
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.
@ -443,12 +458,17 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
private final int code;
private final String reasonPhrase;
private final byte[] bytes;
/**
* Creates a new instance with the specified {@code code} and its
* {@code reasonPhrase}.
*/
public HttpResponseStatus(int code, String reasonPhrase) {
this(code, reasonPhrase, false);
}
private HttpResponseStatus(int code, String reasonPhrase, boolean bytes) {
if (code < 0) {
throw new IllegalArgumentException(
"code: " + code + " (expected: 0+)");
@ -462,15 +482,20 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
char c = reasonPhrase.charAt(i);
// Check prohibited characters.
switch (c) {
case '\n': case '\r':
throw new IllegalArgumentException(
"reasonPhrase contains one of the following prohibited characters: " +
"\\r\\n: " + reasonPhrase);
case '\n': case '\r':
throw new IllegalArgumentException(
"reasonPhrase contains one of the following prohibited characters: " +
"\\r\\n: " + reasonPhrase);
}
}
this.code = code;
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);
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;
import io.netty.buffer.ByteBuf;
import io.netty.util.CharsetUtil;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -34,12 +37,12 @@ public class HttpVersion implements Comparable<HttpVersion> {
/**
* 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
*/
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
@ -99,6 +102,7 @@ public class HttpVersion implements Comparable<HttpVersion> {
private final int minorVersion;
private final String text;
private final boolean keepAliveDefault;
private final byte[] bytes;
/**
* 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));
this.text = protocolName + '/' + majorVersion + '.' + minorVersion;
this.keepAliveDefault = keepAliveDefault;
bytes = null;
}
/**
@ -147,6 +152,12 @@ public class HttpVersion implements Comparable<HttpVersion> {
public HttpVersion(
String protocolName, int majorVersion, int minorVersion,
boolean keepAliveDefault) {
this(protocolName, majorVersion, minorVersion, keepAliveDefault, false);
}
private HttpVersion(
String protocolName, int majorVersion, int minorVersion,
boolean keepAliveDefault, boolean bytes) {
if (protocolName == null) {
throw new NullPointerException("protocolName");
}
@ -158,7 +169,7 @@ public class HttpVersion implements Comparable<HttpVersion> {
for (int i = 0; i < protocolName.length(); i ++) {
if (Character.isISOControl(protocolName.charAt(i)) ||
Character.isWhitespace(protocolName.charAt(i))) {
Character.isWhitespace(protocolName.charAt(i))) {
throw new IllegalArgumentException("invalid character in protocolName");
}
}
@ -175,6 +186,12 @@ public class HttpVersion implements Comparable<HttpVersion> {
this.minorVersion = minorVersion;
text = protocolName + '/' + majorVersion + '.' + minorVersion;
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();
}
void encode(ByteBuf buf) {
if (bytes == null) {
HttpHeaders.encodeAscii0(text, buf);
} else {
buf.writeBytes(bytes);
}
}
}