diff --git a/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpChunkTrailer.java b/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpChunkTrailer.java index 3b59db8687..d245b354f6 100644 --- a/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpChunkTrailer.java +++ b/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpChunkTrailer.java @@ -15,17 +15,12 @@ */ package org.jboss.netty.handler.codec.http; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeMap; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.util.internal.CaseIgnoringComparator; /** * The default {@link HttpChunkTrailer} implementation. @@ -36,95 +31,61 @@ import org.jboss.netty.util.internal.CaseIgnoringComparator; */ public class DefaultHttpChunkTrailer implements HttpChunkTrailer { - // FIXME Lots of code duplication with DefaultHttpMessage - private final Map> headers = new TreeMap>(CaseIgnoringComparator.INSTANCE); + private final HttpHeaders headers = new HttpHeaders() { + @Override + void validateHeaderName(String name) { + super.validateHeaderName(name); + if (name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) || + name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) || + name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) { + throw new IllegalArgumentException( + "prohibited trailing header: " + name); + } + } + }; public boolean isLast() { return true; } public void addHeader(final String name, final String value) { - validateHeaderName(name); - HttpCodecUtil.validateHeaderValue(value); - if (headers.get(name) == null) { - headers.put(name, new ArrayList(1)); - } - headers.get(name).add(value); + headers.addHeader(name, value); } public void setHeader(final String name, final String value) { - validateHeaderName(name); - HttpCodecUtil.validateHeaderValue(value); - List values = new ArrayList(1); - values.add(value); - headers.put(name, values); + headers.setHeader(name, value); } public void setHeader(final String name, final Iterable values) { - validateHeaderName(name); - if (values == null) { - throw new NullPointerException("values"); - } - - int nValues = 0; - for (String v: values) { - HttpCodecUtil.validateHeaderValue(v); - nValues ++; - } - - if (nValues == 0) { - throw new IllegalArgumentException("values is empty."); - } - - if (values instanceof List) { - headers.put(name, (List) values); - } else { - List valueList = new LinkedList(); - for (String v: values) { - valueList.add(v); - } - headers.put(name, valueList); - } - } - - private static void validateHeaderName(String name) { - HttpCodecUtil.validateHeaderName(name); - if (name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) || - name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) || - name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) { - throw new IllegalArgumentException( - "prohibited trailing header: " + name); - } + headers.setHeader(name, values); } public void removeHeader(final String name) { - headers.remove(name); + headers.removeHeader(name); } public void clearHeaders() { - headers.clear(); + headers.clearHeaders(); } public String getHeader(final String name) { - List header = headers.get(name); - return header != null && header.size() > 0 ? headers.get(name).get(0) : null; + return headers.getHeader(name); } public List getHeaders(final String name) { - List values = headers.get(name); - if (values == null) { - return Collections.emptyList(); - } else { - return values; - } + return headers.getHeaders(name); + } + + public List> getHeaders() { + return headers.getHeaders(); } public boolean containsHeader(final String name) { - return headers.containsKey(name); + return headers.containsHeader(name); } public Set getHeaderNames() { - return headers.keySet(); + return headers.getHeaderNames(); } public ChannelBuffer getContent() { diff --git a/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpMessage.java b/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpMessage.java index c95361bf04..3313421704 100644 --- a/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpMessage.java +++ b/src/main/java/org/jboss/netty/handler/codec/http/DefaultHttpMessage.java @@ -15,17 +15,12 @@ */ package org.jboss.netty.handler.codec.http; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeMap; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.util.internal.CaseIgnoringComparator; /** * The default {@link HttpMessage} implementation. @@ -37,7 +32,7 @@ import org.jboss.netty.util.internal.CaseIgnoringComparator; */ public class DefaultHttpMessage implements HttpMessage { - private final Map> headers = new TreeMap>(CaseIgnoringComparator.INSTANCE); + private final HttpHeaders headers = new HttpHeaders(); private HttpVersion version; private ChannelBuffer content = ChannelBuffers.EMPTY_BUFFER; private boolean chunked; @@ -50,51 +45,19 @@ public class DefaultHttpMessage implements HttpMessage { } public void addHeader(final String name, final String value) { - HttpCodecUtil.validateHeaderName(name); - HttpCodecUtil.validateHeaderValue(value); - if (headers.get(name) == null) { - headers.put(name, new ArrayList(1)); - } - headers.get(name).add(value); + headers.addHeader(name, value); } public void setHeader(final String name, final String value) { - HttpCodecUtil.validateHeaderName(name); - HttpCodecUtil.validateHeaderValue(value); - List values = new ArrayList(1); - values.add(value); - headers.put(name, values); + headers.setHeader(name, value); } public void setHeader(final String name, final Iterable values) { - HttpCodecUtil.validateHeaderName(name); - if (values == null) { - throw new NullPointerException("values"); - } - - int nValues = 0; - for (String v: values) { - HttpCodecUtil.validateHeaderValue(v); - nValues ++; - } - - if (nValues == 0) { - throw new IllegalArgumentException("values is empty."); - } - - if (values instanceof List) { - headers.put(name, (List) values); - } else { - List valueList = new LinkedList(); - for (String v: values) { - valueList.add(v); - } - headers.put(name, valueList); - } + headers.setHeader(name, values); } public void removeHeader(final String name) { - headers.remove(name); + headers.removeHeader(name); } public long getContentLength() { @@ -102,7 +65,7 @@ public class DefaultHttpMessage implements HttpMessage { } public long getContentLength(long defaultValue) { - List contentLength = headers.get(HttpHeaders.Names.CONTENT_LENGTH); + List contentLength = getHeaders(HttpHeaders.Names.CONTENT_LENGTH); if (contentLength != null && contentLength.size() > 0) { return Long.parseLong(contentLength.get(0)); } @@ -137,7 +100,7 @@ public class DefaultHttpMessage implements HttpMessage { } public void clearHeaders() { - headers.clear(); + headers.clearHeaders(); } public void setContent(ChannelBuffer content) { @@ -152,25 +115,24 @@ public class DefaultHttpMessage implements HttpMessage { } public String getHeader(final String name) { - List header = headers.get(name); - return header != null && header.size() > 0 ? headers.get(name).get(0) : null; + List values = getHeaders(name); + return values.size() > 0 ? values.get(0) : null; } public List getHeaders(final String name) { - List values = headers.get(name); - if (values == null) { - return Collections.emptyList(); - } else { - return values; - } + return headers.getHeaders(name); + } + + public List> getHeaders() { + return headers.getHeaders(); } public boolean containsHeader(final String name) { - return headers.containsKey(name); + return headers.containsHeader(name); } public Set getHeaderNames() { - return headers.keySet(); + return headers.getHeaderNames(); } public HttpVersion getProtocolVersion() { diff --git a/src/main/java/org/jboss/netty/handler/codec/http/HttpChunk.java b/src/main/java/org/jboss/netty/handler/codec/http/HttpChunk.java index 51d7cb5d96..fe820436d6 100644 --- a/src/main/java/org/jboss/netty/handler/codec/http/HttpChunk.java +++ b/src/main/java/org/jboss/netty/handler/codec/http/HttpChunk.java @@ -17,6 +17,7 @@ package org.jboss.netty.handler.codec.http; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import org.jboss.netty.buffer.ChannelBuffer; @@ -79,6 +80,10 @@ public interface HttpChunk { return Collections.emptyList(); } + public List> getHeaders() { + return Collections.emptyList(); + } + public void removeHeader(String name) { // NOOP } diff --git a/src/main/java/org/jboss/netty/handler/codec/http/HttpChunkTrailer.java b/src/main/java/org/jboss/netty/handler/codec/http/HttpChunkTrailer.java index 5f336693ea..09ebbd873c 100644 --- a/src/main/java/org/jboss/netty/handler/codec/http/HttpChunkTrailer.java +++ b/src/main/java/org/jboss/netty/handler/codec/http/HttpChunkTrailer.java @@ -16,6 +16,7 @@ package org.jboss.netty.handler.codec.http; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -50,6 +51,14 @@ public interface HttpChunkTrailer extends HttpChunk { */ List getHeaders(String name); + /** + * Returns the all header names and values that this trailer contains. + * + * @return the {@link List} of the header name-value pairs. An empty list + * if there is no header in this trailer. + */ + List> getHeaders(); + /** * Returns {@code true} if and only if there is a trailing header with * the specified header name. diff --git a/src/main/java/org/jboss/netty/handler/codec/http/HttpHeaders.java b/src/main/java/org/jboss/netty/handler/codec/http/HttpHeaders.java index eab62a6714..c0822cb2df 100644 --- a/src/main/java/org/jboss/netty/handler/codec/http/HttpHeaders.java +++ b/src/main/java/org/jboss/netty/handler/codec/http/HttpHeaders.java @@ -15,6 +15,14 @@ */ package org.jboss.netty.handler.codec.http; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.jboss.netty.util.internal.CaseIgnoringComparator; + /** * Standard HTTP header names and values. * @@ -24,7 +32,7 @@ package org.jboss.netty.handler.codec.http; * * @apiviz.stereotype static */ -public final class HttpHeaders { +public class HttpHeaders { /** * Standard HTTP header names. @@ -384,6 +392,10 @@ public final class HttpHeaders { * {@code "trailers"} */ public static final String TRAILERS = "trailers"; + /** + * {@code "Upgrade"} + */ + public static final String UPGRADE = "Upgrade"; /** * {@code "WebSocket"} */ @@ -394,7 +406,273 @@ public final class HttpHeaders { } } - private HttpHeaders() { - super(); + 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 boolean eq(String name1, String name2) { + int nameLen = name1.length(); + if (nameLen != name2.length()) { + return false; + } + + for (int i = nameLen - 1; i >= 0; i --) { + char c1 = name1.charAt(i); + char c2 = name2.charAt(i); + if (c1 != c2) { + if (c1 >= 'A' && c1 <= 'Z') { + c1 += 32; + } + if (c2 >= 'A' && c2 <= 'Z') { + c2 += 32; + } + if (c1 != c2) { + return false; + } + } + } + return true; + } + + private final Entry[] entries = new Entry[17]; + private final Entry head = new Entry(-1, null, null); + + HttpHeaders() { + head.before = head.after = head; + } + + private int index(int hash) { + return hash % entries.length; + } + + void validateHeaderName(String name) { + HttpCodecUtil.validateHeaderName(name); + } + + void addHeader(final String name, final String value) { + validateHeaderName(name); + HttpCodecUtil.validateHeaderValue(value); + int h = hash(name); + int i = index(h); + addHeader0(h, i, name, value); + } + + private void addHeader0(int h, int i, final String name, final String value) { + // Update the hash table. + Entry e = entries[i]; + Entry newEntry; + if (e == null) { + entries[i] = newEntry = new Entry(h, name, value); + } else { + entries[i] = newEntry = new Entry(h, name, value); + newEntry.next = e; + } + + // Update the linked list. + newEntry.addBefore(head); + } + + void removeHeader(final String name) { + int h = hash(name); + int i = index(h); + removeHeader0(h, i, name); + } + + private void removeHeader0(int h, int i, String name) { + Entry e = entries[i]; + if (e == null) { + return; + } + + for (;;) { + if (e.hash == h && eq(name, e.key)) { + Entry next = e.next; + if (next != null) { + entries[i] = next; + e = next; + } else { + entries[i] = null; + return; + } + } else { + break; + } + } + + for (;;) { + Entry next = e.next; + if (next == null) { + break; + } + if (next.hash == h && eq(name, next.key)) { + e.next = next.next; + next.remove(); + } else { + e = next; + } + } + } + + void setHeader(final String name, final String value) { + validateHeaderName(name); + HttpCodecUtil.validateHeaderValue(value); + int h = hash(name); + int i = index(h); + removeHeader0(h, i, name); + addHeader0(h, i, name, value); + } + + void setHeader(final String name, final Iterable values) { + validateHeaderName(name); + if (values == null) { + throw new NullPointerException("values"); + } + + boolean empty = true; + for (String v: values) { + if (v == null) { + break; + } + HttpCodecUtil.validateHeaderValue(v); + empty = false; + } + + int h = hash(name); + int i = index(h); + + removeHeader0(h, i, name); + if (empty) { + return; + } + + for (String v: values) { + if (v == null) { + break; + } + addHeader0(h, i, name, v); + } + } + + void clearHeaders() { + for (int i = 0; i < entries.length; i ++) { + entries[i] = null; + } + head.before = head.after = head; + } + + String getHeader(final String name) { + int h = hash(name); + int i = index(h); + Entry e = entries[i]; + while (e != null) { + if (e.hash == h && eq(name, e.key)) { + return e.value; + } + + e = e.next; + } + return null; + } + + List getHeaders(final String name) { + LinkedList values = new LinkedList(); + + int h = hash(name); + int i = index(h); + Entry e = entries[i]; + while (e != null) { + if (e.hash == h && eq(name, e.key)) { + values.addFirst(e.value); + } + e = e.next; + } + return values; + } + + List> getHeaders() { + List> all = + new LinkedList>(); + + Entry e = head.after; + while (e != head) { + all.add(e); + e = e.after; + } + return all; + } + + boolean containsHeader(String name) { + return getHeader(name) != null; + } + + Set getHeaderNames() { + Set names = + new TreeSet(CaseIgnoringComparator.INSTANCE); + + Entry e = head.after; + while (e != head) { + names.add(e.key); + e = e.after; + } + return names; + } + + private static final class Entry implements Map.Entry { + final int hash; + final String key; + String value; + Entry next; + Entry before, after; + + Entry(int hash, String key, String value) { + this.hash = hash; + this.key = key; + this.value = value; + } + + void remove() { + before.after = after; + after.before = before; + } + + void addBefore(Entry e) { + after = e; + before = e.before; + before.after = this; + after.before = this; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public String setValue(String value) { + if (value == null) { + throw new NullPointerException("value"); + } + HttpCodecUtil.validateHeaderValue(value); + String oldValue = this.value; + this.value = value; + return oldValue; + } } } diff --git a/src/main/java/org/jboss/netty/handler/codec/http/HttpMessage.java b/src/main/java/org/jboss/netty/handler/codec/http/HttpMessage.java index 3f4608c102..589f0a65b4 100644 --- a/src/main/java/org/jboss/netty/handler/codec/http/HttpMessage.java +++ b/src/main/java/org/jboss/netty/handler/codec/http/HttpMessage.java @@ -16,6 +16,7 @@ package org.jboss.netty.handler.codec.http; import java.util.List; +import java.util.Map; import java.util.Set; import org.jboss.netty.buffer.ChannelBuffer; @@ -55,6 +56,14 @@ public interface HttpMessage { */ List getHeaders(String name); + /** + * Returns the all header names and values that this message contains. + * + * @return the {@link List} of the header name-value pairs. An empty list + * if there is no header in this message. + */ + List> getHeaders(); + /** * Returns {@code true} if and only if there is a header with the specified * header name. diff --git a/src/main/java/org/jboss/netty/handler/codec/http/HttpMessageEncoder.java b/src/main/java/org/jboss/netty/handler/codec/http/HttpMessageEncoder.java index 01581581ac..cec5b4d633 100644 --- a/src/main/java/org/jboss/netty/handler/codec/http/HttpMessageEncoder.java +++ b/src/main/java/org/jboss/netty/handler/codec/http/HttpMessageEncoder.java @@ -19,8 +19,7 @@ import static org.jboss.netty.buffer.ChannelBuffers.*; import static org.jboss.netty.handler.codec.http.HttpCodecUtil.*; import java.io.UnsupportedEncodingException; -import java.util.List; -import java.util.Set; +import java.util.Map; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; @@ -131,13 +130,9 @@ public abstract class HttpMessageEncoder extends OneToOneEncoder { } private void encodeHeaders(ChannelBuffer buf, HttpMessage message) { - Set headers = message.getHeaderNames(); try { - for (String header : headers) { - List values = message.getHeaders(header); - for (String value : values) { - encodeHeader(buf, header, value); - } + for (Map.Entry h: message.getHeaders()) { + encodeHeader(buf, h.getKey(), h.getValue()); } } catch (UnsupportedEncodingException e) { throw (Error) new Error().initCause(e); @@ -145,13 +140,9 @@ public abstract class HttpMessageEncoder extends OneToOneEncoder { } private void encodeTrailingHeaders(ChannelBuffer buf, HttpChunkTrailer trailer) { - Set headers = trailer.getHeaderNames(); try { - for (String header : headers) { - List values = trailer.getHeaders(header); - for (String value : values) { - encodeHeader(buf, header, value); - } + for (Map.Entry h: trailer.getHeaders()) { + encodeHeader(buf, h.getKey(), h.getValue()); } } catch (UnsupportedEncodingException e) { throw (Error) new Error().initCause(e);