diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java index 902ae4c91d..09963221b3 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java @@ -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 getAll(final String name) { + return getAll((CharSequence) name); + } + + @Override + public List 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> { private HeaderEntry current = head; @@ -368,17 +438,23 @@ public class DefaultHttpHeaders extends HttpHeaders { private final class HeaderEntry implements Map.Entry { 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); } } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java index 5c50f42a49..599c543d64 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java @@ -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); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderEntity.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderEntity.java new file mode 100644 index 0000000000..c667782bc9 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderEntity.java @@ -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); + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java index 7ba9264109..0450f8d895 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java @@ -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> { + 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> * {@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> 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> * @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> 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> * and {@link Calendar} which are formatted to the date format defined in * RFC2616. */ - 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> * } * */ - 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> * and {@link Calendar} which are formatted to the date format defined in * RFC2616. */ - 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> 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> * @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> 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> * @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> } /** - * 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 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 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> * @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> 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> * @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> } } + /** + * @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 * RFC2616 */ - 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> } } + /** + * @see {@link #setDateHeader(HttpMessage, CharSequence, Iterable)} + */ + public static void setDateHeader(HttpMessage message, String name, Iterable 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 * RFC2616 */ - public static void setDateHeader(HttpMessage message, String name, Iterable values) { + public static void setDateHeader(HttpMessage message, CharSequence name, Iterable 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 * RFC2616 */ - 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> * 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> * 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> 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> * 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> * 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> * 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> * 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> */ 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> } // 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> */ 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> * * @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> * * @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> * @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 values = m.headers().getAll(Names.TRANSFER_ENCODING); + List values = m.headers().getAll(TRANSFER_ENCODING_ENTITY); if (values.isEmpty()) { return; } Iterator 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> } 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 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> * @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 getAll(String name); /** * Returns the values of headers with the specified name @@ -1205,7 +1426,9 @@ public abstract class HttpHeaders implements Iterable> * @return A {@link List} of header values which will be empty if no values * are found */ - public abstract List getAll(String name); + public List 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> */ public abstract List> 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> */ public abstract Set 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> * * @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> * @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> 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> * @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> * @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> 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> 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 values = getAll(name); @@ -1378,4 +1638,16 @@ public abstract class HttpHeaders implements Iterable> } 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); + } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java index 908abe11a7..6abe639fdb 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java @@ -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 { * 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 { * 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 methodMap = new HashMap(); @@ -122,6 +125,7 @@ public class HttpMethod implements Comparable { } 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 { * ICAP */ 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 { } 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 { 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); + } + } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java index 35312ab5f4..15bd1e1cf6 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java @@ -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 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 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 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 extends MessageTo throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg)); } - private static void encodeHeaders(ByteBuf buf, HttpHeaders headers) { - for (Map.Entry 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; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestEncoder.java index 3dc959bd1d..52bb90fa86 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestEncoder.java @@ -35,7 +35,7 @@ public class HttpRequestEncoder extends HttpObjectEncoder { @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 { buf.writeBytes(uri.getBytes(CharsetUtil.UTF_8)); buf.writeByte(SP); - encodeAscii(request.getProtocolVersion().toString(), buf); + request.getProtocolVersion().encode(buf); buf.writeBytes(CRLF); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseEncoder.java index ad0d73bcc3..07e2414998 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseEncoder.java @@ -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 { @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); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java index ca6d48c86e..985d85291f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseStatus.java @@ -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 * RTSP and @@ -25,282 +30,292 @@ public class HttpResponseStatus implements Comparable { /** * 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 { 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 { 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 { 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); + } + } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java index 1e954dbd74..26be9ebe62 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java @@ -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 { /** * 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 { 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 { 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 { 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 { 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 { 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 { return minorVersion() - o.minorVersion(); } + + void encode(ByteBuf buf) { + if (bytes == null) { + HttpHeaders.encodeAscii0(text, buf); + } else { + buf.writeBytes(bytes); + } + } }